diff --git a/Discord.Net.targets b/Discord.Net.targets index 4ee3ada7e..94d2524d4 100644 --- a/Discord.Net.targets +++ b/Discord.Net.targets @@ -2,6 +2,7 @@ 1.1.0-alpha + latest RogueException discord;discordapp https://github.com/RogueException/Discord.Net @@ -18,7 +19,7 @@ build-$(BuildNumber) - $(DefineConstants);FILESYSTEM;DEFAULTUDPCLIENT;DEFAULTWEBSOCKET + $(DefineConstants);FILESYSTEM;DEFAULTUDPCLIENT;DEFAULTWEBSOCKET;MSBUFFER $(DefineConstants);FORMATSTR;UNIXTIME;MSTRYBUFFER;UDPDISPOSE diff --git a/src/Discord.Net.Core/Discord.Net.Core.csproj b/src/Discord.Net.Core/Discord.Net.Core.csproj index bce577ddd..adbed2ca0 100644 --- a/src/Discord.Net.Core/Discord.Net.Core.csproj +++ b/src/Discord.Net.Core/Discord.Net.Core.csproj @@ -5,10 +5,16 @@ Discord The core components for the Discord.Net library. net45;netstandard1.1;netstandard1.3 + true - + + + + + + \ No newline at end of file diff --git a/src/Discord.Net.Core/Net/Rest/IRestClient.cs b/src/Discord.Net.Core/Net/Rest/IRestClient.cs index addfa9061..61e05d07b 100644 --- a/src/Discord.Net.Core/Net/Rest/IRestClient.cs +++ b/src/Discord.Net.Core/Net/Rest/IRestClient.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -10,7 +11,7 @@ namespace Discord.Net.Rest void SetCancelToken(CancellationToken cancelToken); Task SendAsync(string method, string endpoint, CancellationToken cancelToken, bool headerOnly = false, string reason = null); - Task SendAsync(string method, string endpoint, string json, CancellationToken cancelToken, bool headerOnly = false, string reason = null); + Task SendAsync(string method, string endpoint, ReadOnlyBuffer jsonPayload, CancellationToken cancelToken, bool headerOnly = false, string reason = null); Task SendAsync(string method, string endpoint, IReadOnlyDictionary multipartParams, CancellationToken cancelToken, bool headerOnly = false, string reason = null); } } diff --git a/src/Discord.Net.Core/Net/Rest/RestResponse.cs b/src/Discord.Net.Core/Net/Rest/RestResponse.cs index 412ff4dce..ee6be282e 100644 --- a/src/Discord.Net.Core/Net/Rest/RestResponse.cs +++ b/src/Discord.Net.Core/Net/Rest/RestResponse.cs @@ -1,5 +1,5 @@ -using System.Collections.Generic; -using System.IO; +using System; +using System.Collections.Generic; using System.Net; namespace Discord.Net.Rest @@ -8,13 +8,13 @@ namespace Discord.Net.Rest { public HttpStatusCode StatusCode { get; } public Dictionary Headers { get; } - public Stream Stream { get; } + public ReadOnlyBuffer Data { get; } - public RestResponse(HttpStatusCode statusCode, Dictionary headers, Stream stream) + public RestResponse(HttpStatusCode statusCode, Dictionary headers, ReadOnlyBuffer data) { StatusCode = statusCode; Headers = headers; - Stream = stream; + Data = data; } } } diff --git a/src/Discord.Net.Core/Net/WebSockets/IWebSocketClient.cs b/src/Discord.Net.Core/Net/WebSockets/IWebSocketClient.cs index 7eccaabf2..652ec292c 100644 --- a/src/Discord.Net.Core/Net/WebSockets/IWebSocketClient.cs +++ b/src/Discord.Net.Core/Net/WebSockets/IWebSocketClient.cs @@ -6,8 +6,7 @@ namespace Discord.Net.WebSockets { public interface IWebSocketClient { - event Func BinaryMessage; - event Func TextMessage; + event Func, bool, Task> Message; event Func Closed; void SetHeader(string key, string value); @@ -16,6 +15,6 @@ namespace Discord.Net.WebSockets Task ConnectAsync(string host); Task DisconnectAsync(); - Task SendAsync(byte[] data, int index, int count, bool isText); + Task SendAsync(ReadOnlyBuffer data, bool isText); } } diff --git a/src/Discord.Net.Core/_corefxlab/BufferExtensions.cs b/src/Discord.Net.Core/_corefxlab/BufferExtensions.cs new file mode 100644 index 000000000..f6484dbae --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/BufferExtensions.cs @@ -0,0 +1,160 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Sequences; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace System.Buffers +{ + public static class ExperimentalBufferExtensions + { + public static ReadOnlySpan ToSpan(this T bufferSequence) where T : ISequence> + { + Position position = Position.First; + ReadOnlyBuffer buffer; + ResizableArray array = new ResizableArray(1024); + while (bufferSequence.TryGet(ref position, out buffer)) + { + array.AddAll(buffer.Span); + } + array.Resize(array.Count); + return array.Items.Slice(0, array.Count); + } + + /// + /// Creates a new slice over the portion of the target array segment. + /// + /// The target array segment. + /// + public static Span Slice(this ArraySegment arraySegment) + { + return new Span(arraySegment.Array, arraySegment.Offset, arraySegment.Count); + } + + /// + /// Creates a new slice over the portion of the target array. + /// + /// The target array. + /// + /// Thrown if the 'array' parameter is null. + /// + public static Span Slice(this T[] array) + { + return new Span(array); + } + + /// + /// Creates a new slice over the portion of the target array beginning + /// at 'start' index. + /// + /// The target array. + /// The index at which to begin the slice. + /// + /// Thrown if the 'array' parameter is null. + /// + /// + /// Thrown when the specified start index is not in range (<0 or >&eq;length). + /// + public static Span Slice(this T[] array, int start) + { + return new Span(array, start); + } + + /// + /// Creates a new slice over the portion of the target array beginning + /// at 'start' index and with 'length' items. + /// + /// The target array. + /// The index at which to begin the slice. + /// The number of items in the new slice. + /// + /// Thrown if the 'array' parameter is null. + /// + /// + /// Thrown when the specified start or end index is not in range (<0 or >&eq;length). + /// + public static Span Slice(this T[] array, int start, int length) + { + return new Span(array, start, length); + } + + /// + /// Creates a new readonly span over the portion of the target string. + /// + /// The target string. + /// Thrown when is null. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ReadOnlySpan Slice(this string text) + { + if (text == null) + throw new ArgumentNullException(nameof(text)); + + int textLength = text.Length; + + if (textLength == 0) return ReadOnlySpan.Empty; + + fixed (char* charPointer = text) + { + return ReadOnlySpan.DangerousCreate(text, ref *charPointer, textLength); + } + } + + /// + /// Creates a new readonly span over the portion of the target string, beginning at 'start'. + /// + /// The target string. + /// The index at which to begin this slice. + /// Thrown when is null. + /// + /// Thrown when the specified index is not in range (<0 or >Length). + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ReadOnlySpan Slice(this string text, int start) + { + if (text == null) + throw new ArgumentNullException(nameof(text)); + + int textLength = text.Length; + + if ((uint) start > (uint) textLength) + throw new ArgumentOutOfRangeException(nameof(start)); + + if (textLength - start == 0) return ReadOnlySpan.Empty; + + fixed (char* charPointer = text) + { + return ReadOnlySpan.DangerousCreate(text, ref *(charPointer + start), textLength - start); + } + } + + /// + /// Creates a new readonly span over the portion of the target string, beginning at , of given . + /// + /// The target string. + /// The index at which to begin this slice. + /// The number of items in the span. + /// Thrown when is null. + /// + /// Thrown when the specified or end index is not in range (<0 or >=Length). + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ReadOnlySpan Slice(this string text, int start, int length) + { + if (text == null) + throw new ArgumentNullException(nameof(text)); + + int textLength = text.Length; + + if ((uint)start > (uint)textLength || (uint)length > (uint)(textLength - start)) + throw new ArgumentOutOfRangeException(nameof(start)); + + if (length == 0) return ReadOnlySpan.Empty; + + fixed (char* charPointer = text) + { + return ReadOnlySpan.DangerousCreate(text, ref *(charPointer + start), length); + } + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Binary/System/Binary/BufferReader.cs b/src/Discord.Net.Core/_corefxlab/System.Binary/System/Binary/BufferReader.cs new file mode 100644 index 000000000..b0ce4e2ae --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Binary/System/Binary/BufferReader.cs @@ -0,0 +1,109 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Runtime; +using System.Runtime.CompilerServices; + +namespace System.Binary +{ + /// + /// Reads bytes as primitives with specific endianness + /// + /// + /// For native formats, SpanExtensions.Read should be used. + /// Use these helpers when you need to read specific endinanness. + /// + public static class BufferReader + { + /// + /// Reads a structure of type out of a span of bytes. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T ReadBigEndian<[Primitive]T>(this ReadOnlySpan span) where T : struct + => BitConverter.IsLittleEndian ? UnsafeUtilities.Reverse(span.Read()) : span.Read(); + + /// + /// Reads a structure of type out of a span of bytes. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T ReadLittleEndian<[Primitive]T>(this ReadOnlySpan span) where T : struct + => BitConverter.IsLittleEndian ? span.Read() : UnsafeUtilities.Reverse(span.Read()); + + /// + /// Reads a structure of type out of a span of bytes. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T ReadBigEndian<[Primitive]T>(this Span span) where T : struct + => BitConverter.IsLittleEndian ? UnsafeUtilities.Reverse(span.Read()) : span.Read(); + + /// + /// Reads a structure of type out of a span of bytes. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T ReadLittleEndian<[Primitive]T>(this Span span) where T : struct + => BitConverter.IsLittleEndian ? span.Read() : UnsafeUtilities.Reverse(span.Read()); + + /// + /// Reads a structure of type T out of a slice of bytes. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Read<[Primitive]T>(this Span slice) + where T : struct + { + RequiresInInclusiveRange(Unsafe.SizeOf(), (uint)slice.Length); + return Unsafe.ReadUnaligned(ref slice.DangerousGetPinnableReference()); + } + + /// + /// Reads a structure of type T out of a slice of bytes. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Read<[Primitive]T>(this ReadOnlySpan slice) + where T : struct + { + RequiresInInclusiveRange(Unsafe.SizeOf(), (uint)slice.Length); + return Unsafe.ReadUnaligned(ref slice.DangerousGetPinnableReference()); + } + + /// + /// Reads a structure of type T out of a slice of bytes. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryRead<[Primitive]T>(this ReadOnlySpan slice, out T value) + where T : struct + { + if (Unsafe.SizeOf() > (uint)slice.Length) + { + value = default; + return false; + } + value = Unsafe.ReadUnaligned(ref slice.DangerousGetPinnableReference()); + return true; + } + + /// + /// Reads a structure of type T out of a slice of bytes. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryRead<[Primitive]T>(this Span slice, out T value) + where T : struct + { + if (Unsafe.SizeOf() > (uint)slice.Length) + { + value = default; + return false; + } + value = Unsafe.ReadUnaligned(ref slice.DangerousGetPinnableReference()); + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void RequiresInInclusiveRange(int start, uint length) + { + if ((uint)start > length) + { + throw new ArgumentOutOfRangeException(); + } + } + } +} \ No newline at end of file diff --git a/src/Discord.Net.Core/_corefxlab/System.Binary/System/Binary/BufferWriter.cs b/src/Discord.Net.Core/_corefxlab/System.Binary/System/Binary/BufferWriter.cs new file mode 100644 index 000000000..1deee40fe --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Binary/System/Binary/BufferWriter.cs @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Runtime; +using System.Runtime.CompilerServices; + +namespace System.Binary +{ + /// + /// Writes endian-specific primitives into spans. + /// + /// + /// Use these helpers when you need to write specific endinaness. + /// + public static class BufferWriter + { + /// + /// Writes a structure of type T to a span of bytes. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteBigEndian<[Primitive]T>(this Span span, T value) where T : struct + => span.Write(BitConverter.IsLittleEndian ? UnsafeUtilities.Reverse(value) : value); + + /// + /// Writes a structure of type T to a span of bytes. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteLittleEndian<[Primitive]T>(this Span span, T value) where T : struct + => span.Write(BitConverter.IsLittleEndian ? value : UnsafeUtilities.Reverse(value)); + + + + /// + /// Writes a structure of type T into a slice of bytes. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Write<[Primitive]T>(this Span slice, T value) + where T : struct + { + if ((uint)Unsafe.SizeOf() > (uint)slice.Length) + { + throw new ArgumentOutOfRangeException(); + } + Unsafe.WriteUnaligned(ref slice.DangerousGetPinnableReference(), value); + } + + /// + /// Writes a structure of type T into a slice of bytes. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryWrite<[Primitive]T>(this Span slice, T value) + where T : struct + { + if (Unsafe.SizeOf() > (uint)slice.Length) + { + return false; + } + Unsafe.WriteUnaligned(ref slice.DangerousGetPinnableReference(), value); + return true; + } + } +} \ No newline at end of file diff --git a/src/Discord.Net.Core/_corefxlab/System.Binary/System/Binary/UnsafeUtilities.cs b/src/Discord.Net.Core/_corefxlab/System.Binary/System/Binary/UnsafeUtilities.cs new file mode 100644 index 000000000..f3a60c2b7 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Binary/System/Binary/UnsafeUtilities.cs @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; + +namespace System.Runtime +{ + /// + /// A collection of unsafe helper methods that we cannot implement in C#. + /// NOTE: these can be used for VeryBadThings(tm), so tread with care... + /// + internal static class UnsafeUtilities + { + /// + /// Reverses a primitive value - performs an endianness swap + /// + public static unsafe T Reverse<[Primitive]T>(T value) where T : struct + { + // note: relying on JIT goodness here! + if (typeof(T) == typeof(byte) || typeof(T) == typeof(sbyte)) { + return value; + } + else if (typeof(T) == typeof(ushort) || typeof(T) == typeof(short)) { + ushort val = 0; + Unsafe.Write(&val, value); + val = (ushort)((val >> 8) | (val << 8)); + return Unsafe.Read(&val); + } + else if (typeof(T) == typeof(uint) || typeof(T) == typeof(int) + || typeof(T) == typeof(float)) { + uint val = 0; + Unsafe.Write(&val, value); + val = (val << 24) + | ((val & 0xFF00) << 8) + | ((val & 0xFF0000) >> 8) + | (val >> 24); + return Unsafe.Read(&val); + } + else if (typeof(T) == typeof(ulong) || typeof(T) == typeof(long) + || typeof(T) == typeof(double)) { + ulong val = 0; + Unsafe.Write(&val, value); + val = (val << 56) + | ((val & 0xFF00) << 40) + | ((val & 0xFF0000) << 24) + | ((val & 0xFF000000) << 8) + | ((val & 0xFF00000000) >> 8) + | ((val & 0xFF0000000000) >> 24) + | ((val & 0xFF000000000000) >> 40) + | (val >> 56); + return Unsafe.Read(&val); + } + else { + // default implementation + int len = Unsafe.SizeOf(); + var val = stackalloc byte[len]; + Unsafe.Write(val, value); + int to = len >> 1, dest = len - 1; + for (int i = 0; i < to; i++) { + var tmp = val[i]; + val[i] = val[dest]; + val[dest--] = tmp; + } + return Unsafe.Read(val); + } + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/Buffer.cs b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/Buffer.cs new file mode 100644 index 000000000..6fb6eabc5 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/Buffer.cs @@ -0,0 +1,238 @@ +// 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.ComponentModel; +using System.Diagnostics; +using System.Runtime; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace System +{ + [DebuggerTypeProxy(typeof(BufferDebuggerView<>))] + public struct Buffer + { + // The highest order bit of _index is used to discern whether _arrayOrOwnedBuffer is an array or an owned buffer + // if (_index >> 31) == 1, object _arrayOrOwnedBuffer is an OwnedBuffer + // else, object _arrayOrOwnedBuffer is a T[] + readonly object _arrayOrOwnedBuffer; + readonly int _index; + readonly int _length; + + private const int bitMask = 0x7FFFFFFF; + + internal Buffer(OwnedBuffer owner, int index, int length) + { + _arrayOrOwnedBuffer = owner; + _index = index | (1 << 31); // Before using _index, check if _index < 0, then 'and' it with bitMask + _length = length; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Buffer(T[] array) + { + if (array == null) + BufferPrimitivesThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + if (default(T) == null && array.GetType() != typeof(T[])) + BufferPrimitivesThrowHelper.ThrowArrayTypeMismatchException(typeof(T)); + + _arrayOrOwnedBuffer = array; + _index = 0; + _length = array.Length; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Buffer(T[] array, int start) + { + if (array == null) + BufferPrimitivesThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + if (default(T) == null && array.GetType() != typeof(T[])) + BufferPrimitivesThrowHelper.ThrowArrayTypeMismatchException(typeof(T)); + + int arrayLength = array.Length; + if ((uint)start > (uint)arrayLength) + BufferPrimitivesThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start); + + _arrayOrOwnedBuffer = array; + _index = start; + _length = arrayLength - start; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Buffer(T[] array, int start, int length) + { + if (array == null) + BufferPrimitivesThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + if (default(T) == null && array.GetType() != typeof(T[])) + BufferPrimitivesThrowHelper.ThrowArrayTypeMismatchException(typeof(T)); + if ((uint)start > (uint)array.Length || (uint)length > (uint)(array.Length - start)) + BufferPrimitivesThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start); + + _arrayOrOwnedBuffer = array; + _index = start; + _length = length; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ReadOnlyBuffer(Buffer buffer) + { + // There is no need to 'and' _index by the bit mask here + // since the constructor will set the highest order bit again anyway + if (buffer._index < 0) + return new ReadOnlyBuffer(Unsafe.As>(buffer._arrayOrOwnedBuffer), buffer._index, buffer._length); + return new ReadOnlyBuffer(Unsafe.As(buffer._arrayOrOwnedBuffer), buffer._index, buffer._length); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Buffer(T[] array) + { + return new Buffer(array, 0, array.Length); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Buffer(ArraySegment arraySegment) + { + return new Buffer(arraySegment.Array, arraySegment.Offset, arraySegment.Count); + } + + public static Buffer Empty { get; } = OwnedBuffer.EmptyArray; + + public int Length => _length; + + public bool IsEmpty => Length == 0; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Buffer Slice(int start) + { + if ((uint)start > (uint)_length) + BufferPrimitivesThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start); + + // There is no need to and _index by the bit mask here + // since the constructor will set the highest order bit again anyway + if (_index < 0) + return new Buffer(Unsafe.As>(_arrayOrOwnedBuffer), _index + start, _length - start); + return new Buffer(Unsafe.As(_arrayOrOwnedBuffer), _index + start, _length - start); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Buffer Slice(int start, int length) + { + if ((uint)start > (uint)_length || (uint)length > (uint)(_length - start)) + BufferPrimitivesThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start); + + // There is no need to 'and' _index by the bit mask here + // since the constructor will set the highest order bit again anyway + if (_index < 0) + return new Buffer(Unsafe.As>(_arrayOrOwnedBuffer), _index + start, length); + return new Buffer(Unsafe.As(_arrayOrOwnedBuffer), _index + start, length); + } + + public Span Span + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if (_index < 0) + return Unsafe.As>(_arrayOrOwnedBuffer).AsSpan(_index & bitMask, _length); + return new Span(Unsafe.As(_arrayOrOwnedBuffer), _index, _length); + } + } + + public BufferHandle Retain(bool pin = false) + { + BufferHandle bufferHandle; + if (pin) + { + if (_index < 0) + { + bufferHandle = Unsafe.As>(_arrayOrOwnedBuffer).Pin(_index & bitMask); + } + else + { + var handle = GCHandle.Alloc(Unsafe.As(_arrayOrOwnedBuffer), GCHandleType.Pinned); + unsafe + { + var pointer = OwnedBuffer.Add((void*)handle.AddrOfPinnedObject(), _index); + bufferHandle = new BufferHandle(null, pointer, handle); + } + } + } + else + { + if (_index < 0) + { + Unsafe.As>(_arrayOrOwnedBuffer).Retain(); + bufferHandle = new BufferHandle(Unsafe.As>(_arrayOrOwnedBuffer)); + } + else + { + bufferHandle = new BufferHandle(null); + } + } + return bufferHandle; + } + + public bool TryGetArray(out ArraySegment arraySegment) + { + if (_index < 0) + { + if (Unsafe.As>(_arrayOrOwnedBuffer).TryGetArray(out var segment)) + { + arraySegment = new ArraySegment(segment.Array, segment.Offset + (_index & bitMask), _length); + return true; + } + } + else + { + arraySegment = new ArraySegment(Unsafe.As(_arrayOrOwnedBuffer), _index, _length); + return true; + } + + arraySegment = default; + return false; + } + + public T[] ToArray() => Span.ToArray(); + + public void CopyTo(Span span) => Span.CopyTo(span); + + public void CopyTo(Buffer buffer) => Span.CopyTo(buffer.Span); + + public bool TryCopyTo(Span span) => Span.TryCopyTo(span); + + public bool TryCopyTo(Buffer buffer) => Span.TryCopyTo(buffer.Span); + + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object obj) + { + if (obj is ReadOnlyBuffer readOnlyBuffer) + { + return readOnlyBuffer.Equals(this); + } + else if (obj is Buffer buffer) + { + return Equals(buffer); + } + else + { + return false; + } + } + + public bool Equals(Buffer other) + { + return + _arrayOrOwnedBuffer == other._arrayOrOwnedBuffer && + (_index & bitMask) == (other._index & bitMask) && + _length == other._length; + } + + [EditorBrowsable( EditorBrowsableState.Never)] + public override int GetHashCode() + { + return HashingHelper.CombineHashCodes(_arrayOrOwnedBuffer.GetHashCode(), (_index & bitMask).GetHashCode(), _length.GetHashCode()); + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/BufferExtensions.cs b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/BufferExtensions.cs new file mode 100644 index 000000000..fbb8a3e67 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/BufferExtensions.cs @@ -0,0 +1,19 @@ +// 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. + +namespace System.Buffers +{ + public static class BufferExtensions + { + public static bool SequenceEqual(this Buffer first, Buffer second) where T : struct, IEquatable + { + return first.Span.SequenceEqual(second.Span); + } + + public static bool SequenceEqual(this ReadOnlyBuffer first, ReadOnlyBuffer second) where T : struct, IEquatable + { + return first.Span.SequenceEqual(second.Span); + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/BufferHandle.cs b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/BufferHandle.cs new file mode 100644 index 000000000..9a15a6bb9 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/BufferHandle.cs @@ -0,0 +1,46 @@ +// 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.Runtime; +using System.Runtime.InteropServices; + +namespace System.Buffers +{ + public unsafe struct BufferHandle : IDisposable + { + IRetainable _owner; + void* _pointer; + GCHandle _handle; + + public BufferHandle(IRetainable owner, void* pinnedPointer, GCHandle handle = default) + { + _pointer = pinnedPointer; + _handle = handle; + _owner = owner; + } + + public BufferHandle(IRetainable owner) : this(owner, null) { } + + public void* PinnedPointer { + get { + if (_pointer == null) BufferPrimitivesThrowHelper.ThrowInvalidOperationException(); + return _pointer; + } + } + + public void Dispose() + { + if (_handle.IsAllocated) { + _handle.Free(); + } + + if (_owner != null) { + _owner.Release(); + _owner = null; + } + + _pointer = null; + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/BufferPool.cs b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/BufferPool.cs new file mode 100644 index 000000000..3520e7145 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/BufferPool.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace System.Buffers +{ + public abstract class BufferPool : IDisposable + { + public static BufferPool Default => Internal.ManagedBufferPool.Shared; + + public abstract OwnedBuffer Rent(int minimumBufferSize); + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + ~BufferPool() + { + Dispose(false); + } + + protected abstract void Dispose(bool disposing); + } +} \ No newline at end of file diff --git a/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/IOutput.cs b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/IOutput.cs new file mode 100644 index 000000000..948b8b7e2 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/IOutput.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace System.Buffers +{ + public interface IOutput + { + Span Buffer { get; } + void Advance(int bytes); + + /// desiredBufferLength == 0 means "i don't care" + void Enlarge(int desiredBufferLength = 0); + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/IRetainable.cs b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/IRetainable.cs new file mode 100644 index 000000000..a39481239 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/IRetainable.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace System.Buffers +{ + public interface IRetainable + { + void Retain(); + void Release(); + bool IsRetained { get; } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/OwnedBuffer.cs b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/OwnedBuffer.cs new file mode 100644 index 000000000..4fbf9a58b --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/OwnedBuffer.cs @@ -0,0 +1,75 @@ +// 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.Runtime; +using System.Runtime.CompilerServices; + +namespace System.Buffers +{ + public abstract class OwnedBuffer : IDisposable, IRetainable + { + protected OwnedBuffer() { } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator OwnedBuffer(T[] array) + { + return new Internal.OwnedArray(array); + } + + public abstract int Length { get; } + + public abstract Span AsSpan(int index, int length); + + public virtual Span AsSpan() + { + if (IsDisposed) BufferPrimitivesThrowHelper.ThrowObjectDisposedException(nameof(OwnedBuffer)); + return AsSpan(0, Length); + } + + public Buffer Buffer + { + get { + if (IsDisposed) BufferPrimitivesThrowHelper.ThrowObjectDisposedException(nameof(OwnedBuffer)); + return new Buffer(this, 0, Length); + } + } + + public ReadOnlyBuffer ReadOnlyBuffer + { + get { + if (IsDisposed) BufferPrimitivesThrowHelper.ThrowObjectDisposedException(nameof(OwnedBuffer)); + return new ReadOnlyBuffer(this, 0, Length); + } + } + + public abstract BufferHandle Pin(int index = 0); + + protected internal abstract bool TryGetArray(out ArraySegment arraySegment); + + #region Lifetime Management + public abstract bool IsDisposed { get; } + + public void Dispose() + { + if (IsRetained) throw new InvalidOperationException("outstanding references detected."); + Dispose(true); + } + + protected abstract void Dispose(bool disposing); + + public abstract bool IsRetained { get; } + + public abstract void Retain(); + + public abstract void Release(); + #endregion + + protected internal static unsafe void* Add(void* pointer, int offset) + { + return (byte*)pointer + ((ulong)Unsafe.SizeOf() * (ulong)offset); + } + + internal static readonly T[] EmptyArray = new T[0]; + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/ReadOnlyBuffer.cs b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/ReadOnlyBuffer.cs new file mode 100644 index 000000000..2158496c5 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/ReadOnlyBuffer.cs @@ -0,0 +1,223 @@ +// 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.ComponentModel; +using System.Diagnostics; +using System.Runtime; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace System +{ + [DebuggerTypeProxy(typeof(ReadOnlyBufferDebuggerView<>))] + public struct ReadOnlyBuffer + { + // The highest order bit of _index is used to discern whether _arrayOrOwnedBuffer is an array or an owned buffer + // if (_index >> 31) == 1, object _arrayOrOwnedBuffer is an OwnedBuffer + // else, object _arrayOrOwnedBuffer is a T[] + readonly object _arrayOrOwnedBuffer; + readonly int _index; + readonly int _length; + + private const int bitMask = 0x7FFFFFFF; + + internal ReadOnlyBuffer(OwnedBuffer owner, int index, int length) + { + _arrayOrOwnedBuffer = owner; + _index = index | (1 << 31); // Before using _index, check if _index < 0, then 'and' it with bitMask + _length = length; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlyBuffer(T[] array) + { + if (array == null) + BufferPrimitivesThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + + _arrayOrOwnedBuffer = array; + _index = 0; + _length = array.Length; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlyBuffer(T[] array, int start) + { + if (array == null) + BufferPrimitivesThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + + int arrayLength = array.Length; + if ((uint)start > (uint)arrayLength) + BufferPrimitivesThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start); + + _arrayOrOwnedBuffer = array; + _index = start; + _length = arrayLength - start; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlyBuffer(T[] array, int start, int length) + { + if (array == null) + BufferPrimitivesThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + if ((uint)start > (uint)array.Length || (uint)length > (uint)(array.Length - start)) + BufferPrimitivesThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start); + + _arrayOrOwnedBuffer = array; + _index = start; + _length = length; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ReadOnlyBuffer(T[] array) + { + return new ReadOnlyBuffer(array, 0, array.Length); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ReadOnlyBuffer(ArraySegment arraySegment) + { + return new ReadOnlyBuffer(arraySegment.Array, arraySegment.Offset, arraySegment.Count); + } + + public static ReadOnlyBuffer Empty { get; } = OwnedBuffer.EmptyArray; + + public int Length => _length; + + public bool IsEmpty => Length == 0; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlyBuffer Slice(int start) + { + if ((uint)start > (uint)_length) + BufferPrimitivesThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start); + + // There is no need to 'and' _index by the bit mask here + // since the constructor will set the highest order bit again anyway + if (_index < 0) + return new ReadOnlyBuffer(Unsafe.As>(_arrayOrOwnedBuffer), _index + start, _length - start); + return new ReadOnlyBuffer(Unsafe.As(_arrayOrOwnedBuffer), _index + start, _length - start); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlyBuffer Slice(int start, int length) + { + if ((uint)start > (uint)_length || (uint)length > (uint)(_length - start)) + BufferPrimitivesThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start); + + // There is no need to 'and' _index by the bit mask here + // since the constructor will set the highest order bit again anyway + if (_index < 0) + return new ReadOnlyBuffer(Unsafe.As>(_arrayOrOwnedBuffer), _index + start, length); + return new ReadOnlyBuffer(Unsafe.As(_arrayOrOwnedBuffer), _index + start, length); + } + + public ReadOnlySpan Span + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if (_index < 0) + return Unsafe.As>(_arrayOrOwnedBuffer).AsSpan(_index & bitMask, _length); + return new ReadOnlySpan(Unsafe.As(_arrayOrOwnedBuffer), _index, _length); + } + } + + public BufferHandle Retain(bool pin = false) + { + BufferHandle bufferHandle; + if (pin) + { + if (_index < 0) + { + bufferHandle = Unsafe.As>(_arrayOrOwnedBuffer).Pin(_index & bitMask); + } + else + { + var handle = GCHandle.Alloc(Unsafe.As(_arrayOrOwnedBuffer), GCHandleType.Pinned); + unsafe + { + var pointer = OwnedBuffer.Add((void*)handle.AddrOfPinnedObject(), _index); + bufferHandle = new BufferHandle(null, pointer, handle); + } + } + } + else + { + if (_index < 0) + { + Unsafe.As>(_arrayOrOwnedBuffer).Retain(); + bufferHandle = new BufferHandle(Unsafe.As>(_arrayOrOwnedBuffer)); + } + else + { + bufferHandle = new BufferHandle(null); + } + } + return bufferHandle; + } + + public T[] ToArray() => Span.ToArray(); + + [EditorBrowsable(EditorBrowsableState.Never)] + public bool DangerousTryGetArray(out ArraySegment arraySegment) + { + if (_index < 0) + { + if (Unsafe.As>(_arrayOrOwnedBuffer).TryGetArray(out var segment)) + { + arraySegment = new ArraySegment(segment.Array, segment.Offset + (_index & bitMask), _length); + return true; + } + } + else + { + arraySegment = new ArraySegment(Unsafe.As(_arrayOrOwnedBuffer), _index, _length); + return true; + } + + arraySegment = default; + return false; + } + + public void CopyTo(Span span) => Span.CopyTo(span); + + public void CopyTo(Buffer buffer) => Span.CopyTo(buffer.Span); + + public bool TryCopyTo(Span span) => Span.TryCopyTo(span); + + public bool TryCopyTo(Buffer buffer) => Span.TryCopyTo(buffer.Span); + + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object obj) + { + if (obj is ReadOnlyBuffer readOnlyBuffer) + { + return Equals(readOnlyBuffer); + } + else if (obj is Buffer buffer) + { + return Equals(buffer); + } + else + { + return false; + } + } + + public bool Equals(ReadOnlyBuffer other) + { + return + _arrayOrOwnedBuffer == other._arrayOrOwnedBuffer && + (_index & bitMask) == (other._index & bitMask) && + _length == other._length; + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() + { + return HashingHelper.CombineHashCodes(_arrayOrOwnedBuffer.GetHashCode(), (_index & bitMask).GetHashCode(), _length.GetHashCode()); + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/Transformation.cs b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/Transformation.cs new file mode 100644 index 000000000..d27d1acc9 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/Transformation.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace System.Buffers +{ + public abstract class Transformation + { + public abstract TransformationStatus Transform(ReadOnlySpan source, Span destination, out int bytesConsumed, out int bytesWritten); + } + + public enum TransformationStatus + { + Done, + DestinationTooSmall, + NeedMoreSourceData, + InvalidData // TODO: how do we communicate details of the error + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Internal/ManagedBufferPool.cs b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Internal/ManagedBufferPool.cs new file mode 100644 index 000000000..f2ef069e5 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Internal/ManagedBufferPool.cs @@ -0,0 +1,101 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Runtime; +using System.Runtime.InteropServices; +using System.Threading; + +namespace System.Buffers.Internal +{ + internal sealed class ManagedBufferPool : BufferPool + { + readonly static ManagedBufferPool s_shared = new ManagedBufferPool(); + + public static ManagedBufferPool Shared + { + get + { + return s_shared; + } + } + + public override OwnedBuffer Rent(int minimumBufferSize) + { + var buffer = new ArrayPoolBuffer(minimumBufferSize); + return buffer; + } + + protected override void Dispose(bool disposing) + { + } + + private sealed class ArrayPoolBuffer : OwnedBuffer + { + byte[] _array; + bool _disposed; + int _referenceCount; + + public ArrayPoolBuffer(int size) + { + _array = ArrayPool.Shared.Rent(size); + } + + public override int Length => _array.Length; + + public override bool IsDisposed => _disposed; + + public override bool IsRetained => _referenceCount > 0; + + public override Span AsSpan(int index, int length) + { + if (IsDisposed) BufferPrimitivesThrowHelper.ThrowObjectDisposedException(nameof(ArrayPoolBuffer)); + return new Span(_array, index, length); + } + + protected override void Dispose(bool disposing) + { + var array = Interlocked.Exchange(ref _array, null); + if (array != null) { + _disposed = true; + ArrayPool.Shared.Return(array); + } + } + + protected internal override bool TryGetArray(out ArraySegment arraySegment) + { + if (IsDisposed) BufferPrimitivesThrowHelper.ThrowObjectDisposedException(nameof(ManagedBufferPool)); + arraySegment = new ArraySegment(_array); + return true; + } + + public override BufferHandle Pin(int index = 0) + { + unsafe + { + Retain(); // this checks IsDisposed + var handle = GCHandle.Alloc(_array, GCHandleType.Pinned); + var pointer = Add((void*)handle.AddrOfPinnedObject(), index); + return new BufferHandle(this, pointer, handle); + } + } + + public override void Retain() + { + if (IsDisposed) BufferPrimitivesThrowHelper.ThrowObjectDisposedException(nameof(ArrayPoolBuffer)); + Interlocked.Increment(ref _referenceCount); + } + + public override void Release() + { + var newRefCount = Interlocked.Decrement(ref _referenceCount); + if (newRefCount == 0) { + Dispose(); + return; + } + if(newRefCount < 0) { + throw new InvalidOperationException(); + } + } + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Internal/OwnedArray.cs b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Internal/OwnedArray.cs new file mode 100644 index 000000000..4e782f530 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Internal/OwnedArray.cs @@ -0,0 +1,88 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Runtime; +using System.Runtime.InteropServices; +using System.Threading; + +namespace System.Buffers.Internal +{ + internal class OwnedArray : OwnedBuffer + { + T[] _array; + int _referenceCount; + + public OwnedArray(int length) + { + _array = new T[length]; + } + + public OwnedArray(T[] array) + { + if (array == null) BufferPrimitivesThrowHelper.ThrowArgumentNullException(nameof(array)); + _array = array; + } + + public static implicit operator OwnedArray(T[] array) => new OwnedArray(array); + + public override int Length => _array.Length; + + public override Span AsSpan(int index, int length) + { + if (IsDisposed) BufferPrimitivesThrowHelper.ThrowObjectDisposedException(nameof(OwnedArray)); + return new Span(_array, index, length); + } + + public override Span AsSpan() + { + if (IsDisposed) BufferPrimitivesThrowHelper.ThrowObjectDisposedException(nameof(OwnedArray)); + return new Span(_array, 0, _array.Length); + } + + public override BufferHandle Pin(int index = 0) + { + unsafe + { + Retain(); + var handle = GCHandle.Alloc(_array, GCHandleType.Pinned); + var pointer = Add((void*)handle.AddrOfPinnedObject(), index); + return new BufferHandle(this, pointer, handle); + } + } + + protected internal override bool TryGetArray(out ArraySegment arraySegment) + { + if (IsDisposed) BufferPrimitivesThrowHelper.ThrowObjectDisposedException(nameof(OwnedArray)); + arraySegment = new ArraySegment(_array); + return true; + } + + protected override void Dispose(bool disposing) + { + _array = null; + } + + public override void Retain() + { + if (IsDisposed) BufferPrimitivesThrowHelper.ThrowObjectDisposedException(nameof(OwnedArray)); + Interlocked.Increment(ref _referenceCount); + } + + public override void Release() + { + if (!IsRetained) BufferPrimitivesThrowHelper.ThrowInvalidOperationException(); + if (Interlocked.Decrement(ref _referenceCount) == 0) + { + OnNoReferences(); + } + } + + protected virtual void OnNoReferences() + { + } + + public override bool IsRetained => _referenceCount > 0; + + public override bool IsDisposed => _array == null; + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Runtime/BufferDebuggerView.cs b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Runtime/BufferDebuggerView.cs new file mode 100644 index 000000000..323f36fa2 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Runtime/BufferDebuggerView.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Diagnostics; + +namespace System.Runtime +{ + internal class BufferDebuggerView + { + private ReadOnlyBuffer _buffer; + + public BufferDebuggerView(Buffer buffer) + { + _buffer = buffer; + } + + [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + public T[] Items + { + get { + return _buffer.ToArray(); + } + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Runtime/BufferPrimitivesThrowHelper.cs b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Runtime/BufferPrimitivesThrowHelper.cs new file mode 100644 index 000000000..881dc2f2a --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Runtime/BufferPrimitivesThrowHelper.cs @@ -0,0 +1,131 @@ +// 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.Diagnostics; +using System.Runtime.CompilerServices; + +namespace System.Runtime +{ + internal static class BufferPrimitivesThrowHelper + { + public static void ThrowArgumentNullException(string argument) + { + throw new ArgumentNullException(argument); + } + + public static void ThrowArgumentNullException(ExceptionArgument argument) + { + throw GetArgumentNullException(argument); + } + + public static void ThrowArgumentException() + { + throw GetArgumentException(); + } + + public static void ThrowArgumentException(ExceptionArgument argument) + { + throw GetArgumentException(argument); + } + + public static void ThrowArgumentOutOfRangeException() + { + throw GetArgumentOutOfRangeException(); + } + + public static void ThrowArgumentOutOfRangeException(ExceptionArgument argument) + { + throw GetArgumentOutOfRangeException(argument); + } + + public static void ThrowInvalidOperationException() + { + throw GetInvalidOperationException(); + } + + public static void ThrowInvalidOperationException_ForBoxingSpans() + { + throw GetInvalidOperationException_ForBoxingSpans(); + } + + public static void ThrowObjectDisposedException(string objectName) + { + throw GetObjectDisposedException(objectName); + } + + public static void ThrowArrayTypeMismatchException(Type type) + { + throw CreateArrayTypeMismatchException(type); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static ArgumentNullException GetArgumentNullException(ExceptionArgument argument) + { + return new ArgumentNullException(GetArgumentName(argument)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static ArgumentException GetArgumentException() + { + return new ArgumentException(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static ArgumentException GetArgumentException(ExceptionArgument argument) + { + return new ArgumentException(GetArgumentName(argument)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static ArgumentOutOfRangeException GetArgumentOutOfRangeException() + { + return new ArgumentOutOfRangeException(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static ArgumentOutOfRangeException GetArgumentOutOfRangeException(ExceptionArgument argument) + { + return new ArgumentOutOfRangeException(GetArgumentName(argument)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static InvalidOperationException GetInvalidOperationException() + { + return new InvalidOperationException(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static InvalidOperationException GetInvalidOperationException_ForBoxingSpans() + { + return new InvalidOperationException("Spans must not be boxed"); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static ObjectDisposedException GetObjectDisposedException(string objectName) + { + return new ObjectDisposedException(objectName); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static ArrayTypeMismatchException CreateArrayTypeMismatchException(Type type) + { + return new ArrayTypeMismatchException(type.ToString()); + } + + private static string GetArgumentName(ExceptionArgument argument) + { + Debug.Assert(Enum.IsDefined(typeof(ExceptionArgument), argument), + "The enum value is not defined, please check the ExceptionArgument Enum."); + + return argument.ToString(); + } + } + + internal enum ExceptionArgument + { + pointer, + array, + start + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Runtime/Contract.cs b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Runtime/Contract.cs new file mode 100644 index 000000000..b4982f718 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Runtime/Contract.cs @@ -0,0 +1,122 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; + +namespace System.Runtime +{ + internal static class Contract + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Requires(bool condition) + { + if (!condition) + { + BufferPrimitivesThrowHelper.ThrowArgumentException(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void RequiresNotNull(ExceptionArgument argument, T obj) where T : class + { + if (obj == null) + { + BufferPrimitivesThrowHelper.ThrowArgumentNullException(argument); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe void RequiresNotNull(ExceptionArgument argument, void* ptr) + { + if (ptr == null) + { + BufferPrimitivesThrowHelper.ThrowArgumentNullException(argument); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe void RequiresSameReference(void* ptr0, void* ptr1) + { + if (ptr0 != ptr1) + { + BufferPrimitivesThrowHelper.ThrowArgumentException(ExceptionArgument.pointer); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void RequiresNonNegative(int n) + { + if (n < 0) + { + BufferPrimitivesThrowHelper.ThrowArgumentOutOfRangeException(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void RequiresInRange(int start, uint length) + { + if ((uint)start >= length) + { + BufferPrimitivesThrowHelper.ThrowArgumentOutOfRangeException(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void RequiresInRange(uint start, uint length) + { + if (start >= length) + { + BufferPrimitivesThrowHelper.ThrowArgumentOutOfRangeException(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void RequiresInInclusiveRange(int start, uint length) + { + if ((uint)start > length) + { + BufferPrimitivesThrowHelper.ThrowArgumentOutOfRangeException(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static unsafe void RequiresOneNotNull(T[] array, void* pointer) + { + if (array == null && pointer == null) + { + BufferPrimitivesThrowHelper.ThrowArgumentException(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void RequiresInInclusiveRange( uint start, uint length) + { + if (start > length) + { + BufferPrimitivesThrowHelper.ThrowArgumentOutOfRangeException(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void RequiresInInclusiveRange(int start, int length, uint existingLength) + { + if ((uint)start > existingLength + || length < 0 + || (uint)(start + length) > existingLength) + { + BufferPrimitivesThrowHelper.ThrowArgumentOutOfRangeException(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void RequiresInInclusiveRange(uint start, uint length, uint existingLength) + { + if (start > existingLength + || (start + length) > existingLength) + { + BufferPrimitivesThrowHelper.ThrowArgumentOutOfRangeException(); + } + } + } +} + diff --git a/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Runtime/HashingHelper.cs b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Runtime/HashingHelper.cs new file mode 100644 index 000000000..6f8eb5f93 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Runtime/HashingHelper.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace System.Runtime +{ + static class HashingHelper + { + public static int CombineHashCodes(int left, int right) + { + return ((left << 5) + left) ^ right; + } + + public static int CombineHashCodes(int h1, int h2, int h3) + { + return CombineHashCodes(CombineHashCodes(h1, h2), h3); + } + + public static int CombineHashCodes(int h1, int h2, int h3, int h4) + { + return CombineHashCodes(CombineHashCodes(h1, h2), CombineHashCodes(h3, h4)); + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Runtime/PrimitiveAttribute.cs b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Runtime/PrimitiveAttribute.cs new file mode 100644 index 000000000..8ef158b77 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Runtime/PrimitiveAttribute.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace System.Runtime +{ + [AttributeUsage(AttributeTargets.GenericParameter)] + public sealed class PrimitiveAttribute : Attribute + { + } +} + diff --git a/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Runtime/ReadOnlyBufferDebuggerView.cs b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Runtime/ReadOnlyBufferDebuggerView.cs new file mode 100644 index 000000000..4d3ff9912 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Runtime/ReadOnlyBufferDebuggerView.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Diagnostics; + +namespace System.Runtime +{ + internal class ReadOnlyBufferDebuggerView + { + private ReadOnlyBuffer _buffer; + + public ReadOnlyBufferDebuggerView(ReadOnlyBuffer buffer) + { + _buffer = buffer; + } + + [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + public T[] Items + { + get { + return _buffer.ToArray(); + } + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Collections.Sequences/System/Collections/Sequences/ArrayList.cs b/src/Discord.Net.Core/_corefxlab/System.Collections.Sequences/System/Collections/Sequences/ArrayList.cs new file mode 100644 index 000000000..209b89bda --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Collections.Sequences/System/Collections/Sequences/ArrayList.cs @@ -0,0 +1,41 @@ +// 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. + + +namespace System.Collections.Sequences +{ + // This type is illustrating how to implement the new enumerable on index based datastructure + public sealed class ArrayList : ISequence + { + ResizableArray _items; + + public ArrayList() + { + _items = new ResizableArray(0); + } + public ArrayList(int capacity) + { + _items = new ResizableArray(capacity); + } + + public int Length => _items.Count; + + public T this[int index] => _items[index]; + + public void Add(T item) + { + _items.Add(item); + } + + public SequenceEnumerator GetEnumerator() + { + return new SequenceEnumerator(this); + } + + public bool TryGet(ref Position position, out T item, bool advance = true) + { + return _items.TryGet(ref position, out item, advance); + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Collections.Sequences/System/Collections/Sequences/ISequence.cs b/src/Discord.Net.Core/_corefxlab/System.Collections.Sequences/System/Collections/Sequences/ISequence.cs new file mode 100644 index 000000000..f907fe364 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Collections.Sequences/System/Collections/Sequences/ISequence.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace System.Collections.Sequences +{ + // new interface + public interface ISequence + { + /// + /// + /// + /// + /// + /// + /// + bool TryGet(ref Position position, out T item, bool advance = true); + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Collections.Sequences/System/Collections/Sequences/Position.cs b/src/Discord.Net.Core/_corefxlab/System.Collections.Sequences/System/Collections/Sequences/Position.cs new file mode 100644 index 000000000..b4eda126b --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Collections.Sequences/System/Collections/Sequences/Position.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; + +namespace System.Collections.Sequences +{ + public struct Position : IEquatable + { + public object ObjectPosition; + public int IntegerPosition; + public int Tag; + + public static readonly Position First = new Position(); + public static readonly Position AfterLast = new Position() { IntegerPosition = int.MaxValue - 1 }; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator==(Position left, Position right) + { + return left.Equals(right); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator!=(Position left, Position right) + { + return left.Equals(right); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(Position other) + { + return IntegerPosition == other.IntegerPosition && ObjectPosition == other.ObjectPosition; + } + + public override int GetHashCode() + { + return ObjectPosition == null ? IntegerPosition.GetHashCode() : ObjectPosition.GetHashCode(); + } + + public override bool Equals(object obj) + { + if(obj is Position) + return base.Equals((Position)obj); + return false; + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Collections.Sequences/System/Collections/Sequences/ResizableArray.cs b/src/Discord.Net.Core/_corefxlab/System.Collections.Sequences/System/Collections/Sequences/ResizableArray.cs new file mode 100644 index 000000000..d09ab2cdf --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Collections.Sequences/System/Collections/Sequences/ResizableArray.cs @@ -0,0 +1,124 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + + +namespace System.Collections.Sequences +{ + // a List like type designed to be embeded in other types + public struct ResizableArray + { + private T[] _array; + private int _count; + + public ResizableArray(int capacity) + { + _array = new T[capacity]; + _count = 0; + } + + public ResizableArray(T[] array, int count = 0) + { + _array = array; + _count = count; + } + + public T[] Items + { + get { return _array; } + set { _array = value; } + } + public int Count + { + get { return _count; } + set { _count = value; } + } + + public int Capacity => _array.Length; + + public T this[int index] + { + get { + if (index > _count - 1) throw new IndexOutOfRangeException(); + return _array[index]; + } + set { + if (index > _count - 1) throw new IndexOutOfRangeException(); + _array[index] = value; + } + } + + public void Add(T item) + { + if (_array.Length == _count) { + Resize(); + } + _array[_count++] = item; + } + + public void AddAll(T[] items) + { + if (items.Length > _array.Length - _count) { + Resize(items.Length + _count); + } + items.CopyTo(_array, _count); + _count += items.Length; + } + + public void AddAll(ReadOnlySpan items) + { + if (items.Length > _array.Length - _count) { + Resize(items.Length + _count); + } + items.CopyTo(new Span(_array, _count)); + _count += items.Length; + } + + public void Clear() + { + _count = 0; + } + + public T[] Resize(int newSize = -1) + { + var oldArray = _array; + if (newSize == -1) { + if(_array == null || _array.Length == 0) { + newSize = 4; + } + else { + newSize = _array.Length << 1; + } + } + + var newArray = new T[newSize]; + new Span(_array, 0, _count).CopyTo(newArray); + _array = newArray; + return oldArray; + } + + public T[] Resize(T[] newArray) + { + if (newArray.Length < _count) throw new ArgumentOutOfRangeException(nameof(newArray)); + var oldArray = _array; + Array.Copy(_array, 0, newArray, 0, _count); + _array = newArray; + return oldArray; + } + + public bool TryGet(ref Position position, out T item, bool advance = true) + { + if (position.IntegerPosition < _count) { + item = _array[position.IntegerPosition]; + if (advance) { position.IntegerPosition++; } + return true; + } + + item = default; + position = Position.AfterLast; + return false; + } + + public ArraySegment Full => new ArraySegment(_array, 0, _count); + public ArraySegment Free => new ArraySegment(_array, _count, _array.Length - _count); + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Collections.Sequences/System/Collections/Sequences/SequenceEnumerator.cs b/src/Discord.Net.Core/_corefxlab/System.Collections.Sequences/System/Collections/Sequences/SequenceEnumerator.cs new file mode 100644 index 000000000..08b26aa8b --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Collections.Sequences/System/Collections/Sequences/SequenceEnumerator.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; + +namespace System.Collections.Sequences +{ + public struct SequenceEnumerator + { + Position _position; + ISequence _sequence; + T _current; + bool first; // this is needed so that MoveNext does not advance the first time it's called + + public SequenceEnumerator(ISequence sequence) { + _sequence = sequence; + _position = Position.First; + _current = default; + first = true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() { + var result = _sequence.TryGet(ref _position, out _current, advance: !first); + first = false; + return result; + } + + public T Current => _current; + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Formatting/System/Text/Formatting/CompositeFormat.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Formatting/System/Text/Formatting/CompositeFormat.cs new file mode 100644 index 000000000..5a7f14e1c --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Formatting/System/Text/Formatting/CompositeFormat.cs @@ -0,0 +1,402 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Buffers; +using System.Diagnostics; + +namespace System.Text.Formatting +{ + + // This whole API is very speculative, i.e. I am not sure I am happy with the design + // This API is trying to do composite formatting without boxing (or any other allocations). + // And because not all types in the platfrom implement IBufferFormattable (in particular built-in primitives don't), + // it needs to play some tricks with generic type parameters. But as you can see at the end of AppendUntyped, I am not sure how to tick the type system + // not never box. + public static class CompositeFormattingExtensions + { + public static void Format(this TFormatter formatter, string compositeFormat, T0 arg0) where TFormatter : ITextOutput + { + var reader = new CompositeFormatReader(compositeFormat); + while (true) + { + var segment = reader.Next(); + if (segment == null) return; + + if (segment.Value.Count == 0) // insertion point + { + if (segment.Value.Index == 0) formatter.AppendUntyped(arg0, segment.Value.Format); + else throw new Exception("invalid insertion point"); + } + else // literal + { + formatter.Append(compositeFormat, segment.Value.Index, segment.Value.Count); + } + } + } + + public static void Format(this TFormatter formatter, string compositeFormat, T0 arg0, T1 arg1) where TFormatter : ITextOutput + { + var reader = new CompositeFormatReader(compositeFormat); + while (true) + { + var segment = reader.Next(); + if (segment == null) return; + + if (segment.Value.Count == 0) // insertion point + { + if (segment.Value.Index == 0) formatter.AppendUntyped(arg0, segment.Value.Format); + else if (segment.Value.Index == 1) formatter.AppendUntyped(arg1, segment.Value.Format); + else throw new Exception("invalid insertion point"); + } + else // literal + { + formatter.Append(compositeFormat, segment.Value.Index, segment.Value.Count); + } + } + } + + public static void Format(this TFormatter formatter, string compositeFormat, T0 arg0, T1 arg1, T2 arg2) where TFormatter : ITextOutput + { + var reader = new CompositeFormatReader(compositeFormat); + while (true) + { + var segment = reader.Next(); + if (segment == null) return; + + if (segment.Value.Count == 0) // insertion point + { + if (segment.Value.Index == 0) formatter.AppendUntyped(arg0, segment.Value.Format); + else if (segment.Value.Index == 1) formatter.AppendUntyped(arg1, segment.Value.Format); + else if (segment.Value.Index == 2) formatter.AppendUntyped(arg2, segment.Value.Format); + else throw new Exception("invalid insertion point"); + } + else // literal + { + formatter.Append(compositeFormat, segment.Value.Index, segment.Value.Count); + } + } + } + + public static void Format(this TFormatter formatter, string compositeFormat, T0 arg0, T1 arg1, T2 arg2, T3 arg3) where TFormatter : ITextOutput + { + var reader = new CompositeFormatReader(compositeFormat); + while (true) + { + var segment = reader.Next(); + if (segment == null) return; + + if (segment.Value.Count == 0) // insertion point + { + if (segment.Value.Index == 0) formatter.AppendUntyped(arg0, segment.Value.Format); + else if (segment.Value.Index == 1) formatter.AppendUntyped(arg1, segment.Value.Format); + else if (segment.Value.Index == 2) formatter.AppendUntyped(arg2, segment.Value.Format); + else if (segment.Value.Index == 3) formatter.AppendUntyped(arg3, segment.Value.Format); + else throw new Exception("invalid insertion point"); + } + else // literal + { + formatter.Append(compositeFormat, segment.Value.Index, segment.Value.Count); + } + } + } + + public static void Format(this TFormatter formatter, string compositeFormat, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4) where TFormatter : ITextOutput + { + var reader = new CompositeFormatReader(compositeFormat); + while (true) + { + var segment = reader.Next(); + if (segment == null) return; + + if (segment.Value.Count == 0) // insertion point + { + if (segment.Value.Index == 0) formatter.AppendUntyped(arg0, segment.Value.Format); + else if (segment.Value.Index == 1) formatter.AppendUntyped(arg1, segment.Value.Format); + else if (segment.Value.Index == 2) formatter.AppendUntyped(arg2, segment.Value.Format); + else if (segment.Value.Index == 3) formatter.AppendUntyped(arg3, segment.Value.Format); + else if (segment.Value.Index == 4) formatter.AppendUntyped(arg4, segment.Value.Format); + else throw new Exception("invalid insertion point"); + } + else // literal + { + formatter.Append(compositeFormat, segment.Value.Index, segment.Value.Count); + } + } + } + + // TODO: this should be removed and an ability to append substrings should be added + static void Append(this TFormatter formatter, string whole, int index, int count) where TFormatter : ITextOutput + { + var buffer = formatter.Buffer; + var maxBytes = count << 4; // this is the worst case, i.e. 4 bytes per char + while(buffer.Length < maxBytes) + { + formatter.Enlarge(maxBytes); + buffer = formatter.Buffer; + } + + // this should be optimized using fixed pointer to substring, but I will wait with this till we design proper substring + + var characters = whole.Slice(index, count); + if (!formatter.TryAppend(characters, formatter.SymbolTable)) + { + Debug.Assert(false, "this should never happen"); // because I pre-resized the buffer to 4 bytes per char at the top of this method. + } + } + + static void AppendUntyped(this TFormatter formatter, T value, ParsedFormat format) where TFormatter : ITextOutput + { + #region Built in types + var i32 = value as int?; + if (i32 != null) + { + formatter.Append(i32.Value, format); + return; + } + var i64 = value as long?; + if (i64 != null) + { + formatter.Append(i64.Value, format); + return; + } + var i16 = value as short?; + if (i16 != null) + { + formatter.Append(i16.Value, format); + return; + } + var b = value as byte?; + if (b != null) + { + formatter.Append(b.Value, format); + return; + } + var c = value as char?; + if (c != null) + { + formatter.Append(c.Value); + return; + } + var u32 = value as uint?; + if (u32 != null) + { + formatter.Append(u32.Value, format); + return; + } + var u64 = value as ulong?; + if (u64 != null) + { + formatter.Append(u64.Value, format); + return; + } + var u16 = value as ushort?; + if (u16 != null) + { + formatter.Append(u16.Value, format); + return; + } + var sb = value as sbyte?; + if (sb != null) + { + formatter.Append(sb.Value, format); + return; + } + var str = value as string; + if (str != null) + { + formatter.Append(str); + return; + } + var dt = value as DateTime?; + if (dt != null) + { + formatter.Append(dt.Value, format); + return; + } + var dto = value as DateTimeOffset?; + if (dto != null) { + formatter.Append(dto.Value, format); + return; + } + var ts = value as TimeSpan?; + if (ts != null) + { + formatter.Append(ts.Value, format); + return; + } + var guid = value as Guid?; + if (guid != null) { + formatter.Append(guid.Value, format); + return; + } + #endregion + + if (value is IBufferFormattable) + { + formatter.Append((IBufferFormattable)value, format); // this is boxing. not sure how to avoid it. + return; + } + + throw new NotSupportedException("value is not formattable."); + } + + // this is just a state machine walking the composite format and instructing CompositeFormattingExtensions.Format overloads on what to do. + // this whole type is not just a hacky prototype. + // I will clean it up later if I decide that I like this whole composite format model. + struct CompositeFormatReader + { + string _compositeFormatString; + int _currentIndex; + int _spanStart; + State _state; + + public CompositeFormatReader(string format) + { + _compositeFormatString = format; + _currentIndex = 0; + _spanStart = 0; + _state = State.New; + } + + public CompositeSegment? Next() + { + while (_currentIndex < _compositeFormatString.Length) + { + char c = _compositeFormatString[_currentIndex]; + if (c == '{') + { + if (_state == State.Literal) + { + _state = State.New; + return CompositeSegment.Literal(_spanStart, _currentIndex); + } + if ((_currentIndex + 1 < _compositeFormatString.Length) && (_compositeFormatString[_currentIndex + 1] == c)) + { + _state = State.Literal; + _currentIndex++; + _spanStart = _currentIndex; + } + else + { + _currentIndex++; + return ParseInsertionPoint(); + } + } + else if (c == '}') + { + if ((_currentIndex + 1 < _compositeFormatString.Length) && (_compositeFormatString[_currentIndex + 1] == c)) + { + if (_state == State.Literal) + { + _state = State.New; + return CompositeSegment.Literal(_spanStart, _currentIndex); + } + _state = State.Literal; + _currentIndex++; + _spanStart = _currentIndex; + } + else + { + throw new Exception("missing start bracket"); + } + } + else + { + if (_state != State.Literal) + { + _state = State.Literal; + _spanStart = _currentIndex; + } + } + _currentIndex++; + } + if (_state == State.Literal) + { + _state = State.New; + return CompositeSegment.Literal(_spanStart, _currentIndex); + } + return null; + } + + // this should be replaced with InvariantFormatter.Parse + static bool TryParse(string compositeFormat, int start, int count, out uint value, out int consumed) + { + consumed = 0; + value = 0; + for (int i = start; i < start + count; i++) + { + var digit = (byte)(compositeFormat[i] - '0'); + if (digit >= 0 && digit <= 9) + { + value *= 10; + value += digit; + consumed++; + } + else + { + if (i == start) return false; + else return true; + } + } + return true; + } + + CompositeSegment ParseInsertionPoint() + { + uint arg; + int consumed; + char? formatSpecifier = null; + + if (!TryParse(_compositeFormatString, _currentIndex, 5, out arg, out consumed)) + { + throw new Exception("invalid insertion point"); + } + _currentIndex += consumed; + if (_currentIndex >= _compositeFormatString.Length) + { + throw new Exception("missing end bracket"); + } + + if(_compositeFormatString[_currentIndex] == ':') + { + _currentIndex++; + formatSpecifier = _compositeFormatString[_currentIndex]; + _currentIndex++; + } + + if (_compositeFormatString[_currentIndex] != '}') + { + throw new Exception("missing end bracket"); + } + + _currentIndex++; + var parsedFormat = (formatSpecifier.HasValue && formatSpecifier.Value != 0) ? new ParsedFormat(formatSpecifier.Value) : default; + return CompositeSegment.InsertionPoint(arg, parsedFormat); + } + + public enum State : byte + { + New, + Literal, + InsertionPoint + } + + public struct CompositeSegment + { + public ParsedFormat Format { get; private set; } + public int Index { get; private set; } + public int Count { get; private set; } + + public static CompositeSegment InsertionPoint(uint argIndex, ParsedFormat format) + { + return new CompositeSegment() { Index = (int)argIndex, Format = format }; + } + + public static CompositeSegment Literal(int startIndex, int endIndex) + { + return new CompositeSegment() { Index = startIndex, Count = endIndex - startIndex }; + } + } + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Formatting/System/Text/Formatting/Formatters/ArrayFormatter.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Formatting/System/Text/Formatting/Formatters/ArrayFormatter.cs new file mode 100644 index 000000000..a63f358bc --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Formatting/System/Text/Formatting/Formatters/ArrayFormatter.cs @@ -0,0 +1,55 @@ +// 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.Sequences; + +namespace System.Text.Formatting +{ + public class ArrayFormatter : ITextOutput + { + ResizableArray _buffer; + SymbolTable _symbolTable; + ArrayPool _pool; + + public ArrayFormatter(int capacity, SymbolTable symbolTable, ArrayPool pool = null) + { + _pool = pool ?? ArrayPool.Shared; + _symbolTable = symbolTable; + _buffer = new ResizableArray(_pool.Rent(capacity)); + } + + public int CommitedByteCount => _buffer.Count; + + public void Clear() { + _buffer.Count = 0; + } + + public ArraySegment Free => _buffer.Free; + public ArraySegment Formatted => _buffer.Full; + + public SymbolTable SymbolTable => _symbolTable; + + public Span Buffer => Free.AsSpan(); + + void IOutput.Enlarge(int desiredBufferLength) + { + if (desiredBufferLength < 1) desiredBufferLength = 1; + var doubleCount = _buffer.Free.Count * 2; + int newSize = desiredBufferLength>doubleCount?desiredBufferLength:doubleCount; + var newArray = _pool.Rent(newSize + _buffer.Count); + var oldArray = _buffer.Resize(newArray); + _pool.Return(oldArray); + } + + void IOutput.Advance(int bytes) + { + _buffer.Count += bytes; + if(_buffer.Count > _buffer.Count) + { + throw new InvalidOperationException("More bytes commited than returned from FreeBuffer"); + } + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Formatting/System/Text/Formatting/Formatters/OutputFormatter.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Formatting/System/Text/Formatting/Formatters/OutputFormatter.cs new file mode 100644 index 000000000..e745517bf --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Formatting/System/Text/Formatting/Formatters/OutputFormatter.cs @@ -0,0 +1,32 @@ +// 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; + +namespace System.Text.Formatting +{ + public struct OutputFormatter : ITextOutput where TOutput : IOutput + { + TOutput _output; + SymbolTable _symbolTable; + + public OutputFormatter(TOutput output, SymbolTable symbolTable) + { + _output = output; + _symbolTable = symbolTable; + } + + public OutputFormatter(TOutput output) : this(output, SymbolTable.InvariantUtf8) + { + } + + public Span Buffer => _output.Buffer; + + public SymbolTable SymbolTable => _symbolTable; + + public void Advance(int bytes) => _output.Advance(bytes); + + public void Enlarge(int desiredBufferLength = 0) => _output.Enlarge(desiredBufferLength); + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Formatting/System/Text/Formatting/Formatters/SequenceFormatter.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Formatting/System/Text/Formatting/Formatters/SequenceFormatter.cs new file mode 100644 index 000000000..40fdb84fb --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Formatting/System/Text/Formatting/Formatters/SequenceFormatter.cs @@ -0,0 +1,105 @@ +// 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.Sequences; + +namespace System.Text.Formatting +{ + public static class SequenceFormatterExtensions + { + public static SequenceFormatter CreateFormatter(this TSequence sequence, SymbolTable symbolTable = null) where TSequence : ISequence> + { + return new SequenceFormatter(sequence, symbolTable); + } + } + + public class SequenceFormatter : ITextOutput where TSequence : ISequence> + { + ISequence> _buffers; + SymbolTable _symbolTable; + + Position _currentPosition = Position.First; + int _currentWrittenBytes; + Position _previousPosition = Position.AfterLast; + int _previousWrittenBytes; + int _totalWritten; + + public SequenceFormatter(TSequence buffers, SymbolTable symbolTable) + { + _symbolTable = symbolTable; + _buffers = buffers; + _previousWrittenBytes = -1; + } + + Span IOutput.Buffer + { + get { + return Current.Span.Slice(_currentWrittenBytes); + } + } + + private Buffer Current { + get { + Buffer result; + if (!_buffers.TryGet(ref _currentPosition, out result, advance: false)) { throw new InvalidOperationException(); } + return result; + } + } + private Buffer Previous + { + get { + Buffer result; + if (!_buffers.TryGet(ref _previousPosition, out result, advance: false)) { throw new InvalidOperationException(); } + return result; + } + } + private bool NeedShift => _previousWrittenBytes != -1; + + SymbolTable ITextOutput.SymbolTable => _symbolTable; + + public int TotalWritten => _totalWritten; + + void IOutput.Enlarge(int desiredBufferLength) + { + if (NeedShift) throw new NotImplementedException("need to allocate temp array"); + + _previousPosition = _currentPosition; + _previousWrittenBytes = _currentWrittenBytes; + + Buffer span; + if (!_buffers.TryGet(ref _currentPosition, out span)) { + throw new InvalidOperationException(); + } + _currentWrittenBytes = 0; + } + + void IOutput.Advance(int bytes) + { + var current = Current; + if (NeedShift) { + var previous = Previous; + var spaceInPrevious = previous.Length - _previousWrittenBytes; + if(spaceInPrevious < bytes) { + current.Slice(0, spaceInPrevious).CopyTo(previous.Span.Slice(_previousWrittenBytes)); + current.Slice(spaceInPrevious, bytes - spaceInPrevious).CopyTo(current.Span); + _previousWrittenBytes = -1; + _currentWrittenBytes = bytes - spaceInPrevious; + } + else { + current.Slice(0, bytes).CopyTo(previous.Span.Slice(_previousWrittenBytes)); + _currentPosition = _previousPosition; + _currentWrittenBytes = _previousWrittenBytes + bytes; + } + + } + else { + if (current.Length - _currentWrittenBytes < bytes) throw new NotImplementedException(); + _currentWrittenBytes += bytes; + } + + _totalWritten += bytes; + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Formatting/System/Text/Formatting/Formatters/StreamFormatter.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Formatting/System/Text/Formatting/Formatters/StreamFormatter.cs new file mode 100644 index 000000000..0d8182894 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Formatting/System/Text/Formatting/Formatters/StreamFormatter.cs @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.IO; +using System.Buffers; + +namespace System.Text.Formatting +{ + public struct StreamFormatter : ITextOutput, IDisposable + { + Stream _stream; + SymbolTable _symbolTable; + byte[] _buffer; + ArrayPool _pool; + + public StreamFormatter(Stream stream, ArrayPool pool) : this(stream, SymbolTable.InvariantUtf16, pool) + { + } + + public StreamFormatter(Stream stream, SymbolTable symbolTable, ArrayPool pool, int bufferSize = 256) + { + _pool = pool; + _buffer = null; + if (bufferSize > 0) + { + _buffer = _pool.Rent(bufferSize); + } + _symbolTable = symbolTable; + _stream = stream; + } + + Span IOutput.Buffer + { + get + { + if (_buffer == null) + { + _buffer = _pool.Rent(256); + } + return new Span(_buffer); + } + } + + void IOutput.Enlarge(int desiredBufferLength) + { + var newSize = _buffer.Length * 2; + if(desiredBufferLength != 0){ + newSize = desiredBufferLength; + } + var temp = _buffer; + _buffer = _pool.Rent(newSize); + _pool.Return(temp); + } + + // ISSUE + // I would like to lazy write to the stream, but unfortunatelly this seems to be exclusive with this type being a struct. + // If the write was lazy, passing this struct by value could result in data loss. + // A stack frame could write more data to the buffer, and then when the frame pops, the infroamtion about how much was written could be lost. + // On the other hand, I cannot make this type a class and keep using it as it can be used today (i.e. pass streams around and create instances of this type on demand). + // Too bad we don't support move semantics and stack only structs. + void IOutput.Advance(int bytes) + { + _stream.Write(_buffer, 0, bytes); + } + + SymbolTable ITextOutput.SymbolTable + { + get { + return _symbolTable; + } + } + + /// + /// Returns buffers to the pool + /// + public void Dispose() + { + _pool.Return(_buffer); + _buffer = null; + _stream = null; + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Formatting/System/Text/Formatting/Formatters/StringFormatter.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Formatting/System/Text/Formatting/Formatters/StringFormatter.cs new file mode 100644 index 000000000..d9f6396e7 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Formatting/System/Text/Formatting/Formatters/StringFormatter.cs @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Buffers; +using System.Collections.Sequences; + +namespace System.Text.Formatting +{ + public class StringFormatter : ITextOutput, IDisposable + { + ResizableArray _buffer; + ArrayPool _pool; + public SymbolTable SymbolTable { get; set; } = SymbolTable.InvariantUtf16; + + public StringFormatter(int characterCapacity = 32, ArrayPool pool = null) + { + if (pool == null) _pool = ArrayPool.Shared; + else _pool = pool; + _buffer = new ResizableArray(_pool.Rent(characterCapacity * 2)); + } + + public void Dispose() + { + _pool.Return(_buffer.Items); + _buffer.Count = 0; + } + + public void Append(char character) { + _buffer.Add((byte)character); + _buffer.Add((byte)(character >> 8)); + } + + //TODO: this should use Span + public void Append(string text) + { + foreach (char character in text) + { + Append(character); + } + } + + //TODO: this should use Span + public void Append(ReadOnlySpan substring) + { + for (int i = 0; i < substring.Length; i++) + { + Append(substring[i]); + } + } + + public void Clear() + { + _buffer.Clear(); + } + + public override string ToString() + { + var text = Encoding.Unicode.GetString(_buffer.Items, 0, _buffer.Count); + return text; + } + + Span IOutput.Buffer => _buffer.Free.AsSpan(); + + void IOutput.Enlarge(int desiredBufferLength) + { + if (desiredBufferLength < 1) desiredBufferLength = 1; + var doubleCount = _buffer.Free.Count * 2; + int newSize = desiredBufferLength > doubleCount ? desiredBufferLength : doubleCount; + var newArray = _pool.Rent(newSize + _buffer.Count); + var oldArray = _buffer.Resize(newArray); + _pool.Return(oldArray); + } + + void IOutput.Advance(int bytes) + { + _buffer.Count += bytes; + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Formatting/System/Text/Formatting/IOutputExtensions.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Formatting/System/Text/Formatting/IOutputExtensions.cs new file mode 100644 index 000000000..db8afa090 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Formatting/System/Text/Formatting/IOutputExtensions.cs @@ -0,0 +1,346 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Buffers; +using System.Text.Utf8; + +namespace System.Text.Formatting +{ + public static class IOutputExtensions + { + public static void Append(this TFormatter formatter, T value, SymbolTable symbolTable, ParsedFormat format = default) where T : IBufferFormattable where TFormatter : IOutput + { + while (!formatter.TryAppend(value, symbolTable, format)) { + formatter.Enlarge(); + } + } + + public static bool TryAppend(this TFormatter formatter, T value, SymbolTable symbolTable, ParsedFormat format = default) where T : IBufferFormattable where TFormatter : IOutput + { + int bytesWritten; + if (!value.TryFormat(formatter.Buffer, out bytesWritten, format, symbolTable)) { + return false; + } + formatter.Advance(bytesWritten); + return true; + } + + public static void Append(this TFormatter formatter, string value, SymbolTable symbolTable) where TFormatter : IOutput + { + formatter.Append(value.AsSpan(), symbolTable); + } + + public static bool TryAppend(this TFormatter formatter, string value, SymbolTable symbolTable) where TFormatter : IOutput + { + return formatter.TryAppend(value.AsSpan(), symbolTable); + } + + public static void Append(this TFormatter formatter, ReadOnlySpan value, SymbolTable symbolTable) where TFormatter : IOutput + { + if (value.Length <= 256) + { + while (!formatter.TryAppend(value, symbolTable)) { + formatter.Enlarge(); + } + } + else // slice the span into smaller pieces, otherwise the enlarge might fail. + { + var leftToWrite = value; + while (leftToWrite.Length > 0) + { + var chunkLength = leftToWrite.Length < 256 ? leftToWrite.Length : 256; + if (char.IsHighSurrogate(leftToWrite[chunkLength - 1])) + { + chunkLength--; + if (chunkLength == 0) throw new Exception("value ends in a high surrogate"); + } + + var chunk = leftToWrite.Slice(0, chunkLength); + formatter.Append(chunk, symbolTable); + leftToWrite = leftToWrite.Slice(chunkLength); + } + } + } + + public static bool TryAppend(this TFormatter formatter, ReadOnlySpan value, SymbolTable symbolTable) where TFormatter : IOutput + { + var result = symbolTable.TryEncode(value, formatter.Buffer, out int consumed, out int written); + if (result) + formatter.Advance(written); + + return result; + } + + public static void Append(this TFormatter formatter, char value, SymbolTable symbolTable) where TFormatter : IOutput + { + while (!formatter.TryAppend(value, symbolTable)) { + formatter.Enlarge(); + } + } + + public static bool TryAppend(this TFormatter formatter, char value, SymbolTable symbolTable) where TFormatter : IOutput + { + unsafe + { + ReadOnlySpan input = new ReadOnlySpan(&value, 1); + return formatter.TryAppend(input, symbolTable); + } + } + + public static void Append(this TFormatter formatter, Utf8String value, SymbolTable encoder) where TFormatter : IOutput + { + while (!formatter.TryAppend(value, encoder)) { + formatter.Enlarge(); + } + } + + public static bool TryAppend(this TFormatter formatter, Utf8String value, SymbolTable symbolTable) where TFormatter : IOutput + { + int bytesWritten; + int consumed; + if (!symbolTable.TryEncode(value, formatter.Buffer, out consumed, out bytesWritten)) { + return false; + } + formatter.Advance(bytesWritten); + return true; + } + + public static void Append(this TFormatter formatter, uint value, SymbolTable symbolTable, ParsedFormat format = default) where TFormatter : IOutput + { + while (!formatter.TryAppend(value, symbolTable, format)) { + formatter.Enlarge(); + } + } + + public static bool TryAppend(this TFormatter formatter, uint value, SymbolTable symbolTable, ParsedFormat format = default) where TFormatter : IOutput + { + int bytesWritten; + if (!value.TryFormat(formatter.Buffer, out bytesWritten, format, symbolTable)) { + return false; + } + formatter.Advance(bytesWritten); + return true; + } + + public static void Append(this TFormatter formatter, ulong value, SymbolTable symbolTable, ParsedFormat format = default) where TFormatter : IOutput + { + while (!formatter.TryAppend(value, symbolTable, format)) { + formatter.Enlarge(); + } + } + + public static bool TryAppend(this TFormatter formatter, ulong value, SymbolTable symbolTable, ParsedFormat format = default) where TFormatter : IOutput + { + int bytesWritten; + if (!value.TryFormat(formatter.Buffer, out bytesWritten, format, symbolTable)) { + return false; + } + formatter.Advance(bytesWritten); + return true; + } + + public static void Append(this TFormatter formatter, int value, SymbolTable symbolTable, ParsedFormat format = default) where TFormatter : IOutput + { + while (!formatter.TryAppend(value, symbolTable, format)) { + formatter.Enlarge(); + } + } + + public static bool TryAppend(this TFormatter formatter, int value, SymbolTable symbolTable, ParsedFormat format = default) where TFormatter : IOutput + { + int bytesWritten; + if (!value.TryFormat(formatter.Buffer, out bytesWritten, format, symbolTable)) { + return false; + } + formatter.Advance(bytesWritten); + return true; + } + + public static void Append(this TFormatter formatter, long value, SymbolTable symbolTable, ParsedFormat format = default) where TFormatter : IOutput + { + while (!formatter.TryAppend(value, symbolTable, format)) { + formatter.Enlarge(); + } + } + + public static bool TryAppend(this TFormatter formatter, long value, SymbolTable symbolTable, ParsedFormat format = default) where TFormatter : IOutput + { + int bytesWritten; + if (!value.TryFormat(formatter.Buffer, out bytesWritten, format, symbolTable)) { + return false; + } + formatter.Advance(bytesWritten); + return true; + } + + public static void Append(this TFormatter formatter, byte value, SymbolTable symbolTable, ParsedFormat format = default) where TFormatter : IOutput + { + while (!formatter.TryAppend(value, symbolTable, format)) { + formatter.Enlarge(); + } + } + + public static bool TryAppend(this TFormatter formatter, byte value, SymbolTable symbolTable, ParsedFormat format = default) where TFormatter : IOutput + { + int bytesWritten; + if (!value.TryFormat(formatter.Buffer, out bytesWritten, format, symbolTable)) { + return false; + } + formatter.Advance(bytesWritten); + return true; + } + + public static void Append(this TFormatter formatter, sbyte value, SymbolTable symbolTable, ParsedFormat format = default) where TFormatter : IOutput + { + while (!formatter.TryAppend(value, symbolTable, format)) { + formatter.Enlarge(); + } + } + + public static bool TryAppend(this TFormatter formatter, sbyte value, SymbolTable symbolTable, ParsedFormat format = default) where TFormatter : IOutput + { + int bytesWritten; + if (!value.TryFormat(formatter.Buffer, out bytesWritten, format, symbolTable)) { + return false; + } + formatter.Advance(bytesWritten); + return true; + } + + public static void Append(this TFormatter formatter, ushort value, SymbolTable symbolTable, ParsedFormat format = default) where TFormatter : IOutput + { + while (!formatter.TryAppend(value, symbolTable, format)) { + formatter.Enlarge(); + } + } + + public static bool TryAppend(this TFormatter formatter, ushort value, SymbolTable symbolTable, ParsedFormat format = default) where TFormatter : IOutput + { + int bytesWritten; + if (!value.TryFormat(formatter.Buffer, out bytesWritten, format, symbolTable)) { + return false; + } + formatter.Advance(bytesWritten); + return true; + } + + public static void Append(this TFormatter formatter, short value, SymbolTable symbolTable, ParsedFormat format = default) where TFormatter : IOutput + { + while (!formatter.TryAppend(value, symbolTable, format)) { + formatter.Enlarge(); + } + } + + public static bool TryAppend(this TFormatter formatter, short value, SymbolTable symbolTable, ParsedFormat format = default) where TFormatter : IOutput + { + int bytesWritten; + if (!value.TryFormat(formatter.Buffer, out bytesWritten, format, symbolTable)) { + return false; + } + formatter.Advance(bytesWritten); + return true; + } + + public static void Append(this TFormatter formatter, Guid value, SymbolTable symbolTable, ParsedFormat format = default) where TFormatter : IOutput + { + while (!formatter.TryAppend(value, symbolTable, format)) { + formatter.Enlarge(); + } + } + + public static bool TryAppend(this TFormatter formatter, Guid value, SymbolTable symbolTable, ParsedFormat format = default) where TFormatter : IOutput + { + int bytesWritten; + if (!value.TryFormat(formatter.Buffer, out bytesWritten, format, symbolTable)) { + return false; + } + formatter.Advance(bytesWritten); + return true; + } + + public static void Append(this TFormatter formatter, DateTime value, SymbolTable symbolTable, ParsedFormat format = default) where TFormatter : IOutput + { + while (!formatter.TryAppend(value, symbolTable, format)) { + formatter.Enlarge(); + } + } + + public static bool TryAppend(this TFormatter formatter, DateTime value, SymbolTable symbolTable, ParsedFormat format = default) where TFormatter : IOutput + { + int bytesWritten; + if (!value.TryFormat(formatter.Buffer, out bytesWritten, format, symbolTable)) { + return false; + } + formatter.Advance(bytesWritten); + return true; + } + + public static void Append(this TFormatter formatter, DateTimeOffset value, SymbolTable symbolTable, ParsedFormat format = default) where TFormatter : IOutput + { + while (!formatter.TryAppend(value, symbolTable, format)) { + formatter.Enlarge(); + } + } + + public static bool TryAppend(this TFormatter formatter, DateTimeOffset value, SymbolTable symbolTable, ParsedFormat format = default) where TFormatter : IOutput + { + int bytesWritten; + if (!value.TryFormat(formatter.Buffer, out bytesWritten, format, symbolTable)) { + return false; + } + formatter.Advance(bytesWritten); + return true; + } + + public static void Append(this TFormatter formatter, TimeSpan value, SymbolTable symbolTable, ParsedFormat format = default) where TFormatter : IOutput + { + while (!formatter.TryAppend(value, symbolTable, format)) { + formatter.Enlarge(); + } + } + + public static bool TryAppend(this TFormatter formatter, TimeSpan value, SymbolTable symbolTable, ParsedFormat format = default) where TFormatter : IOutput + { + int bytesWritten; + if (!value.TryFormat(formatter.Buffer, out bytesWritten, format, symbolTable)) { + return false; + } + formatter.Advance(bytesWritten); + return true; + } + + public static void Append(this TFormatter formatter, float value, SymbolTable symbolTable, ParsedFormat format = default) where TFormatter : IOutput + { + while (!formatter.TryAppend(value, symbolTable, format)) { + formatter.Enlarge(); + } + } + + public static bool TryAppend(this TFormatter formatter, float value, SymbolTable symbolTable, ParsedFormat format = default) where TFormatter : IOutput + { + int bytesWritten; + if (!value.TryFormat(formatter.Buffer, out bytesWritten, format, symbolTable)) { + return false; + } + formatter.Advance(bytesWritten); + return true; + } + + public static void Append(this TFormatter formatter, double value, SymbolTable symbolTable, ParsedFormat format = default) where TFormatter : IOutput + { + while (!formatter.TryAppend(value, symbolTable, format)) { + formatter.Enlarge(); + } + } + + public static bool TryAppend(this TFormatter formatter, double value, SymbolTable symbolTable, ParsedFormat format = default) where TFormatter : IOutput + { + int bytesWritten; + if (!value.TryFormat(formatter.Buffer, out bytesWritten, format, symbolTable)) { + return false; + } + formatter.Advance(bytesWritten); + return true; + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Formatting/System/Text/Formatting/ITextOutput.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Formatting/System/Text/Formatting/ITextOutput.cs new file mode 100644 index 000000000..16ba755ac --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Formatting/System/Text/Formatting/ITextOutput.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Buffers; + +namespace System.Text.Formatting +{ + // this interface would be implemented by types that want to support formatting, i.e. TextWriter/StringBuilder-like types. + // the interface is used by an extension method in IFormatterExtensions. + // One thing I am not sure here is if it's ok for these APIs to be synchronous, but I guess I will wait till I find a concrete issue with this. + public interface ITextOutput : IOutput + { + SymbolTable SymbolTable { get; } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Formatting/System/Text/Formatting/ITextOutputExtensions.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Formatting/System/Text/Formatting/ITextOutputExtensions.cs new file mode 100644 index 000000000..feae214f1 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Formatting/System/Text/Formatting/ITextOutputExtensions.cs @@ -0,0 +1,319 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Text.Utf8; + +namespace System.Text.Formatting +{ + public static class ITextOutputExtensions + { + public static void Append(this TFormatter formatter, T value, ParsedFormat format = default) where T : IBufferFormattable where TFormatter : ITextOutput + { + while(!formatter.TryAppend(value, format)) { + formatter.Enlarge(); + } + } + + public static bool TryAppend(this TFormatter formatter, T value, ParsedFormat format = default) where T : IBufferFormattable where TFormatter : ITextOutput + { + int bytesWritten; + if(!value.TryFormat(formatter.Buffer, out bytesWritten, format, formatter.SymbolTable)) { + return false; + } + formatter.Advance(bytesWritten); + return true; + } + + public static void Append(this TFormatter formatter, byte value, ParsedFormat format = default) where TFormatter : ITextOutput + { + while (!formatter.TryAppend(value, format)) { + formatter.Enlarge(); + } + } + + public static bool TryAppend(this TFormatter formatter, byte value, ParsedFormat format = default) where TFormatter : ITextOutput + { + int bytesWritten; + if(!value.TryFormat(formatter.Buffer, out bytesWritten, format, formatter.SymbolTable)) { + return false; + } + formatter.Advance(bytesWritten); + return true; + } + + public static void Append(this TFormatter formatter, sbyte value, ParsedFormat format = default) where TFormatter : ITextOutput + { + while (!formatter.TryAppend(value, format)) { + formatter.Enlarge(); + } + } + + public static bool TryAppend(this TFormatter formatter, sbyte value, ParsedFormat format = default) where TFormatter : ITextOutput + { + int bytesWritten; + if (!value.TryFormat(formatter.Buffer, out bytesWritten, format, formatter.SymbolTable)) { + return false; + } + formatter.Advance(bytesWritten); + return true; + } + + public static void Append(this TFormatter formatter, ushort value, ParsedFormat format = default) where TFormatter : ITextOutput + { + while (!formatter.TryAppend(value, format)) { + formatter.Enlarge(); + } + } + + public static bool TryAppend(this TFormatter formatter, ushort value, ParsedFormat format = default) where TFormatter : ITextOutput + { + int bytesWritten; + if (!value.TryFormat(formatter.Buffer, out bytesWritten, format, formatter.SymbolTable)) { + return false; + } + formatter.Advance(bytesWritten); + return true; + } + + public static void Append(this TFormatter formatter, short value, ParsedFormat format = default) where TFormatter : ITextOutput + { + while (!formatter.TryAppend(value, format)) { + formatter.Enlarge(); + } + } + + public static bool TryAppend(this TFormatter formatter, short value, ParsedFormat format = default) where TFormatter : ITextOutput + { + int bytesWritten; + if (!value.TryFormat(formatter.Buffer, out bytesWritten, format, formatter.SymbolTable)) { + return false; + } + formatter.Advance(bytesWritten); + return true; + } + + public static void Append(this TFormatter formatter, uint value, ParsedFormat format = default) where TFormatter : ITextOutput + { + while (!formatter.TryAppend(value, format)) { + formatter.Enlarge(); + } + } + + public static bool TryAppend(this TFormatter formatter, uint value, ParsedFormat format = default) where TFormatter : ITextOutput + { + int bytesWritten; + if (!value.TryFormat(formatter.Buffer, out bytesWritten, format, formatter.SymbolTable)) { + return false; + } + formatter.Advance(bytesWritten); + return true; + } + + public static void Append(this TFormatter formatter, int value, ParsedFormat format = default) where TFormatter : ITextOutput + { + while (!formatter.TryAppend(value, format)) { + formatter.Enlarge(); + } + } + + public static bool TryAppend(this TFormatter formatter, int value, ParsedFormat format = default) where TFormatter : ITextOutput + { + int bytesWritten; + if (!value.TryFormat(formatter.Buffer, out bytesWritten, format, formatter.SymbolTable)) { + return false; + } + formatter.Advance(bytesWritten); + return true; + } + + public static void Append(this TFormatter formatter, ulong value, ParsedFormat format = default) where TFormatter : ITextOutput + { + while (!formatter.TryAppend(value, format)) { + formatter.Enlarge(); + } + } + + public static bool TryAppend(this TFormatter formatter, ulong value, ParsedFormat format = default) where TFormatter : ITextOutput + { + int bytesWritten; + if (!value.TryFormat(formatter.Buffer, out bytesWritten, format, formatter.SymbolTable)) { + return false; + } + formatter.Advance(bytesWritten); + return true; + } + + public static void Append(this TFormatter formatter, long value, ParsedFormat format = default) where TFormatter : ITextOutput + { + while (!formatter.TryAppend(value, format)) { + formatter.Enlarge(); + } + } + + public static bool TryAppend(this TFormatter formatter, long value, ParsedFormat format = default) where TFormatter : ITextOutput + { + int bytesWritten; + if (!value.TryFormat(formatter.Buffer, out bytesWritten, format, formatter.SymbolTable)) { + return false; + } + formatter.Advance(bytesWritten); + return true; + } + + public static void Append(this TFormatter formatter, char value) where TFormatter : ITextOutput + { + while (!formatter.TryAppend(value)) { + formatter.Enlarge(); + } + } + + public static bool TryAppend(this TFormatter formatter, char value) where TFormatter : ITextOutput + { + return formatter.TryAppend(value, formatter.SymbolTable); + } + + public static void Append(this TFormatter formatter, ReadOnlySpan value) where TFormatter : ITextOutput + { + while (!formatter.TryAppend(value)) { + formatter.Enlarge(); + } + } + + public static bool TryAppend(this TFormatter formatter, ReadOnlySpan value) where TFormatter : ITextOutput + { + return formatter.TryAppend(value, formatter.SymbolTable); + } + + public static void Append(this TFormatter formatter, string value) where TFormatter : ITextOutput + { + while (!formatter.TryAppend(value)) { + formatter.Enlarge(); + } + } + + public static bool TryAppend(this TFormatter formatter, string value) where TFormatter : ITextOutput + { + return formatter.TryAppend(value, formatter.SymbolTable); + } + + public static void Append(this TFormatter formatter, Utf8String value) where TFormatter : ITextOutput + { + while (!formatter.TryAppend(value)) { + formatter.Enlarge(); + } + } + + public static bool TryAppend(this TFormatter formatter, Utf8String value) where TFormatter : ITextOutput + { + int bytesWritten; + int consumed; + if (!formatter.SymbolTable.TryEncode(value, formatter.Buffer, out consumed, out bytesWritten)) { + return false; + } + formatter.Advance(bytesWritten); + return true; + } + + public static void Append(this TFormatter formatter, Guid value, ParsedFormat format = default) where TFormatter : ITextOutput + { + while (!formatter.TryAppend(value, format)) { + formatter.Enlarge(); + } + } + + public static bool TryAppend(this TFormatter formatter, Guid value, ParsedFormat format = default) where TFormatter : ITextOutput + { + int bytesWritten; + if (!value.TryFormat(formatter.Buffer, out bytesWritten, format, formatter.SymbolTable)) { + return false; + } + formatter.Advance(bytesWritten); + return true; + } + + public static void Append(this TFormatter formatter, DateTime value, ParsedFormat format = default) where TFormatter : ITextOutput + { + while (!formatter.TryAppend(value, format)) { + formatter.Enlarge(); + } + } + + public static bool TryAppend(this TFormatter formatter, DateTime value, ParsedFormat format = default) where TFormatter : ITextOutput + { + int bytesWritten; + if (!value.TryFormat(formatter.Buffer, out bytesWritten, format, formatter.SymbolTable)) { + return false; + } + formatter.Advance(bytesWritten); + return true; + } + + public static void Append(this TFormatter formatter, DateTimeOffset value, ParsedFormat format = default) where TFormatter : ITextOutput + { + while (!formatter.TryAppend(value, format)) { + formatter.Enlarge(); + } + } + + public static bool TryAppend(this TFormatter formatter, DateTimeOffset value, ParsedFormat format = default) where TFormatter : ITextOutput + { + int bytesWritten; + if (!value.TryFormat(formatter.Buffer, out bytesWritten, format, formatter.SymbolTable)) { + return false; + } + formatter.Advance(bytesWritten); + return true; + } + + public static void Append(this TFormatter formatter, TimeSpan value, ParsedFormat format = default) where TFormatter : ITextOutput + { + while (!formatter.TryAppend(value, format)) { + formatter.Enlarge(); + } + } + + public static bool TryAppend(this TFormatter formatter, TimeSpan value, ParsedFormat format = default) where TFormatter : ITextOutput + { + int bytesWritten; + if (!value.TryFormat(formatter.Buffer, out bytesWritten, format, formatter.SymbolTable)) { + return false; + } + formatter.Advance(bytesWritten); + return true; + } + + public static void Append(this TFormatter formatter, float value, ParsedFormat format = default) where TFormatter : ITextOutput + { + while (!formatter.TryAppend(value, format)) { + formatter.Enlarge(); + } + } + + public static bool TryAppend(this TFormatter formatter, float value, ParsedFormat format = default) where TFormatter : ITextOutput + { + int bytesWritten; + if (!value.TryFormat(formatter.Buffer, out bytesWritten, format, formatter.SymbolTable)) { + return false; + } + formatter.Advance(bytesWritten); + return true; + } + + public static void Append(this TFormatter formatter, double value, ParsedFormat format = default) where TFormatter : ITextOutput + { + while (!formatter.TryAppend(value, format)) { + formatter.Enlarge(); + } + } + + public static bool TryAppend(this TFormatter formatter, double value, ParsedFormat format = default) where TFormatter : ITextOutput + { + int bytesWritten; + if (!value.TryFormat(formatter.Buffer, out bytesWritten, format, formatter.SymbolTable)) { + return false; + } + formatter.Advance(bytesWritten); + return true; + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Formatting/System/Text/Parsing/SequenceParser.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Formatting/System/Text/Parsing/SequenceParser.cs new file mode 100644 index 000000000..225380ee6 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Formatting/System/Text/Parsing/SequenceParser.cs @@ -0,0 +1,159 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Buffers; +using System.Collections.Sequences; + +namespace System.Text.Parsing +{ + public static class TextSequenceExtensions + { + const int StackBufferSize = 128; + + public static bool TryParseUInt64(this T bufferSequence, out ulong value, out int consumed) where T : ISequence> + { + value = default; + consumed = default; + Position position = Position.First; + + // Fetch the first segment + ReadOnlyBuffer first; + if (!bufferSequence.TryGet(ref position, out first)) { + return false; + } + + // Attempt to parse the first segment. If it works (and it should in most cases), then return success. + bool parsed = PrimitiveParser.InvariantUtf8.TryParseUInt64(first.Span, out value, out consumed); + if (parsed && consumed < first.Length) { + return true; + } + + // Apparently the we need data from the second segment to succesfully parse, and so fetch the second segment. + ReadOnlyBuffer second; + if (!bufferSequence.TryGet(ref position, out second)) { + // if there is no second segment and the first parsed succesfully, return the result of the parsing. + if (parsed) return true; + return false; + } + + // Combine the first, the second, and potentially more segments into a stack allocated buffer + ReadOnlySpan combinedSpan; + unsafe + { + if (first.Length < StackBufferSize) { + var data = stackalloc byte[StackBufferSize]; + var destination = new Span(data, StackBufferSize); + + first.CopyTo(destination); + var free = destination.Slice(first.Length); + + if (second.Length > free.Length) second = second.Slice(0, free.Length); + second.CopyTo(free); + free = free.Slice(second.Length); + + ReadOnlyBuffer next; + while (free.Length > 0) { + if (bufferSequence.TryGet(ref position, out next)) { + if (next.Length > free.Length) next = next.Slice(0, free.Length); + next.CopyTo(free); + free = free.Slice(next.Length); + } + else { + break; + } + } + + combinedSpan = destination.Slice(0, StackBufferSize - free.Length); + + // if the stack allocated buffer parsed succesfully (and for uint it should always do), then return success. + if (PrimitiveParser.InvariantUtf8.TryParseUInt64(combinedSpan, out value, out consumed)) { + if(consumed < combinedSpan.Length || combinedSpan.Length < StackBufferSize) { + return true; + } + } + } + } + + // for invariant culture, we should never reach this point, as invariant uint text is never longer than 127 bytes. + // I left this code here, as we will need it for custom cultures and possibly when we shrink the stack allocated buffer. + combinedSpan = bufferSequence.ToSpan(); + if (!PrimitiveParser.InvariantUtf8.TryParseUInt64(first.Span, out value, out consumed)) { + return false; + } + return true; + } + + public static bool TryParseUInt32(this T bufferSequence, out uint value, out int consumed) where T : ISequence> + { + value = default; + consumed = default; + Position position = Position.First; + + // Fetch the first segment + ReadOnlyBuffer first; + if (!bufferSequence.TryGet(ref position, out first)) { + return false; + } + + // Attempt to parse the first segment. If it works (and it should in most cases), then return success. + bool parsed = PrimitiveParser.InvariantUtf8.TryParseUInt32(first.Span, out value, out consumed); + if (parsed && consumed < first.Length) { + return true; + } + + // Apparently the we need data from the second segment to succesfully parse, and so fetch the second segment. + ReadOnlyBuffer second; + if (!bufferSequence.TryGet(ref position, out second)) { + // if there is no second segment and the first parsed succesfully, return the result of the parsing. + if (parsed) return true; + return false; + } + + // Combine the first, the second, and potentially more segments into a stack allocated buffer + ReadOnlySpan combinedSpan; + unsafe + { + if (first.Length < StackBufferSize) { + var data = stackalloc byte[StackBufferSize]; + var destination = new Span(data, StackBufferSize); + + first.CopyTo(destination); + var free = destination.Slice(first.Length); + + if (second.Length > free.Length) second = second.Slice(0, free.Length); + second.CopyTo(free); + free = free.Slice(second.Length); + + ReadOnlyBuffer next; + while (free.Length > 0) { + if (bufferSequence.TryGet(ref position, out next)) { + if (next.Length > free.Length) next = next.Slice(0, free.Length); + next.CopyTo(free); + free = free.Slice(next.Length); + } + else { + break; + } + } + + combinedSpan = destination.Slice(0, StackBufferSize - free.Length); + + // if the stack allocated buffer parsed succesfully (and for uint it should always do), then return success. + if (PrimitiveParser.InvariantUtf8.TryParseUInt32(combinedSpan, out value, out consumed)) { + if(consumed < combinedSpan.Length || combinedSpan.Length < StackBufferSize) { + return true; + } + } + } + } + + // for invariant culture, we should never reach this point, as invariant uint text is never longer than 127 bytes. + // I left this code here, as we will need it for custom cultures and possibly when we shrink the stack allocated buffer. + combinedSpan = bufferSequence.ToSpan(); + if (!PrimitiveParser.InvariantUtf8.TryParseUInt32(combinedSpan, out value, out consumed)) { + return false; + } + return true; + } + } +} \ No newline at end of file diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Precondition.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Precondition.cs new file mode 100644 index 000000000..497a69802 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Precondition.cs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; + +namespace System.Diagnostics +{ + internal static class Precondition + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Require(bool condition) + { + if (!condition) + { + Fail(); + } + } + + private static void Fail() + { + if (Debugger.IsAttached) + { + Debugger.Break(); + } + throw new Failure(); + } + + public sealed class Failure : Exception + { + static string s_message = "precondition failed"; + internal Failure() : base(s_message) { } + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Encoding/Ascii/Ascii_casing.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Encoding/Ascii/Ascii_casing.cs new file mode 100644 index 000000000..be94fbb7f --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Encoding/Ascii/Ascii_casing.cs @@ -0,0 +1,72 @@ +// 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; + +namespace System.Text.Encoders +{ + public static partial class Ascii + { + static readonly byte[] s_toLower = new byte[128]; + static readonly byte[] s_toUpper = new byte[128]; + + static Ascii() + { + for (int i = 0; i < s_toLower.Length; i++) + { + s_toLower[i] = (byte)char.ToLowerInvariant(((char)i)); + s_toUpper[i] = (byte)char.ToUpperInvariant(((char)i)); + } + } + + public static TransformationStatus ToLowerInPlace(Span ascii, out int bytesChanged) + { + for (bytesChanged = 0; bytesChanged < ascii.Length; bytesChanged++) + { + byte next = ascii[bytesChanged]; + if (next > 127) + { + return TransformationStatus.InvalidData; + } + ascii[bytesChanged] = s_toLower[next]; + } + return TransformationStatus.Done; + } + + public static TransformationStatus ToLower(ReadOnlySpan input, Span output, out int processedBytes) + { + int min = input.Length < output.Length ? input.Length : output.Length; + for (processedBytes = 0; processedBytes < min; processedBytes++) + { + byte next = input[processedBytes]; + if (next > 127) return TransformationStatus.InvalidData; + output[processedBytes] = s_toLower[next]; + } + return TransformationStatus.Done; + } + + public static TransformationStatus ToUpperInPlace(Span ascii, out int bytesChanged) + { + for (bytesChanged = 0; bytesChanged < ascii.Length; bytesChanged++) + { + byte next = ascii[bytesChanged]; + if (next > 127) return TransformationStatus.InvalidData; + ascii[bytesChanged] = s_toUpper[next]; + } + return TransformationStatus.Done; + } + + public static TransformationStatus ToUpper(ReadOnlySpan input, Span output, out int processedBytes) + { + int min = input.Length < output.Length ? input.Length : output.Length; + for (processedBytes = 0; processedBytes < min; processedBytes++) + { + byte next = input[processedBytes]; + if (next > 127) return TransformationStatus.InvalidData; + output[processedBytes] = s_toUpper[next]; + } + return TransformationStatus.Done; + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Encoding/Ascii/Ascii_encoding.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Encoding/Ascii/Ascii_encoding.cs new file mode 100644 index 000000000..42f2b7a56 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Encoding/Ascii/Ascii_encoding.cs @@ -0,0 +1,128 @@ +// 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.Runtime.CompilerServices; + +namespace System.Text.Encoders +{ + public static partial class Ascii + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string ToUtf16String(ReadOnlySpan bytes) + { + var len = bytes.Length; + if (len == 0) { + return string.Empty; + } + + var result = new string('\0', len); + + unsafe + { + fixed (char* destination = result) + fixed (byte* source = &bytes.DangerousGetPinnableReference()) { + if (!TryGetAsciiString(source, destination, len)) { + ThrowArgumentException(); + } + } + } + + return result; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string ToUtf16String(Span bytes) + { + var len = bytes.Length; + if (len == 0) { + return string.Empty; + } + + var result = new string('\0', len); + + unsafe + { + fixed (char* destination = result) + fixed (byte* source = &bytes.DangerousGetPinnableReference()) { + if (!TryGetAsciiString(source, destination, len)) { + ThrowArgumentException(); + } + } + } + + return result; + } + + static void ThrowArgumentException() + { + throw new ArgumentException(); + } + + static unsafe bool TryGetAsciiString(byte* input, char* output, int count) + { + var i = 0; + + int isValid = 0; + while (i < count - 11) { + isValid = isValid | *input | *(input + 1) | *(input + 2) | + *(input + 3) | *(input + 4) | *(input + 5) | *(input + 6) | + *(input + 7) | *(input + 8) | *(input + 9) | *(input + 10) | + *(input + 11); + + i += 12; + *(output) = (char)*(input); + *(output + 1) = (char)*(input + 1); + *(output + 2) = (char)*(input + 2); + *(output + 3) = (char)*(input + 3); + *(output + 4) = (char)*(input + 4); + *(output + 5) = (char)*(input + 5); + *(output + 6) = (char)*(input + 6); + *(output + 7) = (char)*(input + 7); + *(output + 8) = (char)*(input + 8); + *(output + 9) = (char)*(input + 9); + *(output + 10) = (char)*(input + 10); + *(output + 11) = (char)*(input + 11); + output += 12; + input += 12; + } + if (i < count - 5) { + isValid = isValid | *input | *(input + 1) | *(input + 2) | + *(input + 3) | *(input + 4) | *(input + 5); + + i += 6; + *(output) = (char)*(input); + *(output + 1) = (char)*(input + 1); + *(output + 2) = (char)*(input + 2); + *(output + 3) = (char)*(input + 3); + *(output + 4) = (char)*(input + 4); + *(output + 5) = (char)*(input + 5); + output += 6; + input += 6; + } + if (i < count - 3) { + isValid = isValid | *input | *(input + 1) | *(input + 2) | + *(input + 3); + + i += 4; + *(output) = (char)*(input); + *(output + 1) = (char)*(input + 1); + *(output + 2) = (char)*(input + 2); + *(output + 3) = (char)*(input + 3); + output += 4; + input += 4; + } + + while (i < count) { + isValid = isValid | *input; + + i++; + *output = (char)*input; + output++; + input++; + } + + return isValid <= 127; + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Encoding/Encoders.Utf16.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Encoding/Encoders.Utf16.cs new file mode 100644 index 000000000..9ea1efe8d --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Encoding/Encoders.Utf16.cs @@ -0,0 +1,529 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Buffers; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace System.Text.Encoders +{ + public static class Utf16 + { + #region UTF-8 Conversions + + /// + /// Calculates the byte count needed to encode the UTF-16 bytes from the specified UTF-8 sequence. + /// + /// This method will consume as many of the input bytes as possible. + /// + /// A span containing a sequence of UTF-8 bytes. + /// On exit, contains the number of bytes required for encoding from the . + /// A value representing the expected state of the conversion. + public static TransformationStatus FromUtf8Length(ReadOnlySpan source, out int bytesNeeded) + => Utf8.ToUtf16Length(source, out bytesNeeded); + + /// + /// Converts a span containing a sequence of UTF-8 bytes into UTF-16 bytes. + /// + /// This method will consume as many of the input bytes as possible. + /// + /// On successful exit, the entire input was consumed and encoded successfully. In this case, will be + /// equal to the length of the and will equal the total number of bytes written to + /// the . + /// + /// A span containing a sequence of UTF-8 bytes. + /// A span to write the UTF-16 bytes into. + /// On exit, contains the number of bytes that were consumed from the . + /// On exit, contains the number of bytes written to + /// A value representing the state of the conversion. + public static TransformationStatus FromUtf8(ReadOnlySpan source, Span destination, out int bytesConsumed, out int bytesWritten) + => Utf8.ToUtf16(source, destination, out bytesConsumed, out bytesWritten); + + /// + /// Calculates the byte count needed to encode the UTF-8 bytes from the specified UTF-16 sequence. + /// + /// This method will consume as many of the input bytes as possible. + /// + /// A span containing a sequence of UTF-16 bytes. + /// On exit, contains the number of bytes required for encoding from the . + /// A value representing the expected state of the conversion. + public static TransformationStatus ToUtf8Length(ReadOnlySpan source, out int bytesNeeded) + { + bytesNeeded = 0; + + // try? because Convert.ConvertToUtf32 can throw + // if the high/low surrogates aren't valid; no point + // running all the tests twice per code-point + try + { + ref char utf16 = ref Unsafe.As(ref source.DangerousGetPinnableReference()); + int utf16Length = source.Length >> 1; // byte => char count + + for (int i = 0; i < utf16Length; i++) + { + var ch = Unsafe.Add(ref utf16, i); + + if ((ushort)ch <= 0x7f) // Fast path for ASCII + bytesNeeded++; + else if (!char.IsSurrogate(ch)) + bytesNeeded += EncodingHelper.GetUtf8EncodedBytes((uint)ch); + else + { + if (++i >= utf16Length) + return TransformationStatus.NeedMoreSourceData; + + uint codePoint = (uint)char.ConvertToUtf32(ch, Unsafe.Add(ref utf16, i)); + bytesNeeded += EncodingHelper.GetUtf8EncodedBytes(codePoint); + } + } + + if ((utf16Length << 1) != source.Length) + return TransformationStatus.NeedMoreSourceData; + + return TransformationStatus.Done; + } + catch (ArgumentOutOfRangeException) + { + return TransformationStatus.InvalidData; + } + } + + /// + /// Converts a span containing a sequence of UTF-16 bytes into UTF-8 bytes. + /// + /// This method will consume as many of the input bytes as possible. + /// + /// On successful exit, the entire input was consumed and encoded successfully. In this case, will be + /// equal to the length of the and will equal the total number of bytes written to + /// the . + /// + /// A span containing a sequence of UTF-16 bytes. + /// A span to write the UTF-8 bytes into. + /// On exit, contains the number of bytes that were consumed from the . + /// On exit, contains the number of bytes written to + /// A value representing the state of the conversion. + public unsafe static TransformationStatus ToUtf8(ReadOnlySpan source, Span destination, out int bytesConsumed, out int bytesWritten) + { + // + // + // KEEP THIS IMPLEMENTATION IN SYNC WITH https://github.com/dotnet/corert/blob/master/src/System.Private.CoreLib/src/System/Text/UTF8Encoding.cs + // + // + fixed (byte* chars = &source.DangerousGetPinnableReference()) + fixed (byte* bytes = &destination.DangerousGetPinnableReference()) + { + char* pSrc = (char*)chars; + byte* pTarget = bytes; + + char* pEnd = (char*)(chars + source.Length); + byte* pAllocatedBufferEnd = pTarget + destination.Length; + + // assume that JIT will enregister pSrc, pTarget and ch + + // Entering the fast encoding loop incurs some overhead that does not get amortized for small + // number of characters, and the slow encoding loop typically ends up running for the last few + // characters anyway since the fast encoding loop needs 5 characters on input at least. + // Thus don't use the fast decoding loop at all if we don't have enough characters. The threashold + // was choosen based on performance testing. + // Note that if we don't have enough bytes, pStop will prevent us from entering the fast loop. + while (pEnd - pSrc > 13) + { + // we need at least 1 byte per character, but Convert might allow us to convert + // only part of the input, so try as much as we can. Reduce charCount if necessary + int available = Math.Min(EncodingHelper.PtrDiff(pEnd, pSrc), EncodingHelper.PtrDiff(pAllocatedBufferEnd, pTarget)); + + // FASTLOOP: + // - optimistic range checks + // - fallbacks to the slow loop for all special cases, exception throwing, etc. + + // To compute the upper bound, assume that all characters are ASCII characters at this point, + // the boundary will be decreased for every non-ASCII character we encounter + // Also, we need 5 chars reserve for the unrolled ansi decoding loop and for decoding of surrogates + // If there aren't enough bytes for the output, then pStop will be <= pSrc and will bypass the loop. + char* pStop = pSrc + available - 5; + if (pSrc >= pStop) + break; + + do + { + int ch = *pSrc; + pSrc++; + + if (ch > 0x7F) + { + goto LongCode; + } + *pTarget = (byte)ch; + pTarget++; + + // get pSrc aligned + if ((unchecked((int)pSrc) & 0x2) != 0) + { + ch = *pSrc; + pSrc++; + if (ch > 0x7F) + { + goto LongCode; + } + *pTarget = (byte)ch; + pTarget++; + } + + // Run 4 characters at a time! + while (pSrc < pStop) + { + ch = *(int*)pSrc; + int chc = *(int*)(pSrc + 2); + if (((ch | chc) & unchecked((int)0xFF80FF80)) != 0) + { + goto LongCodeWithMask; + } + + // Unfortunately, this is endianess sensitive +#if BIGENDIAN + *pTarget = (byte)(ch >> 16); + *(pTarget + 1) = (byte)ch; + pSrc += 4; + *(pTarget + 2) = (byte)(chc >> 16); + *(pTarget + 3) = (byte)chc; + pTarget += 4; +#else // BIGENDIAN + *pTarget = (byte)ch; + *(pTarget + 1) = (byte)(ch >> 16); + pSrc += 4; + *(pTarget + 2) = (byte)chc; + *(pTarget + 3) = (byte)(chc >> 16); + pTarget += 4; +#endif // BIGENDIAN + } + continue; + + LongCodeWithMask: +#if BIGENDIAN + // be careful about the sign extension + ch = (int)(((uint)ch) >> 16); +#else // BIGENDIAN + ch = (char)ch; +#endif // BIGENDIAN + pSrc++; + + if (ch > 0x7F) + { + goto LongCode; + } + *pTarget = (byte)ch; + pTarget++; + continue; + + LongCode: + // use separate helper variables for slow and fast loop so that the jit optimizations + // won't get confused about the variable lifetimes + int chd; + if (ch <= 0x7FF) + { + // 2 byte encoding + chd = unchecked((sbyte)0xC0) | (ch >> 6); + } + else + { + // if (!IsLowSurrogate(ch) && !IsHighSurrogate(ch)) + if (!EncodingHelper.InRange(ch, EncodingHelper.HighSurrogateStart, EncodingHelper.LowSurrogateEnd)) + { + // 3 byte encoding + chd = unchecked((sbyte)0xE0) | (ch >> 12); + } + else + { + // 4 byte encoding - high surrogate + low surrogate + // if (!IsHighSurrogate(ch)) + if (ch > EncodingHelper.HighSurrogateEnd) + { + // low without high -> bad + goto InvalidData; + } + + chd = *pSrc; + + // if (!IsLowSurrogate(chd)) { + if (!EncodingHelper.InRange(chd, EncodingHelper.LowSurrogateStart, EncodingHelper.LowSurrogateEnd)) + { + // high not followed by low -> bad + goto InvalidData; + } + + pSrc++; + + ch = chd + (ch << 10) + + (0x10000 + - EncodingHelper.LowSurrogateStart + - (EncodingHelper.HighSurrogateStart << 10)); + + *pTarget = (byte)(unchecked((sbyte)0xF0) | (ch >> 18)); + // pStop - this byte is compensated by the second surrogate character + // 2 input chars require 4 output bytes. 2 have been anticipated already + // and 2 more will be accounted for by the 2 pStop-- calls below. + pTarget++; + + chd = unchecked((sbyte)0x80) | (ch >> 12) & 0x3F; + } + *pTarget = (byte)chd; + pStop--; // 3 byte sequence for 1 char, so need pStop-- and the one below too. + pTarget++; + + chd = unchecked((sbyte)0x80) | (ch >> 6) & 0x3F; + } + *pTarget = (byte)chd; + pStop--; // 2 byte sequence for 1 char so need pStop--. + + *(pTarget + 1) = (byte)(unchecked((sbyte)0x80) | ch & 0x3F); + // pStop - this byte is already included + + pTarget += 2; + } + while (pSrc < pStop); + + Debug.Assert(pTarget <= pAllocatedBufferEnd, "[UTF8Encoding.GetBytes]pTarget <= pAllocatedBufferEnd"); + } + + while (pSrc < pEnd) + { + // SLOWLOOP: does all range checks, handles all special cases, but it is slow + + // read next char. The JIT optimization seems to be getting confused when + // compiling "ch = *pSrc++;", so rather use "ch = *pSrc; pSrc++;" instead + int ch = *pSrc; + pSrc++; + + if (ch <= 0x7F) + { + if (pAllocatedBufferEnd - pTarget <= 0) + goto DestinationFull; + + *pTarget = (byte)ch; + pTarget++; + continue; + } + + int chd; + if (ch <= 0x7FF) + { + if (pAllocatedBufferEnd - pTarget <= 1) + goto DestinationFull; + + // 2 byte encoding + chd = unchecked((sbyte)0xC0) | (ch >> 6); + } + else + { + // if (!IsLowSurrogate(ch) && !IsHighSurrogate(ch)) + if (!EncodingHelper.InRange(ch, EncodingHelper.HighSurrogateStart, EncodingHelper.LowSurrogateEnd)) + { + if (pAllocatedBufferEnd - pTarget <= 2) + goto DestinationFull; + + // 3 byte encoding + chd = unchecked((sbyte)0xE0) | (ch >> 12); + } + else + { + if (pAllocatedBufferEnd - pTarget <= 3) + goto DestinationFull; + + // 4 byte encoding - high surrogate + low surrogate + // if (!IsHighSurrogate(ch)) + if (ch > EncodingHelper.HighSurrogateEnd) + { + // low without high -> bad + goto InvalidData; + } + + if (pSrc >= pEnd) + goto NeedMoreData; + + chd = *pSrc; + + // if (!IsLowSurrogate(chd)) { + if (!EncodingHelper.InRange(chd, EncodingHelper.LowSurrogateStart, EncodingHelper.LowSurrogateEnd)) + { + // high not followed by low -> bad + goto InvalidData; + } + + pSrc++; + + ch = chd + (ch << 10) + + (0x10000 + - EncodingHelper.LowSurrogateStart + - (EncodingHelper.HighSurrogateStart << 10)); + + *pTarget = (byte)(unchecked((sbyte)0xF0) | (ch >> 18)); + pTarget++; + + chd = unchecked((sbyte)0x80) | (ch >> 12) & 0x3F; + } + *pTarget = (byte)chd; + pTarget++; + + chd = unchecked((sbyte)0x80) | (ch >> 6) & 0x3F; + } + + *pTarget = (byte)chd; + *(pTarget + 1) = (byte)(unchecked((sbyte)0x80) | ch & 0x3F); + + pTarget += 2; + } + + bytesConsumed = (int)((byte*)pSrc - chars); + bytesWritten = (int)(pTarget - bytes); + return TransformationStatus.Done; + + InvalidData: + bytesConsumed = (int)((byte*)(pSrc - 1) - chars); + bytesWritten = (int)(pTarget - bytes); + return TransformationStatus.InvalidData; + + DestinationFull: + bytesConsumed = (int)((byte*)(pSrc - 1) - chars); + bytesWritten = (int)(pTarget - bytes); + return TransformationStatus.DestinationTooSmall; + + NeedMoreData: + bytesConsumed = (int)((byte*)(pSrc - 1) - chars); + bytesWritten = (int)(pTarget - bytes); + return TransformationStatus.NeedMoreSourceData; + } + } + + #endregion UTF-8 Conversions + + #region UTF-32 Conversions + + /// + /// Calculates the byte count needed to encode the UTF-16 bytes from the specified UTF-32 sequence. + /// + /// This method will consume as many of the input bytes as possible. + /// + /// A span containing a sequence of UTF-32 bytes. + /// On exit, contains the number of bytes required for encoding from the . + /// A value representing the expected state of the conversion. + public static TransformationStatus FromUtf32Length(ReadOnlySpan source, out int bytesNeeded) + => Utf32.ToUtf16Length(source, out bytesNeeded); + + /// + /// Converts a span containing a sequence of UTF-32 bytes into UTF-16 bytes. + /// + /// This method will consume as many of the input bytes as possible. + /// + /// On successful exit, the entire input was consumed and encoded successfully. In this case, will be + /// equal to the length of the and will equal the total number of bytes written to + /// the . + /// + /// A span containing a sequence of UTF-32 bytes. + /// A span to write the UTF-16 bytes into. + /// On exit, contains the number of bytes that were consumed from the . + /// On exit, contains the number of bytes written to + /// A value representing the state of the conversion. + public static TransformationStatus FromUtf32(ReadOnlySpan source, Span destination, out int bytesConsumed, out int bytesWritten) + => Utf32.ToUtf16(source, destination, out bytesConsumed, out bytesWritten); + + /// + /// Calculates the byte count needed to encode the UTF-32 bytes from the specified UTF-16 sequence. + /// + /// This method will consume as many of the input bytes as possible. + /// + /// A span containing a sequence of UTF-16 bytes. + /// On exit, contains the number of bytes required for encoding from the . + /// A value representing the expected state of the conversion. + public static TransformationStatus ToUtf32Length(ReadOnlySpan source, out int bytesNeeded) + { + bytesNeeded = 0; + + ref byte src = ref source.DangerousGetPinnableReference(); + int srcLength = source.Length; + int srcIndex = 0; + + while (srcLength - srcIndex >= sizeof(char)) + { + uint codePoint = Unsafe.As(ref Unsafe.Add(ref src, srcIndex)); + if (EncodingHelper.IsSurrogate(codePoint)) + { + if (!EncodingHelper.IsHighSurrogate(codePoint)) + return TransformationStatus.InvalidData; + + if (srcLength - srcIndex < sizeof(char) * 2) + return TransformationStatus.NeedMoreSourceData; + + uint lowSurrogate = Unsafe.As(ref Unsafe.Add(ref src, srcIndex + 2)); + if (!EncodingHelper.IsLowSurrogate(lowSurrogate)) + return TransformationStatus.InvalidData; + + srcIndex += 2; + } + + srcIndex += 2; + bytesNeeded += 4; + } + + return srcIndex < srcLength ? TransformationStatus.NeedMoreSourceData : TransformationStatus.Done; + } + + /// + /// Converts a span containing a sequence of UTF-16 bytes into UTF-32 bytes. + /// + /// This method will consume as many of the input bytes as possible. + /// + /// On successful exit, the entire input was consumed and encoded successfully. In this case, will be + /// equal to the length of the and will equal the total number of bytes written to + /// the . + /// + /// A span containing a sequence of UTF-16 bytes. + /// A span to write the UTF-32 bytes into. + /// On exit, contains the number of bytes that were consumed from the . + /// On exit, contains the number of bytes written to + /// A value representing the state of the conversion. + public static TransformationStatus ToUtf32(ReadOnlySpan source, Span destination, out int bytesConsumed, out int bytesWritten) + { + bytesConsumed = 0; + bytesWritten = 0; + + ref byte src = ref source.DangerousGetPinnableReference(); + int srcLength = source.Length; + + ref byte dst = ref destination.DangerousGetPinnableReference(); + int dstLength = destination.Length; + + while (srcLength - bytesConsumed >= sizeof(char)) + { + if (dstLength - bytesWritten < sizeof(uint)) + return TransformationStatus.DestinationTooSmall; + + uint codePoint = Unsafe.As(ref Unsafe.Add(ref src, bytesConsumed)); + if (EncodingHelper.IsSurrogate(codePoint)) + { + if (!EncodingHelper.IsHighSurrogate(codePoint)) + return TransformationStatus.InvalidData; + + if (srcLength - bytesConsumed < sizeof(char) * 2) + return TransformationStatus.NeedMoreSourceData; + + uint lowSurrogate = Unsafe.As(ref Unsafe.Add(ref src, bytesConsumed + 2)); + if (!EncodingHelper.IsLowSurrogate(lowSurrogate)) + return TransformationStatus.InvalidData; + + codePoint -= EncodingHelper.HighSurrogateStart; + lowSurrogate -= EncodingHelper.LowSurrogateStart; + codePoint = ((codePoint << 10) | lowSurrogate) + 0x010000u; + bytesConsumed += 2; + } + + Unsafe.As(ref Unsafe.Add(ref dst, bytesWritten)) = codePoint; + bytesConsumed += 2; + bytesWritten += 4; + } + + return bytesConsumed < srcLength ? TransformationStatus.NeedMoreSourceData : TransformationStatus.Done; + } + + #endregion UTF-32 Conversions + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Encoding/Encoders.Utf32.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Encoding/Encoders.Utf32.cs new file mode 100644 index 000000000..7d0a6e1d1 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Encoding/Encoders.Utf32.cs @@ -0,0 +1,258 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Buffers; +using System.Runtime.CompilerServices; + +namespace System.Text.Encoders +{ + public static class Utf32 + { + #region UTF-8 Conversions + + /// + /// Calculates the byte count needed to encode the UTF-32 bytes from the specified UTF-8 sequence. + /// + /// This method will consume as many of the input bytes as possible. + /// + /// A span containing a sequence of UTF-8 bytes. + /// On exit, contains the number of bytes required for encoding from the . + /// A value representing the expected state of the conversion. + public static TransformationStatus FromUtf8Length(ReadOnlySpan source, out int bytesNeeded) + => Utf8.ToUtf32Length(source, out bytesNeeded); + + /// + /// Converts a span containing a sequence of UTF-8 bytes into UTF-32 bytes. + /// + /// This method will consume as many of the input bytes as possible. + /// + /// On successful exit, the entire input was consumed and encoded successfully. In this case, will be + /// equal to the length of the and will equal the total number of bytes written to + /// the . + /// + /// A span containing a sequence of UTF-8 bytes. + /// A span to write the UTF-32 bytes into. + /// On exit, contains the number of bytes that were consumed from the . + /// On exit, contains the number of bytes written to + /// A value representing the state of the conversion. + public static TransformationStatus FromUtf8(ReadOnlySpan source, Span destination, out int bytesConsumed, out int bytesWritten) + => Utf8.ToUtf32(source, destination, out bytesConsumed, out bytesWritten); + + /// + /// Calculates the byte count needed to encode the UTF-8 bytes from the specified UTF-32 sequence. + /// + /// This method will consume as many of the input bytes as possible. + /// + /// A span containing a sequence of UTF-32 bytes. + /// On exit, contains the number of bytes required for encoding from the . + /// A value representing the expected state of the conversion. + public static TransformationStatus ToUtf8Length(ReadOnlySpan source, out int bytesNeeded) + { + bytesNeeded = 0; + + ref uint utf32 = ref Unsafe.As(ref source.DangerousGetPinnableReference()); + int utf32Length = source.Length >> 2; // byte => uint count + + for (int i = 0; i < utf32Length; i++) + { + uint codePoint = Unsafe.Add(ref utf32, i); + if (!EncodingHelper.IsSupportedCodePoint(codePoint)) + return TransformationStatus.InvalidData; + + bytesNeeded += EncodingHelper.GetUtf8EncodedBytes(codePoint); + } + + if (utf32Length << 2 != source.Length) + return TransformationStatus.NeedMoreSourceData; + + return TransformationStatus.Done; + } + + /// + /// Converts a span containing a sequence of UTF-32 bytes into UTF-8 bytes. + /// + /// This method will consume as many of the input bytes as possible. + /// + /// On successful exit, the entire input was consumed and encoded successfully. In this case, will be + /// equal to the length of the and will equal the total number of bytes written to + /// the . + /// + /// A span containing a sequence of UTF-32 bytes. + /// A span to write the UTF-8 bytes into. + /// On exit, contains the number of bytes that were consumed from the . + /// On exit, contains the number of bytes written to + /// A value representing the state of the conversion. + public static TransformationStatus ToUtf8(ReadOnlySpan source, Span destination, out int bytesConsumed, out int bytesWritten) + { + bytesConsumed = 0; + bytesWritten = 0; + + ref byte src = ref source.DangerousGetPinnableReference(); + int srcLength = source.Length; + + ref byte dst = ref destination.DangerousGetPinnableReference(); + int dstLength = destination.Length; + + while (srcLength - bytesConsumed >= sizeof(uint)) + { + uint codePoint = Unsafe.As(ref Unsafe.Add(ref src, bytesConsumed)); + if (!EncodingHelper.IsSupportedCodePoint(codePoint)) + return TransformationStatus.InvalidData; + + int bytesNeeded = EncodingHelper.GetUtf8EncodedBytes(codePoint); + if (dstLength - bytesWritten < bytesNeeded) + return TransformationStatus.DestinationTooSmall; + + switch (bytesNeeded) + { + case 1: + Unsafe.Add(ref dst, bytesWritten) = (byte)(EncodingHelper.b0111_1111U & codePoint); + break; + + case 2: + Unsafe.Add(ref dst, bytesWritten) = (byte)(((codePoint >> 6) & EncodingHelper.b0001_1111U) | EncodingHelper.b1100_0000U); + Unsafe.Add(ref dst, bytesWritten + 1) = (byte)((codePoint & EncodingHelper.b0011_1111U) | EncodingHelper.b1000_0000U); + break; + + case 3: + Unsafe.Add(ref dst, bytesWritten) = (byte)(((codePoint >> 12) & EncodingHelper.b0000_1111U) | EncodingHelper.b1110_0000U); + Unsafe.Add(ref dst, bytesWritten + 1) = (byte)(((codePoint >> 6) & EncodingHelper.b0011_1111U) | EncodingHelper.b1000_0000U); + Unsafe.Add(ref dst, bytesWritten + 2) = (byte)((codePoint & EncodingHelper.b0011_1111U) | EncodingHelper.b1000_0000U); + break; + + case 4: + Unsafe.Add(ref dst, bytesWritten) = (byte)(((codePoint >> 18) & EncodingHelper.b0000_0111U) | EncodingHelper.b1111_0000U); + Unsafe.Add(ref dst, bytesWritten + 1) = (byte)(((codePoint >> 12) & EncodingHelper.b0011_1111U) | EncodingHelper.b1000_0000U); + Unsafe.Add(ref dst, bytesWritten + 2) = (byte)(((codePoint >> 6) & EncodingHelper.b0011_1111U) | EncodingHelper.b1000_0000U); + Unsafe.Add(ref dst, bytesWritten + 3) = (byte)((codePoint & EncodingHelper.b0011_1111U) | EncodingHelper.b1000_0000U); + break; + + default: + return TransformationStatus.InvalidData; + } + + bytesConsumed += 4; + bytesWritten += bytesNeeded; + } + + return bytesConsumed < srcLength ? TransformationStatus.NeedMoreSourceData : TransformationStatus.Done; + } + + #endregion UTF-8 Conversions + + #region UTF-16 Conversions + + /// + /// Calculates the byte count needed to encode the UTF-32 bytes from the specified UTF-16 sequence. + /// + /// This method will consume as many of the input bytes as possible. + /// + /// A span containing a sequence of UTF-16 bytes. + /// On exit, contains the number of bytes required for encoding from the . + /// A value representing the expected state of the conversion. + public static TransformationStatus FromUtf16Length(ReadOnlySpan source, out int bytesNeeded) + => Utf16.ToUtf32Length(source, out bytesNeeded); + + /// + /// Converts a span containing a sequence of UTF-16 bytes into UTF-32 bytes. + /// + /// This method will consume as many of the input bytes as possible. + /// + /// On successful exit, the entire input was consumed and encoded successfully. In this case, will be + /// equal to the length of the and will equal the total number of bytes written to + /// the . + /// + /// A span containing a sequence of UTF-16 bytes. + /// A span to write the UTF-32 bytes into. + /// On exit, contains the number of bytes that were consumed from the . + /// On exit, contains the number of bytes written to + /// A value representing the state of the conversion. + public static TransformationStatus FromUtf16(ReadOnlySpan source, Span destination, out int bytesConsumed, out int bytesWritten) + => Utf16.ToUtf32(source, destination, out bytesConsumed, out bytesWritten); + + /// + /// Calculates the byte count needed to encode the UTF-16 bytes from the specified UTF-32 sequence. + /// + /// This method will consume as many of the input bytes as possible. + /// + /// A span containing a sequence of UTF-32 bytes. + /// On exit, contains the number of bytes required for encoding from the . + /// A value representing the expected state of the conversion. + public static TransformationStatus ToUtf16Length(ReadOnlySpan source, out int bytesNeeded) + { + int index = 0; + int length = source.Length; + ref byte src = ref source.DangerousGetPinnableReference(); + + bytesNeeded = 0; + + while (length - index >= 4) + { + ref uint codePoint = ref Unsafe.As(ref Unsafe.Add(ref src, index)); + + if (!EncodingHelper.IsSupportedCodePoint(codePoint)) + return TransformationStatus.InvalidData; + + bytesNeeded += EncodingHelper.IsBmp(codePoint) ? 2 : 4; + index += 4; + } + + return index < length ? TransformationStatus.NeedMoreSourceData : TransformationStatus.Done; + } + + /// + /// Converts a span containing a sequence of UTF-32 bytes into UTF-16 bytes. + /// + /// This method will consume as many of the input bytes as possible. + /// + /// On successful exit, the entire input was consumed and encoded successfully. In this case, will be + /// equal to the length of the and will equal the total number of bytes written to + /// the . + /// + /// A span containing a sequence of UTF-32 bytes. + /// A span to write the UTF-16 bytes into. + /// On exit, contains the number of bytes that were consumed from the . + /// On exit, contains the number of bytes written to + /// A value representing the state of the conversion. + public static TransformationStatus ToUtf16(ReadOnlySpan source, Span destination, out int bytesConsumed, out int bytesWritten) + { + ref byte src = ref source.DangerousGetPinnableReference(); + ref byte dst = ref destination.DangerousGetPinnableReference(); + int srcLength = source.Length; + int dstLength = destination.Length; + + bytesConsumed = 0; + bytesWritten = 0; + + while (srcLength - bytesConsumed >= sizeof(uint)) + { + ref uint codePoint = ref Unsafe.As(ref Unsafe.Add(ref src, bytesConsumed)); + + if (!EncodingHelper.IsSupportedCodePoint(codePoint)) + return TransformationStatus.InvalidData; + + int written = EncodingHelper.IsBmp(codePoint) ? 2 : 4; + if (dstLength - bytesWritten < written) + return TransformationStatus.DestinationTooSmall; + + unchecked + { + if (written == 2) + Unsafe.As(ref Unsafe.Add(ref dst, bytesWritten)) = (char)codePoint; + else + { + Unsafe.As(ref Unsafe.Add(ref dst, bytesWritten)) = (char)(((codePoint - 0x010000u) >> 10) + EncodingHelper.HighSurrogateStart); + Unsafe.As(ref Unsafe.Add(ref dst, bytesWritten + 2)) = (char)((codePoint & 0x3FF) + EncodingHelper.LowSurrogateStart); + } + } + + bytesWritten += written; + bytesConsumed += 4; + } + + return bytesConsumed < srcLength ? TransformationStatus.NeedMoreSourceData : TransformationStatus.Done; + } + + #endregion UTF-16 Conversions + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Encoding/Encoders.Utf8.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Encoding/Encoders.Utf8.cs new file mode 100644 index 000000000..7f5d395d1 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Encoding/Encoders.Utf8.cs @@ -0,0 +1,838 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Buffers; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace System.Text.Encoders +{ + public static class Utf8 + { + #region UTF-16 Conversions + + /// + /// Calculates the byte count needed to encode the UTF-8 bytes from the specified UTF-16 sequence. + /// + /// This method will consume as many of the input bytes as possible. + /// + /// A span containing a sequence of UTF-16 bytes. + /// On exit, contains the number of bytes required for encoding from the . + /// A value representing the expected state of the conversion. + public static TransformationStatus FromUtf16Length(ReadOnlySpan source, out int bytesNeeded) + => Utf16.ToUtf8Length(source, out bytesNeeded); + + /// + /// Converts a span containing a sequence of UTF-16 bytes into UTF-8 bytes. + /// + /// This method will consume as many of the input bytes as possible. + /// + /// On successful exit, the entire input was consumed and encoded successfully. In this case, will be + /// equal to the length of the and will equal the total number of bytes written to + /// the . + /// + /// A span containing a sequence of UTF-16 bytes. + /// A span to write the UTF-8 bytes into. + /// On exit, contains the number of bytes that were consumed from the . + /// On exit, contains the number of bytes written to + /// A value representing the state of the conversion. + public static TransformationStatus FromUtf16(ReadOnlySpan source, Span destination, out int bytesConsumed, out int bytesWritten) + => Utf16.ToUtf8(source, destination, out bytesConsumed, out bytesWritten); + + /// + /// Calculates the byte count needed to encode the UTF-16 bytes from the specified UTF-8 sequence. + /// + /// This method will consume as many of the input bytes as possible. + /// + /// A span containing a sequence of UTF-8 bytes. + /// On exit, contains the number of bytes required for encoding from the . + /// A value representing the expected state of the conversion. + public unsafe static TransformationStatus ToUtf16Length(ReadOnlySpan source, out int bytesNeeded) + { + fixed (byte* pUtf8 = &source.DangerousGetPinnableReference()) + { + byte* pSrc = pUtf8; + byte* pSrcEnd = pSrc + source.Length; + + bytesNeeded = 0; + + int ch = 0; + while (pSrc < pSrcEnd) + { + int availableBytes = EncodingHelper.PtrDiff(pSrcEnd, pSrc); + + // don't fall into the fast decoding loop if we don't have enough bytes + if (availableBytes <= 13) + { + // try to get over the remainder of the ascii characters fast though + byte* pLocalEnd = pSrc + availableBytes; + while (pSrc < pLocalEnd) + { + ch = *pSrc; + pSrc++; + + if (ch > 0x7F) + goto LongCodeSlow; + + bytesNeeded++; + } + + // we are done + break; + } + + // To compute the upper bound, assume that all characters are ASCII characters at this point, + // the boundary will be decreased for every non-ASCII character we encounter + // Also, we need 7 chars reserve for the unrolled ansi decoding loop and for decoding of multibyte sequences + byte* pStop = pSrc + availableBytes - 7; + + // Fast loop + while (pSrc < pStop) + { + ch = *pSrc; + pSrc++; + + if (ch > 0x7F) + goto LongCode; + + bytesNeeded++; + + // 2-byte align + if ((unchecked((int)pSrc) & 0x1) != 0) + { + ch = *pSrc; + pSrc++; + + if (ch > 0x7F) + goto LongCode; + + bytesNeeded++; + } + + // 4-byte align + if ((unchecked((int)pSrc) & 0x2) != 0) + { + ch = *(ushort*)pSrc; + if ((ch & 0x8080) != 0) + goto LongCodeWithMask16; + pSrc += 2; + bytesNeeded += 2; + } + + // Run 8 characters at a time! + while (pSrc < pStop) + { + ch = *(int*)pSrc; + int chb = *(int*)(pSrc + 4); + if (((ch | chb) & unchecked((int)0x80808080)) != 0) + goto LongCodeWithMask32; + pSrc += 8; + bytesNeeded += 8; + } + + break; + +#if BIGENDIAN + LongCodeWithMask32: + // be careful about the sign extension + ch = (int)(((uint)ch) >> 16); + LongCodeWithMask16: + ch = (int)(((uint)ch) >> 8); +#else // BIGENDIAN + LongCodeWithMask32: + LongCodeWithMask16: + ch &= 0xFF; +#endif // BIGENDIAN + pSrc++; + if (ch <= 0x7F) + { + bytesNeeded++; + continue; + } + + LongCode: + int chc = *pSrc; + pSrc++; + + // Bit 6 should be 0, and trailing byte should be 10vvvvvv + if ((ch & 0x40) == 0 || (chc & unchecked((sbyte)0xC0)) != 0x80) + goto InvalidData; + + chc &= 0x3F; + + if ((ch & 0x20) != 0) + { + // Handle 3 or 4 byte encoding. + + // Fold the first 2 bytes together + chc |= (ch & 0x0F) << 6; + + if ((ch & 0x10) != 0) + { + // 4 byte - surrogate pair + ch = *pSrc; + + // Bit 4 should be zero + the surrogate should be in the range 0x000000 - 0x10FFFF + // and the trailing byte should be 10vvvvvv + if (!EncodingHelper.InRange(chc >> 4, 0x01, 0x10) || (ch & unchecked((sbyte)0xC0)) != 0x80) + goto InvalidData; + + // Merge 3rd byte then read the last byte + chc = (chc << 6) | (ch & 0x3F); + ch = *(pSrc + 1); + + // The last trailing byte still holds the form 10vvvvvv + if ((ch & unchecked((sbyte)0xC0)) != 0x80) + goto InvalidData; + + pSrc += 2; + ch = (chc << 6) | (ch & 0x3F); + + bytesNeeded++; + + ch = (ch & 0x3FF) + unchecked((short)(EncodingHelper.LowSurrogateStart)); + } + else + { + // 3 byte encoding + ch = *pSrc; + + // Check for non-shortest form of 3 byte sequence + // No surrogates + // Trailing byte must be in the form 10vvvvvv + if ((chc & (0x1F << 5)) == 0 || + (chc & (0xF800 >> 6)) == (0xD800 >> 6) || + (ch & unchecked((sbyte)0xC0)) != 0x80) + goto InvalidData; + + pSrc++; + ch = (chc << 6) | (ch & 0x3F); + } + + // extra byte, we're already planning 2 chars for 2 of these bytes, + // but the big loop is testing the target against pStop, so we need + // to subtract 2 more or we risk overrunning the input. Subtract + // one here and one below. + pStop--; + } + else + { + // 2 byte encoding + ch &= 0x1F; + + // Check for non-shortest form + if (ch <= 1) + goto InvalidData; + + ch = (ch << 6) | chc; + } + + bytesNeeded++; + + // extra byte, we're only expecting 1 char for each of these 2 bytes, + // but the loop is testing the target (not source) against pStop. + // subtract an extra count from pStop so that we don't overrun the input. + pStop--; + } + + continue; + + LongCodeSlow: + if (pSrc >= pSrcEnd) + { + // This is a special case where hit the end of the buffer but are in the middle + // of decoding a long code. The error exit thinks we have read 2 extra bytes already, + // so we add +1 to pSrc to get the count correct for the bytes consumed value. + pSrc++; + goto NeedMoreData; + } + + int chd = *pSrc; + pSrc++; + + // Bit 6 should be 0, and trailing byte should be 10vvvvvv + if ((ch & 0x40) == 0 || (chd & unchecked((sbyte)0xC0)) != 0x80) + goto InvalidData; + + chd &= 0x3F; + + if ((ch & 0x20) != 0) + { + // Handle 3 or 4 byte encoding. + + // Fold the first 2 bytes together + chd |= (ch & 0x0F) << 6; + + if ((ch & 0x10) != 0) + { + // 4 byte - surrogate pair + // We need 2 more bytes + if (pSrc >= pSrcEnd - 1) + goto NeedMoreData; + + ch = *pSrc; + + // Bit 4 should be zero + the surrogate should be in the range 0x000000 - 0x10FFFF + // and the trailing byte should be 10vvvvvv + if (!EncodingHelper.InRange(chd >> 4, 0x01, 0x10) || (ch & unchecked((sbyte)0xC0)) != 0x80) + goto InvalidData; + + // Merge 3rd byte then read the last byte + chd = (chd << 6) | (ch & 0x3F); + ch = *(pSrc + 1); + + // The last trailing byte still holds the form 10vvvvvv + // We only know for sure we have room for one more char, but we need an extra now. + if ((ch & unchecked((sbyte)0xC0)) != 0x80) + goto InvalidData; + + pSrc += 2; + ch = (chd << 6) | (ch & 0x3F); + + bytesNeeded++; + + ch = (ch & 0x3FF) + unchecked((short)(EncodingHelper.LowSurrogateStart)); + } + else + { + // 3 byte encoding + if (pSrc >= pSrcEnd) + goto NeedMoreData; + + ch = *pSrc; + + // Check for non-shortest form of 3 byte sequence + // No surrogates + // Trailing byte must be in the form 10vvvvvv + if ((chd & (0x1F << 5)) == 0 || + (chd & (0xF800 >> 6)) == (0xD800 >> 6) || + (ch & unchecked((sbyte)0xC0)) != 0x80) + goto InvalidData; + + pSrc++; + ch = (chd << 6) | (ch & 0x3F); + } + } + else + { + // 2 byte encoding + ch &= 0x1F; + + // Check for non-shortest form + if (ch <= 1) + goto InvalidData; + + ch = (ch << 6) | chd; + } + + bytesNeeded++; + } + + bytesNeeded <<= 1; // Count we have is chars, double for bytes. + return EncodingHelper.PtrDiff(pSrcEnd, pSrc) == 0 ? TransformationStatus.Done : TransformationStatus.DestinationTooSmall; + + NeedMoreData: + bytesNeeded <<= 1; // Count we have is chars, double for bytes. + return TransformationStatus.NeedMoreSourceData; + + InvalidData: + bytesNeeded <<= 1; // Count we have is chars, double for bytes. + return TransformationStatus.InvalidData; + } + } + + /// + /// Converts a span containing a sequence of UTF-8 bytes into UTF-16 bytes. + /// + /// This method will consume as many of the input bytes as possible. + /// + /// On successful exit, the entire input was consumed and encoded successfully. In this case, will be + /// equal to the length of the and will equal the total number of bytes written to + /// the . + /// + /// A span containing a sequence of UTF-8 bytes. + /// A span to write the UTF-16 bytes into. + /// On exit, contains the number of bytes that were consumed from the . + /// On exit, contains the number of bytes written to + /// A value representing the state of the conversion. + public unsafe static TransformationStatus ToUtf16(ReadOnlySpan source, Span destination, out int bytesConsumed, out int bytesWritten) + { + fixed (byte* pUtf8 = &source.DangerousGetPinnableReference()) + fixed (byte* pUtf16 = &destination.DangerousGetPinnableReference()) + { + byte* pSrc = pUtf8; + byte* pSrcEnd = pSrc + source.Length; + char* pDst = (char*)pUtf16; + char* pDstEnd = pDst + (destination.Length >> 1); // Conversion from bytes to chars - div by sizeof(char) + + int ch = 0; + while (pSrc < pSrcEnd && pDst < pDstEnd) + { + // we may need as many as 1 character per byte, so reduce the byte count if necessary. + // If availableChars is too small, pStop will be before pTarget and we won't do fast loop. + int availableChars = EncodingHelper.PtrDiff(pDstEnd, pDst); + int availableBytes = EncodingHelper.PtrDiff(pSrcEnd, pSrc); + + if (availableChars < availableBytes) + availableBytes = availableChars; + + // don't fall into the fast decoding loop if we don't have enough bytes + if (availableBytes <= 13) + { + // try to get over the remainder of the ascii characters fast though + byte* pLocalEnd = pSrc + availableBytes; + while (pSrc < pLocalEnd) + { + ch = *pSrc; + pSrc++; + + if (ch > 0x7F) + goto LongCodeSlow; + + *pDst = (char)ch; + pDst++; + } + + // we are done + break; + } + + // To compute the upper bound, assume that all characters are ASCII characters at this point, + // the boundary will be decreased for every non-ASCII character we encounter + // Also, we need 7 chars reserve for the unrolled ansi decoding loop and for decoding of multibyte sequences + char* pStop = pDst + availableBytes - 7; + + // Fast loop + while (pDst < pStop) + { + ch = *pSrc; + pSrc++; + + if (ch > 0x7F) + goto LongCode; + + *pDst = (char)ch; + pDst++; + + // 2-byte align + if ((unchecked((int)pSrc) & 0x1) != 0) + { + ch = *pSrc; + pSrc++; + + if (ch > 0x7F) + goto LongCode; + + *pDst = (char)ch; + pDst++; + } + + // 4-byte align + if ((unchecked((int)pSrc) & 0x2) != 0) + { + ch = *(ushort*)pSrc; + if ((ch & 0x8080) != 0) + goto LongCodeWithMask16; + + // Unfortunately, endianness sensitive +#if BIGENDIAN + *pDst = (char)((ch >> 8) & 0x7F); + pSrc += 2; + *(pDst + 1) = (char)(ch & 0x7F); + pDst += 2; +#else // BIGENDIAN + *pDst = (char)(ch & 0x7F); + pSrc += 2; + *(pDst + 1) = (char)((ch >> 8) & 0x7F); + pDst += 2; +#endif // BIGENDIAN + } + + // Run 8 characters at a time! + while (pDst < pStop) + { + ch = *(int*)pSrc; + int chb = *(int*)(pSrc + 4); + if (((ch | chb) & unchecked((int)0x80808080)) != 0) + goto LongCodeWithMask32; + + // Unfortunately, endianness sensitive +#if BIGENDIAN + *pDst = (char)((ch >> 24) & 0x7F); + *(pDst+1) = (char)((ch >> 16) & 0x7F); + *(pDst+2) = (char)((ch >> 8) & 0x7F); + *(pDst+3) = (char)(ch & 0x7F); + pSrc += 8; + *(pDst+4) = (char)((chb >> 24) & 0x7F); + *(pDst+5) = (char)((chb >> 16) & 0x7F); + *(pDst+6) = (char)((chb >> 8) & 0x7F); + *(pDst+7) = (char)(chb & 0x7F); + pDst += 8; +#else // BIGENDIAN + *pDst = (char)(ch & 0x7F); + *(pDst + 1) = (char)((ch >> 8) & 0x7F); + *(pDst + 2) = (char)((ch >> 16) & 0x7F); + *(pDst + 3) = (char)((ch >> 24) & 0x7F); + pSrc += 8; + *(pDst + 4) = (char)(chb & 0x7F); + *(pDst + 5) = (char)((chb >> 8) & 0x7F); + *(pDst + 6) = (char)((chb >> 16) & 0x7F); + *(pDst + 7) = (char)((chb >> 24) & 0x7F); + pDst += 8; +#endif // BIGENDIAN + } + + break; + +#if BIGENDIAN + LongCodeWithMask32: + // be careful about the sign extension + ch = (int)(((uint)ch) >> 16); + LongCodeWithMask16: + ch = (int)(((uint)ch) >> 8); +#else // BIGENDIAN + LongCodeWithMask32: + LongCodeWithMask16: + ch &= 0xFF; +#endif // BIGENDIAN + pSrc++; + if (ch <= 0x7F) + { + *pDst = (char)ch; + pDst++; + continue; + } + + LongCode: + int chc = *pSrc; + pSrc++; + + // Bit 6 should be 0, and trailing byte should be 10vvvvvv + if ((ch & 0x40) == 0 || (chc & unchecked((sbyte)0xC0)) != 0x80) + goto InvalidData; + + chc &= 0x3F; + + if ((ch & 0x20) != 0) + { + // Handle 3 or 4 byte encoding. + + // Fold the first 2 bytes together + chc |= (ch & 0x0F) << 6; + + if ((ch & 0x10) != 0) + { + // 4 byte - surrogate pair + ch = *pSrc; + + // Bit 4 should be zero + the surrogate should be in the range 0x000000 - 0x10FFFF + // and the trailing byte should be 10vvvvvv + if (!EncodingHelper.InRange(chc >> 4, 0x01, 0x10) || (ch & unchecked((sbyte)0xC0)) != 0x80) + goto InvalidData; + + // Merge 3rd byte then read the last byte + chc = (chc << 6) | (ch & 0x3F); + ch = *(pSrc + 1); + + // The last trailing byte still holds the form 10vvvvvv + if ((ch & unchecked((sbyte)0xC0)) != 0x80) + goto InvalidData; + + pSrc += 2; + ch = (chc << 6) | (ch & 0x3F); + + *pDst = (char)(((ch >> 10) & 0x7FF) + unchecked((short)(EncodingHelper.HighSurrogateStart - (0x10000 >> 10)))); + pDst++; + + ch = (ch & 0x3FF) + unchecked((short)(EncodingHelper.LowSurrogateStart)); + } + else + { + // 3 byte encoding + ch = *pSrc; + + // Check for non-shortest form of 3 byte sequence + // No surrogates + // Trailing byte must be in the form 10vvvvvv + if ((chc & (0x1F << 5)) == 0 || + (chc & (0xF800 >> 6)) == (0xD800 >> 6) || + (ch & unchecked((sbyte)0xC0)) != 0x80) + goto InvalidData; + + pSrc++; + ch = (chc << 6) | (ch & 0x3F); + } + + // extra byte, we're already planning 2 chars for 2 of these bytes, + // but the big loop is testing the target against pStop, so we need + // to subtract 2 more or we risk overrunning the input. Subtract + // one here and one below. + pStop--; + } + else + { + // 2 byte encoding + ch &= 0x1F; + + // Check for non-shortest form + if (ch <= 1) + goto InvalidData; + + ch = (ch << 6) | chc; + } + + *pDst = (char)ch; + pDst++; + + // extra byte, we're only expecting 1 char for each of these 2 bytes, + // but the loop is testing the target (not source) against pStop. + // subtract an extra count from pStop so that we don't overrun the input. + pStop--; + } + + continue; + + LongCodeSlow: + if (pSrc >= pSrcEnd) + { + // This is a special case where hit the end of the buffer but are in the middle + // of decoding a long code. The error exit thinks we have read 2 extra bytes already, + // so we add +1 to pSrc to get the count correct for the bytes consumed value. + pSrc++; + goto NeedMoreData; + } + + int chd = *pSrc; + pSrc++; + + // Bit 6 should be 0, and trailing byte should be 10vvvvvv + if ((ch & 0x40) == 0 || (chd & unchecked((sbyte)0xC0)) != 0x80) + goto InvalidData; + + chd &= 0x3F; + + if ((ch & 0x20) != 0) + { + // Handle 3 or 4 byte encoding. + + // Fold the first 2 bytes together + chd |= (ch & 0x0F) << 6; + + if ((ch & 0x10) != 0) + { + // 4 byte - surrogate pair + // We need 2 more bytes + if (pSrc >= pSrcEnd - 1) + goto NeedMoreData; + + ch = *pSrc; + + // Bit 4 should be zero + the surrogate should be in the range 0x000000 - 0x10FFFF + // and the trailing byte should be 10vvvvvv + if (!EncodingHelper.InRange(chd >> 4, 0x01, 0x10) || (ch & unchecked((sbyte)0xC0)) != 0x80) + goto InvalidData; + + // Merge 3rd byte then read the last byte + chd = (chd << 6) | (ch & 0x3F); + ch = *(pSrc + 1); + + // The last trailing byte still holds the form 10vvvvvv + // We only know for sure we have room for one more char, but we need an extra now. + if ((ch & unchecked((sbyte)0xC0)) != 0x80) + goto InvalidData; + + if (EncodingHelper.PtrDiff(pDstEnd, pDst) < 2) + goto DestinationFull; + + pSrc += 2; + ch = (chd << 6) | (ch & 0x3F); + + *pDst = (char)(((ch >> 10) & 0x7FF) + unchecked((short)(EncodingHelper.HighSurrogateStart - (0x10000 >> 10)))); + pDst++; + + ch = (ch & 0x3FF) + unchecked((short)(EncodingHelper.LowSurrogateStart)); + } + else + { + // 3 byte encoding + if (pSrc >= pSrcEnd) + goto NeedMoreData; + + ch = *pSrc; + + // Check for non-shortest form of 3 byte sequence + // No surrogates + // Trailing byte must be in the form 10vvvvvv + if ((chd & (0x1F << 5)) == 0 || + (chd & (0xF800 >> 6)) == (0xD800 >> 6) || + (ch & unchecked((sbyte)0xC0)) != 0x80) + goto InvalidData; + + pSrc++; + ch = (chd << 6) | (ch & 0x3F); + } + } + else + { + // 2 byte encoding + ch &= 0x1F; + + // Check for non-shortest form + if (ch <= 1) + goto InvalidData; + + ch = (ch << 6) | chd; + } + + *pDst = (char)ch; + pDst++; + } + + DestinationFull: + bytesConsumed = EncodingHelper.PtrDiff(pSrc, pUtf8); + bytesWritten = EncodingHelper.PtrDiff((byte*)pDst, pUtf16); + return EncodingHelper.PtrDiff(pSrcEnd, pSrc) == 0 ? TransformationStatus.Done : TransformationStatus.DestinationTooSmall; + + NeedMoreData: + bytesConsumed = EncodingHelper.PtrDiff(pSrc - 2, pUtf8); + bytesWritten = EncodingHelper.PtrDiff((byte*)pDst, pUtf16); + return TransformationStatus.NeedMoreSourceData; + + InvalidData: + bytesConsumed = EncodingHelper.PtrDiff(pSrc - 2, pUtf8); + bytesWritten = EncodingHelper.PtrDiff((byte*)pDst, pUtf16); + return TransformationStatus.InvalidData; + } + } + + #endregion UTF-16 Conversions + + #region UTF-32 Conversions + + /// + /// Calculates the byte count needed to encode the UTF-8 bytes from the specified UTF-32 sequence. + /// + /// This method will consume as many of the input bytes as possible. + /// + /// A span containing a sequence of UTF-32 bytes. + /// On exit, contains the number of bytes required for encoding from the . + /// A value representing the expected state of the conversion. + public static TransformationStatus FromUtf32Length(ReadOnlySpan source, out int bytesNeeded) + => Utf32.ToUtf8Length(source, out bytesNeeded); + + /// + /// Converts a span containing a sequence of UTF-32 bytes into UTF-8 bytes. + /// + /// This method will consume as many of the input bytes as possible. + /// + /// On successful exit, the entire input was consumed and encoded successfully. In this case, will be + /// equal to the length of the and will equal the total number of bytes written to + /// the . + /// + /// A span containing a sequence of UTF-32 bytes. + /// A span to write the UTF-8 bytes into. + /// On exit, contains the number of bytes that were consumed from the . + /// On exit, contains the number of bytes written to + /// A value representing the state of the conversion. + public static TransformationStatus FromUtf32(ReadOnlySpan source, Span destination, out int bytesConsumed, out int bytesWritten) + => Utf32.ToUtf8(source, destination, out bytesConsumed, out bytesWritten); + + /// + /// Calculates the byte count needed to encode the UTF-32 bytes from the specified UTF-8 sequence. + /// + /// This method will consume as many of the input bytes as possible. + /// + /// A span containing a sequence of UTF-8 bytes. + /// On exit, contains the number of bytes required for encoding from the . + /// A value representing the expected state of the conversion. + public static TransformationStatus ToUtf32Length(ReadOnlySpan source, out int bytesNeeded) + { + bytesNeeded = 0; + + int index = 0; + int length = source.Length; + ref byte src = ref source.DangerousGetPinnableReference(); + + while (index < length) + { + int count = EncodingHelper.GetUtf8DecodedBytes(Unsafe.Add(ref src, index)); + if (count == 0) + goto InvalidData; + if (length - index < count) + goto NeedMoreData; + + bytesNeeded += count; + } + + return index < length ? TransformationStatus.DestinationTooSmall : TransformationStatus.Done; + + InvalidData: + return TransformationStatus.InvalidData; + + NeedMoreData: + return TransformationStatus.NeedMoreSourceData; + } + + /// + /// Converts a span containing a sequence of UTF-8 bytes into UTF-32 bytes. + /// + /// This method will consume as many of the input bytes as possible. + /// + /// On successful exit, the entire input was consumed and encoded successfully. In this case, will be + /// equal to the length of the and will equal the total number of bytes written to + /// the . + /// + /// A span containing a sequence of UTF-8 bytes. + /// A span to write the UTF-32 bytes into. + /// On exit, contains the number of bytes that were consumed from the . + /// On exit, contains the number of bytes written to + /// A value representing the state of the conversion. + public static TransformationStatus ToUtf32(ReadOnlySpan source, Span destination, out int bytesConsumed, out int bytesWritten) + { + bytesConsumed = 0; + bytesWritten = 0; + + int srcLength = source.Length; + int dstLength = destination.Length; + ref byte src = ref source.DangerousGetPinnableReference(); + ref byte dst = ref destination.DangerousGetPinnableReference(); + + while (bytesConsumed < srcLength && bytesWritten < dstLength) + { + uint codePoint = Unsafe.Add(ref src, bytesConsumed); + + int byteCount = EncodingHelper.GetUtf8DecodedBytes((byte)codePoint); + if (byteCount == 0) + goto InvalidData; + if (srcLength - bytesConsumed < byteCount) + goto NeedMoreData; + + if (byteCount > 1) + codePoint &= (byte)(0x7F >> byteCount); + + for (var i = 1; i < byteCount; i++) + { + ref byte next = ref Unsafe.Add(ref src, bytesConsumed + i); + if ((next & EncodingHelper.b1100_0000U) != EncodingHelper.b1000_0000U) + goto InvalidData; + + codePoint = (codePoint << 6) | (uint)(EncodingHelper.b0011_1111U & next); + } + + Unsafe.As(ref Unsafe.Add(ref dst, bytesWritten)) = codePoint; + bytesWritten += 4; + bytesConsumed += byteCount; + } + + return bytesConsumed < srcLength ? TransformationStatus.DestinationTooSmall : TransformationStatus.Done; + + InvalidData: + return TransformationStatus.InvalidData; + + NeedMoreData: + return TransformationStatus.NeedMoreSourceData; + } + + #endregion UTF-32 Conversions + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Encoding/EncodingHelper.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Encoding/EncodingHelper.cs new file mode 100644 index 000000000..8a5a79279 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Encoding/EncodingHelper.cs @@ -0,0 +1,151 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; + +namespace System.Text +{ + internal static class EncodingHelper + { + #region Constants + + private const uint FirstNotSupportedCodePoint = 0x110000; // 17 * 2^16 + private const uint BasicMultilingualPlaneEndMarker = 0x10000; + + // TODO: Make this immutable and let them be strong typed + // http://unicode.org/cldr/utility/list-unicodeset.jsp?a=\p{whitespace}&g=&i= + private static readonly uint[] SortedWhitespaceCodePoints = new uint[25] + { + 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, + 0x0020, + 0x0085, + 0x00A0, + 0x1680, + 0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006, + 0x2007, + 0x2008, 0x2009, 0x200A, + 0x2028, 0x2029, + 0x202F, + 0x205F, + 0x3000 + }; + + public const char HighSurrogateStart = '\ud800'; + public const char HighSurrogateEnd = '\udbff'; + public const char LowSurrogateStart = '\udc00'; + public const char LowSurrogateEnd = '\udfff'; + + // To get this to compile with dotnet cli, we need to temporarily un-binary the magic values + public const byte b0000_0111U = 0x07; //7 + public const byte b0000_1111U = 0x0F; //15 + public const byte b0001_1111U = 0x1F; //31 + public const byte b0011_1111U = 0x3F; //63 + public const byte b0111_1111U = 0x7F; //127 + public const byte b1000_0000U = 0x80; //128 + public const byte b1100_0000U = 0xC0; //192 + public const byte b1110_0000U = 0xE0; //224 + public const byte b1111_0000U = 0xF0; //240 + public const byte b1111_1000U = 0xF8; //248 + + #endregion Constants + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsWhitespace(uint codePoint) + { + return Array.BinarySearch(SortedWhitespaceCodePoints, codePoint) >= 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsSupportedCodePoint(uint codePoint) + { + if (codePoint >= FirstNotSupportedCodePoint) + return false; + if (codePoint >= HighSurrogateStart && codePoint <= LowSurrogateEnd) + return false; + if (codePoint >= 0xFDD0 && codePoint <= 0xFDEF) + return false; + if (codePoint == 0xFFFE || codePoint == 0xFFFF) + return false; + + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsBmp(uint codePoint) + { + return codePoint < BasicMultilingualPlaneEndMarker; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe static int PtrDiff(char* a, char* b) + { + return (int)(((uint)((byte*)a - (byte*)b)) >> 1); + } + + // byte* flavor just for parity + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe static int PtrDiff(byte* a, byte* b) + { + return (int)(a - b); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool InRange(int ch, int start, int end) + { + return (uint)(ch - start) <= (uint)(end - start); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetUtf8DecodedBytes(byte b) + { + if ((b & b1000_0000U) == 0) + return 1; + + if ((b & b1110_0000U) == b1100_0000U) + return 2; + + if ((b & b1111_0000U) == b1110_0000U) + return 3; + + if ((b & b1111_1000U) == b1111_0000U) + return 4; + + return 0; + } + + internal static int GetUtf8EncodedBytes(uint codePoint) + { + if (codePoint <= 0x7F) + return 1; + + if (codePoint <= 0x7FF) + return 2; + + if (codePoint <= 0xFFFF) + return 3; + + if (codePoint <= 0x10FFFF) + return 4; + + return 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsSurrogate(uint codePoint) + { + return codePoint >= HighSurrogateStart && codePoint <= LowSurrogateEnd; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsLowSurrogate(uint codePoint) + { + return codePoint >= LowSurrogateStart && codePoint <= LowSurrogateEnd; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsHighSurrogate(uint codePoint) + { + return codePoint >= HighSurrogateStart && codePoint <= HighSurrogateEnd; + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Encoding/ParsingTrie.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Encoding/ParsingTrie.cs new file mode 100644 index 000000000..78b0ab54d --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Encoding/ParsingTrie.cs @@ -0,0 +1,343 @@ +// 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.Collections; +using System.Collections.Generic; + +namespace System.Text +{ + internal static class ParsingTrie + { + #region Parsing trie struct + + // The parsing trie is structured as an array, which means that there are two types of + // "nodes" for representational purposes + // + // The first node type (the parent node) uses the valueOrNumChildren to represent the number of children + // underneath it. The index is unused for this type of node, except when it's used for + // sequential node mapping (see below). If valueOrNumChildren is zero for this type of node, the index + // is used and represents an index into _digitsAndSymbols. + // + // The second node types immediately follow the first (the childe nodes). They are composed of a value + // (valueOrNumChildren), which is walked via binary search, and an index, which points to another + // node contained in the array. + // + // We use the int index here to encode max-min info for sequential leaves + // It's very common for digits to be encoded sequentially, so we save time by mapping here + // The index is formatted as such: 0xAABBCCDD, where AA = the min value, + // BB = the index of the min value relative to the current node (1-indexed), + // CC = the max value, and DD = the max value's index in the same coord-system as BB. + public struct Node + { + public byte ValueOrNumChildren; + public int IndexOrSymbol; + } + + #endregion Parsing trie struct + + /// + /// A Suffix represents the ending sequence of bytes that correspond to a symbol. + /// Suffixes play an important role in the parsing trie generation algorithm. + /// + /// Let's say there are four symbols: + /// Symbol 0: Sequence 1, 1, 2, 3 + /// Symbol 1: Sequence 0, 1, 2, 3 + /// Symbol 2: Sequence 0, 1, 4, 4 + /// Symbol 3: Sequence 1, 1, 2, 1 + /// + /// First, a Suffix is created for each symbol's sequence, and the Suffixes are sorted by their byte sequences: + /// ListOfSuffix { + /// Suffix { SymbolIndex: 1, Bytes: { 0, 1, 2, 3 } } + /// Suffix { SymbolIndex: 2, Bytes: { 0, 1, 4, 4 } } + /// Suffix { SymbolIndex: 3, Bytes: { 1, 1, 2, 1 } } + /// Suffix { SymbolIndex: 0, Bytes: { 1, 1, 2, 3 } } + /// } + /// + /// Next, the Suffixes are clumped into SuffixClumps, based on the beginning byte: + /// ListOfSuffixClump { + /// SuffixClump { + /// BeginningByte: 0 + /// Suffixes { + /// Suffix { SymbolIndex: 1, Bytes: { 1, 2, 3 } } + /// Suffix { SymbolIndex: 2, Bytes: { 1, 4, 4 } } + /// } + /// } + /// SuffixClump { + /// BeginningByte: 1 + /// Suffixes { + /// Suffix { SymbolIndex: 3, Bytes: { 1, 2, 1 } } + /// Suffix { SymbolIndex: 0, Bytes: { 1, 2, 3 } } + /// } + /// } + /// } + /// + /// Then, a parent ParsingTrieNode is created, with its NumChildren equal to the number of SuffixClumps. + /// Each SuffixClump represents both a "child" node in the parsing trie, and the "parent" node that child + /// node points to. + /// + /// Each SuffixClump that has more than one Suffix will require further clumping; that is to say, it does + /// not represent a leaf node in the parsing trie. Such SuffixClumps will be recursively clumped. + /// + private struct Suffix : IComparable + { + public int SymbolIndex; + public byte[] Bytes; + + public Suffix(int symbolIndex, byte[] bytes) + { + SymbolIndex = symbolIndex; + Bytes = bytes; + } + + public Suffix(int symbolIndex, ReadOnlySpan bytes) + { + SymbolIndex = symbolIndex; + + // HACKHACK: Keeping Bytes as a Span property on Suffix will cause crashing in .NET Core 2.0. + // Storing as pure array for now until we can re-visit. + // This is necessary to unblock usage of fast Span for Kestrel and others. + Bytes = bytes.ToArray(); + } + + public int CompareTo(Suffix other) + { + var shorter = Math.Min(other.Bytes.Length, Bytes.Length); + for(int index = 0; index < shorter; index++) + { + if (Bytes[index] == other.Bytes[index]) continue; + return Bytes[index].CompareTo(other.Bytes[index]); + } + return Bytes.Length.CompareTo(other.Bytes.Length); + + } + } + + private struct SuffixClump + { + public byte BeginningByte; + public List Suffixes; + + public SuffixClump(byte beginningByte) + { + BeginningByte = beginningByte; + // This list of suffixes will not exceed the number of symbols. Initialize + // the list to be of size 20, which is slightly larger than the number of symbols. + Suffixes = new List(20); + } + } + + private struct Sequence : IComparable + { + public int BeginningIndex; + public int EndIndex; + public byte BeginningValue; + public byte EndValue; + + // This constructor creates a sequence of length 0. + public Sequence(int index, byte value) + { + BeginningIndex = index; + EndIndex = index; + BeginningValue = value; + EndValue = value; + } + + public int CompareTo(Sequence other) + { + int thisLength = EndIndex - BeginningIndex; + int otherLength = other.EndIndex - other.BeginningIndex; + return thisLength.CompareTo(otherLength); + } + + public int Length + { + get + { + return EndIndex - BeginningIndex; + } + } + + // Sequence map is formatted as such: + // 0xAABBCCDD + // AA: The min value + // BB: The index of the min value relative to the current node (1-indexed) + // CC: The max value + // DD: The max value's index in the same coord-system as BB + public int CreateSequenceMap() + { + int sequenceMap = 0; + // AA + sequenceMap += BeginningValue << 24; + // BB: Add 1 to BeginningIndex because the parent node is located 1 place before the 0-indexed child node + sequenceMap += (BeginningIndex + 1) << 16; + // CC + sequenceMap += EndValue << 8; + // DD: Add 1 to EndIndex for same reason as BB + sequenceMap += EndIndex + 1; + + return sequenceMap; + } + } + + // The return value here is the index in parsingTrieList at which the parent node was placed. + private static int CreateParsingTrieNodeAndChildren(ref List parsingTrieList, List sortedSuffixes) + { + // If there is only one suffix, create a leaf node + if (sortedSuffixes.Count == 1) + { + Node leafNode = new Node(); + leafNode.ValueOrNumChildren = 0; + leafNode.IndexOrSymbol = sortedSuffixes[0].SymbolIndex; + int leafNodeIndex = parsingTrieList.Count; + parsingTrieList.Add(leafNode); + return leafNodeIndex; + } + + // Group suffixes into clumps based on first byte + List clumps = new List(sortedSuffixes.Count); + byte beginningByte = sortedSuffixes[0].Bytes[0]; + SuffixClump currentClump = new SuffixClump(beginningByte); + clumps.Add(currentClump); + + // Initialize sequence detection + Sequence currentSequence = new Sequence(0, beginningByte); + Sequence longestSequence = currentSequence; + + foreach (Suffix suffix in sortedSuffixes) + { + var bytesSpan = new Span(suffix.Bytes); + if (suffix.Bytes[0] == beginningByte) + { + currentClump.Suffixes.Add(new Suffix(suffix.SymbolIndex, bytesSpan.Slice(1))); + } + else + { + beginningByte = suffix.Bytes[0]; + + // Determine if the new clump is part of a sequence + if (beginningByte == currentSequence.EndValue + 1) + { + // This clump is part of the current sequence + currentSequence.EndIndex++; + currentSequence.EndValue++; + + if (!currentSequence.Equals(longestSequence) && currentSequence.CompareTo(longestSequence) > 0) + { + // Replace the longest sequence with this sequence + longestSequence = currentSequence; + } + } + else + { + // This clump is part of a new sequence + currentSequence = new Sequence(clumps.Count, beginningByte); + } + + // This is a new clump, with at least one suffix inside it. Add to the list of clumps. + currentClump = new SuffixClump(beginningByte); + currentClump.Suffixes.Add(new Suffix(suffix.SymbolIndex, bytesSpan.Slice(1))); + clumps.Add(currentClump); + } + } + + // Now that we know how many children there are, create parent node and place in list + Node parentNode = new Node(); + parentNode.ValueOrNumChildren = (byte)clumps.Count; + // Only bother specifying a sequence if the longest sequence is sufficiently long + if (longestSequence.Length > 5) + { + parentNode.IndexOrSymbol = longestSequence.CreateSequenceMap(); + } + else + { + parentNode.IndexOrSymbol = 0; + } + int parentNodeIndex = parsingTrieList.Count; + parsingTrieList.Add(parentNode); + + // Reserve space in list for child nodes. In this algorithm, all parent nodes are created first, leaving gaps for the child nodes + // to be filled in once it is known where they point to. + int childNodeStartIndex = parsingTrieList.Count; + for (int i = 0; i < clumps.Count; i++) + { + parsingTrieList.Add(default); + } + + // Process child nodes + List childNodes = new List(); + foreach (SuffixClump clump in clumps) + { + Node childNode = new Node(); + childNode.ValueOrNumChildren = clump.BeginningByte; + childNode.IndexOrSymbol = CreateParsingTrieNodeAndChildren(ref parsingTrieList, clump.Suffixes); + childNodes.Add(childNode); + } + + // Place child nodes in spots allocated for them + int childNodeIndex = childNodeStartIndex; + foreach (Node childNode in childNodes) + { + parsingTrieList[childNodeIndex] = childNode; + childNodeIndex++; + } + + return parentNodeIndex; + } + + public static Node[] Create(byte[][] symbols) + { + List symbolList = new List(symbols.Length); + for (int i = 0; i < symbols.Length; i++) + { + if (symbols[i] != null) + { + symbolList.Add(new Suffix(i, symbols[i])); + } + } + + // Sort the symbol list. This is important for allowing binary search of the child nodes, as well as + // counting the number of children a node has. + symbolList.Sort(); + + // validate symbol consistemcy: + // a) each symbol must be unique + // b) a symbol cannot be a prefix of another symbol + // c) symbols cannot be empty + for(int i = 1; i < symbolList.Count; i++) + { + var first = symbolList[i - 1]; + var second = symbolList[i]; + + if(first.Bytes.Length == 0 || second.Bytes.Length == 0) + { + throw new ArgumentException("Symbol cannot be zero bytes long"); + } + var firstSpan = first.Bytes.AsSpan(); + if (firstSpan.SequenceEqual(second.Bytes)) + { + throw new ArgumentException("Symbols cannot be identical"); + } + if (first.Bytes.Length > second.Bytes.Length) + { + if (firstSpan.StartsWith(second.Bytes)) + { + throw new ArgumentException("Symbols are ambiguous"); + } + } + else if(first.Bytes.Length < second.Bytes.Length) + { + if (second.Bytes.AsSpan().StartsWith(first.Bytes)) + { + throw new ArgumentException("Symbols are ambiguous"); + } + } + } + + List parsingTrieList = new List(100); + CreateParsingTrieNodeAndChildren(ref parsingTrieList, symbolList); + + return parsingTrieList.ToArray(); + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Encoding/SymbolTable.Symbol.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Encoding/SymbolTable.Symbol.cs new file mode 100644 index 000000000..7e6e31746 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Encoding/SymbolTable.Symbol.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace System.Text +{ + public partial class SymbolTable + { + // Do not change the specific enum values without careful consideration of the impacts to the parsers. + public enum Symbol : ushort { + D0 = (ushort)0, + D1 = (ushort)1, + D2 = (ushort)2, + D3 = (ushort)3, + D4 = (ushort)4, + D5 = (ushort)5, + D6 = (ushort)6, + D7 = (ushort)7, + D8 = (ushort)8, + D9 = (ushort)9, + DecimalSeparator = (ushort)10, + Exponent = (ushort)16, + GroupSeparator = (ushort)11, + InfinitySign = (ushort)12, + MinusSign = (ushort)13, + NaN = (ushort)15, + PlusSign = (ushort)14, + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Encoding/SymbolTable.Utf16.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Encoding/SymbolTable.Utf16.cs new file mode 100644 index 000000000..58025ae7b --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Encoding/SymbolTable.Utf16.cs @@ -0,0 +1,98 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Buffers; +using System.Runtime.CompilerServices; + +namespace System.Text +{ + public partial class SymbolTable + { + private sealed class Utf16InvariantSymbolTable : SymbolTable + { + private static readonly byte[][] Utf16DigitsAndSymbols = new byte[][] + { + new byte[] { 48, 0, }, // digit 0 + new byte[] { 49, 0, }, + new byte[] { 50, 0, }, + new byte[] { 51, 0, }, + new byte[] { 52, 0, }, + new byte[] { 53, 0, }, + new byte[] { 54, 0, }, + new byte[] { 55, 0, }, + new byte[] { 56, 0, }, + new byte[] { 57, 0, }, // digit 9 + new byte[] { 46, 0, }, // decimal separator + new byte[] { 44, 0, }, // group separator + new byte[] { 73, 0, 110, 0, 102, 0, 105, 0, 110, 0, 105, 0, 116, 0, 121, 0, }, // Infinity + new byte[] { 45, 0, }, // minus sign + new byte[] { 43, 0, }, // plus sign + new byte[] { 78, 0, 97, 0, 78, 0, }, // NaN + new byte[] { 69, 0, }, // E + new byte[] { 101, 0, }, // e + }; + + public Utf16InvariantSymbolTable() : base(Utf16DigitsAndSymbols) {} + + public override bool TryEncode(byte utf8, Span destination, out int bytesWritten) + { + if (destination.Length < 2) + goto ExitFailed; + + if (utf8 > 0x7F) + goto ExitFailed; + + Unsafe.As(ref destination.DangerousGetPinnableReference()) = (char)utf8; + bytesWritten = 2; + return true; + + ExitFailed: + bytesWritten = 0; + return false; + } + + public override bool TryEncode(ReadOnlySpan utf8, Span destination, out int bytesConsumed, out int bytesWritten) + { + var status = Encoders.Utf8.ToUtf16(utf8, destination, out bytesConsumed, out bytesWritten); + if (status != TransformationStatus.Done) + { + bytesConsumed = bytesWritten = 0; + return false; + } + + return true; + } + + public override bool TryParse(ReadOnlySpan source, out byte utf8, out int bytesConsumed) + { + if (source.Length < 2) + goto ExitFailed; + + ref char value = ref Unsafe.As(ref source.DangerousGetPinnableReference()); + if (value > 0x7F) + goto ExitFailed; + + bytesConsumed = 2; + utf8 = (byte)value; + return true; + + ExitFailed: + utf8 = 0; + bytesConsumed = 0; + return false; + } + + public override bool TryParse(ReadOnlySpan source, Span utf8, out int bytesConsumed, out int bytesWritten) + { + var status = Encoders.Utf16.ToUtf8(source, utf8, out bytesConsumed, out bytesWritten); + if (status != TransformationStatus.Done) + { + bytesConsumed = bytesWritten = 0; + return false; + } + + return true; + } + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Encoding/SymbolTable.Utf8.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Encoding/SymbolTable.Utf8.cs new file mode 100644 index 000000000..20e41d955 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Encoding/SymbolTable.Utf8.cs @@ -0,0 +1,100 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; + +namespace System.Text +{ + public partial class SymbolTable + { + private sealed class Utf8InvariantSymbolTable : SymbolTable + { + private static readonly byte[][] Utf8DigitsAndSymbols = new byte[][] + { + new byte[] { 48, }, + new byte[] { 49, }, + new byte[] { 50, }, + new byte[] { 51, }, + new byte[] { 52, }, + new byte[] { 53, }, + new byte[] { 54, }, + new byte[] { 55, }, + new byte[] { 56, }, + new byte[] { 57, }, // digit 9 + new byte[] { 46, }, // decimal separator + new byte[] { 44, }, // group separator + new byte[] { 73, 110, 102, 105, 110, 105, 116, 121, }, + new byte[] { 45, }, // minus sign + new byte[] { 43, }, // plus sign + new byte[] { 78, 97, 78, }, // NaN + new byte[] { 69, }, // E + new byte[] { 101, }, // e + }; + + public Utf8InvariantSymbolTable() : base(Utf8DigitsAndSymbols) {} + + public override bool TryEncode(byte utf8, Span destination, out int bytesWritten) + { + if (destination.Length < 1) + goto ExitFailed; + + if (utf8 > 0x7F) + goto ExitFailed; + + destination[0] = utf8; + bytesWritten = 1; + return true; + + ExitFailed: + bytesWritten = 0; + return false; + } + + public override bool TryEncode(ReadOnlySpan utf8, Span destination, out int bytesConsumed, out int bytesWritten) + { + // TODO: We might want to validate that the stream we are moving from utf8 to destination (also UTF-8) is valid. + // For now, we are just doing a copy. + if (utf8.TryCopyTo(destination)) + { + bytesConsumed = bytesWritten = utf8.Length; + return true; + } + + bytesConsumed = bytesWritten = 0; + return false; + } + + public override bool TryParse(ReadOnlySpan source, out byte utf8, out int bytesConsumed) + { + if (source.Length < 1) + goto ExitFailed; + + utf8 = source[0]; + if (utf8 > 0x7F) + goto ExitFailed; + + bytesConsumed = 1; + return true; + + ExitFailed: + utf8 = 0; + bytesConsumed = 0; + return false; + } + + public override bool TryParse(ReadOnlySpan source, Span utf8, out int bytesConsumed, out int bytesWritten) + { + // TODO: We might want to validate that the stream we are moving from utf8 to destination (also UTF-8) is valid. + // For now, we are just doing a copy. + if (source.TryCopyTo(utf8)) + { + bytesConsumed = bytesWritten = source.Length; + return true; + } + + bytesConsumed = bytesWritten = 0; + return false; + } + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Encoding/SymbolTable.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Encoding/SymbolTable.cs new file mode 100644 index 000000000..ce51d3ebb --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Encoding/SymbolTable.cs @@ -0,0 +1,242 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace System.Text +{ + public abstract partial class SymbolTable + { + #region Private data + + private readonly byte[][] _symbols; // this could be flattened into a single array + private readonly ParsingTrie.Node[] _parsingTrie; // prefix tree used for parsing + + #endregion Private data + + #region Constructors + + protected SymbolTable(byte[][] symbols) + { + _symbols = symbols; + _parsingTrie = ParsingTrie.Create(symbols); + } + + #endregion Constructors + + #region Static instances + + public readonly static SymbolTable InvariantUtf8 = new Utf8InvariantSymbolTable(); + + public readonly static SymbolTable InvariantUtf16 = new Utf16InvariantSymbolTable(); + + #endregion Static instances + + public bool TryEncode(Symbol symbol, Span destination, out int bytesWritten) + { + byte[] bytes = _symbols[(int)symbol]; + bytesWritten = bytes.Length; + if (bytesWritten > destination.Length) + { + bytesWritten = 0; + return false; + } + + if (bytesWritten == 2) + { + destination[0] = bytes[0]; + destination[1] = bytes[1]; + return true; + } + + if (bytesWritten == 1) + { + destination[0] = bytes[0]; + return true; + } + + new Span(bytes).CopyTo(destination); + return true; + } + + public abstract bool TryEncode(byte utf8, Span destination, out int bytesWritten); + + public abstract bool TryEncode(ReadOnlySpan utf8, Span destination, out int bytesConsumed, out int bytesWritten); + + public bool TryParse(ReadOnlySpan source, out Symbol symbol, out int bytesConsumed) + { + int trieIndex = 0; + int codeUnitIndex = 0; + bytesConsumed = 0; + while (true) + { + if (_parsingTrie[trieIndex].ValueOrNumChildren == 0) // if numChildren == 0, we're on a leaf & we've found our value and completed the code unit + { + symbol = (Symbol)_parsingTrie[trieIndex].IndexOrSymbol; // return the parsed value + if (VerifySuffix(source, codeUnitIndex, symbol)) + { + bytesConsumed = _symbols[(int)symbol].Length; + return true; + } + else + { + symbol = 0; + bytesConsumed = 0; + return false; + } + } + else + { + int search = BinarySearch(trieIndex, codeUnitIndex, source[codeUnitIndex]); // we search the _parsingTrie for the nextByte + + if (search > 0) // if we found a node + { + trieIndex = _parsingTrie[search].IndexOrSymbol; + bytesConsumed++; + codeUnitIndex++; + } + else + { + symbol = 0; + bytesConsumed = 0; + return false; + } + } + } + } + + public abstract bool TryParse(ReadOnlySpan source, out byte utf8, out int bytesConsumed); + + public abstract bool TryParse(ReadOnlySpan source, Span utf8, out int bytesConsumed, out int bytesWritten); + + #region Public UTF-16 to UTF-8 helpers + + public bool TryEncode(ReadOnlySpan source, Span destination, out int bytesConsumed, out int bytesWritten) + { + ReadOnlySpan srcBytes = source.AsBytes(); + + if (this == SymbolTable.InvariantUtf16) + return TryEncodeUtf16(srcBytes, destination, out bytesConsumed, out bytesWritten); + + const int BufferSize = 256; + + int srcLength = srcBytes.Length; + if (srcLength <= 0) + { + bytesConsumed = bytesWritten = 0; + return true; + } + + Span temp; + unsafe + { + byte* pTemp = stackalloc byte[BufferSize]; + temp = new Span(pTemp, BufferSize); + } + + bytesWritten = 0; + bytesConsumed = 0; + while (srcLength > bytesConsumed) + { + var status = Encoders.Utf16.ToUtf8(srcBytes, temp, out int consumed, out int written); + if (status == Buffers.TransformationStatus.InvalidData) + goto ExitFailed; + + srcBytes = srcBytes.Slice(consumed); + bytesConsumed += consumed; + + if (!TryEncode(temp.Slice(0, written), destination, out consumed, out written)) + goto ExitFailed; + + destination = destination.Slice(written); + bytesWritten += written; + } + + return true; + + ExitFailed: + return false; + } + + private bool TryEncodeUtf16(ReadOnlySpan source, Span destination, out int bytesConsumed, out int bytesWritten) + { + // NOTE: There is no validation of this UTF-16 encoding. A caller is expected to do any validation on their own if + // they don't trust the data. + bytesConsumed = source.Length; + bytesWritten = destination.Length; + if (bytesConsumed > bytesWritten) + { + source = source.Slice(0, bytesWritten); + bytesConsumed = bytesWritten; + } + else + { + bytesWritten = bytesConsumed; + } + + source.CopyTo(destination); + return true; + } + + #endregion Public UTF-16 to UTF-8 helpers + + #region Private helpers + + // This binary search implementation returns an int representing either: + // - the index of the item searched for (if the value is positive) + // - the index of the location where the item should be placed to maintain a sorted list (if the value is negative) + private int BinarySearch(int nodeIndex, int level, byte value) + { + int maxMinLimits = _parsingTrie[nodeIndex].IndexOrSymbol; + if (maxMinLimits != 0 && value > (uint)maxMinLimits >> 24 && value < (uint)(maxMinLimits << 16) >> 24) + { + // See the comments on the struct above for more information about this format + return (int)(nodeIndex + ((uint)(maxMinLimits << 8) >> 24) + value - ((uint)maxMinLimits >> 24)); + } + + int leftBound = nodeIndex + 1, rightBound = nodeIndex + _parsingTrie[nodeIndex].ValueOrNumChildren; + int midIndex = 0; + while (true) + { + if (leftBound > rightBound) // if the search failed + { + // this loop is necessary because binary search takes the floor + // of the middle, which means it can give incorrect indices for insertion. + // we should never iterate up more than two indices. + while (midIndex < nodeIndex + _parsingTrie[nodeIndex].ValueOrNumChildren + && _parsingTrie[midIndex].ValueOrNumChildren < value) + { + midIndex++; + } + return -midIndex; + } + + midIndex = (leftBound + rightBound) / 2; // find the middle value + + byte mValue = _parsingTrie[midIndex].ValueOrNumChildren; + + if (mValue < value) + leftBound = midIndex + 1; + else if (mValue > value) + rightBound = midIndex - 1; + else + return midIndex; + } + } + + private bool VerifySuffix(ReadOnlySpan buffer, int codeUnitIndex, Symbol symbol) + { + int codeUnitLength = _symbols[(int)symbol].Length; + if (codeUnitIndex == codeUnitLength - 1) + return true; + + for (int i = 0; i < codeUnitLength - codeUnitIndex; i++) + { + if (buffer[i + codeUnitIndex] != _symbols[(int)symbol][i + codeUnitIndex]) + return false; + } + + return true; + } + + #endregion Private helpers + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Formatting/FloatFormatter.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Formatting/FloatFormatter.cs new file mode 100644 index 000000000..7fdc80298 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Formatting/FloatFormatter.cs @@ -0,0 +1,91 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Diagnostics; + +namespace System.Text +{ + internal static class FloatFormatter + { + public static bool TryFormatNumber(double value, bool isSingle, Span buffer, out int bytesWritten, ParsedFormat format = default, SymbolTable symbolTable = null) + { + Precondition.Require(format.Symbol == 'G' || format.Symbol == 'E' || format.Symbol == 'F'); + + symbolTable = symbolTable ?? SymbolTable.InvariantUtf8; + + bytesWritten = 0; + int written; + + if (Double.IsNaN(value)) + { + return symbolTable.TryEncode(SymbolTable.Symbol.NaN, buffer, out bytesWritten); + } + + if (Double.IsInfinity(value)) + { + if (Double.IsNegativeInfinity(value)) + { + if (!symbolTable.TryEncode(SymbolTable.Symbol.MinusSign, buffer, out written)) + { + bytesWritten = 0; + return false; + } + bytesWritten += written; + } + if (!symbolTable.TryEncode(SymbolTable.Symbol.InfinitySign, buffer.Slice(bytesWritten), out written)) + { + bytesWritten = 0; + return false; + } + bytesWritten += written; + return true; + } + + // TODO: the lines below need to be replaced with properly implemented algorithm + // the problem is the algorithm is complex, so I am commiting a stub for now + var hack = value.ToString(format.Symbol.ToString()); + var utf16Bytes = hack.AsSpan().AsBytes(); + if (symbolTable == SymbolTable.InvariantUtf8) + { + var status = Encoders.Utf16.ToUtf8(utf16Bytes, buffer, out int consumed, out bytesWritten); + return status == Buffers.TransformationStatus.Done; + } + else if (symbolTable == SymbolTable.InvariantUtf16) + { + bytesWritten = utf16Bytes.Length; + if (utf16Bytes.TryCopyTo(buffer)) + return true; + + bytesWritten = 0; + return false; + } + else + { + // TODO: This is currently pretty expensive. Can this be done more efficiently? + // Note: removing the hack might solve this problem a very different way. + var status = Encoders.Utf16.ToUtf8Length(utf16Bytes, out int needed); + if (status != Buffers.TransformationStatus.Done) + { + bytesWritten = 0; + return false; + } + + Span temp; + unsafe + { + var buf = stackalloc byte[needed]; + temp = new Span(buf, needed); + } + + status = Encoders.Utf16.ToUtf8(utf16Bytes, temp, out int consumed, out written); + if (status != Buffers.TransformationStatus.Done) + { + bytesWritten = 0; + return false; + } + + return symbolTable.TryEncode(temp, buffer, out consumed, out bytesWritten); + } + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Formatting/IBufferFormattable.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Formatting/IBufferFormattable.cs new file mode 100644 index 000000000..d9f8cde59 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Formatting/IBufferFormattable.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + + +namespace System.Text +{ + public interface IBufferFormattable + { + // I went back and forth between bytesWritten being out and ref. Ref makes it easier to implement the interface. + // Out makes so that callers don't have to trust calees doing the right thing with the bytesWritten value. + // I prefer correctness here over ease of use (as this is a low level API), so I decided on out parameter. + + /// + /// This interface should be implemented by types that want to support allocation-free formatting. + /// + /// The buffer to format the value into + /// This parameter is used to return the number of bytes that were written to the buffer + /// This is a pre-parsed representation of the formatting string. It's preparsed for efficiency. + /// This object implements the character and symbol encoder. + /// False if the buffer was to small, otherwise true. + bool TryFormat(Span buffer, out int written, ParsedFormat format = default, SymbolTable symbolTable = null); + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Formatting/Internal/FormattingHelpers.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Formatting/Internal/FormattingHelpers.cs new file mode 100644 index 000000000..6b364be89 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Formatting/Internal/FormattingHelpers.cs @@ -0,0 +1,208 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace System.Text +{ + // All the helper methods in this class assume that the by-ref is valid and that there is + // enough space to fit the items that will be written into the underlying memory. The calling + // code must have already done all the necessary validation. + internal static class FormattingHelpers + { + // For the purpose of formatting time, the format specifier contains room for + // exactly 7 digits in the fraction portion. See "Round-trip format specifier" + // at the following URL for more information. + // https://msdn.microsoft.com/en-us/library/az4se3k1(v=vs.110).aspx#Roundtrip + private const int FractionDigits = 7; + + // A simple lookup table for converting numbers to hex. + private const string HexTable = "0123456789abcdef"; + + #region UTF-8 Helper methods + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteHexByte(byte value, ref byte buffer, int index) + { + Unsafe.Add(ref buffer, index) = (byte)HexTable[value >> 4]; + Unsafe.Add(ref buffer, index + 1) = (byte)HexTable[value & 0xF]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int WriteFractionDigits(long value, int digitCount, ref byte buffer, int index) + { + for (var i = FractionDigits; i > digitCount; i--) + value /= 10; + + return WriteDigits(value, digitCount, ref buffer, index); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int WriteDigits(long value, int digitCount, ref byte buffer, int index) + { + long left = value; + + for (var i = digitCount - 1; i >= 0; i--) + { + left = DivMod(left, 10, out long num); + Unsafe.Add(ref buffer, index + i) = (byte)('0' + num); + } + + return digitCount; + } + + /// + /// The unsigned long implementation of this method is much slower than the signed version above + /// due to optimization tricks that happen at the IL to ASM stage. Use the signed version unless + /// you definitely need to deal with numbers larger than long.MaxValue. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int WriteDigits(ulong value, int digitCount, ref byte buffer, int index) + { + ulong left = value; + + for (var i = digitCount - 1; i >= 0; i--) + { + left = DivMod(left, 10, out ulong num); + Unsafe.Add(ref buffer, index + i) = (byte)('0' + num); + } + + return digitCount; + } + + #endregion UTF-8 Helper methods + + #region UTF-16 Helper methods + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteHexByte(byte value, ref char buffer, int index) + { + Unsafe.Add(ref buffer, index) = HexTable[value >> 4]; + Unsafe.Add(ref buffer, index + 1) = HexTable[value & 0xF]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int WriteFractionDigits(long value, int digitCount, ref char buffer, int index) + { + for (var i = FractionDigits; i > digitCount; i--) + value /= 10; + + return WriteDigits(value, digitCount, ref buffer, index); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int WriteDigits(long value, int digitCount, ref char buffer, int index) + { + long left = value; + + for (var i = digitCount - 1; i >= 0; i--) + { + left = DivMod(left, 10, out long num); + Unsafe.Add(ref buffer, index + i) = (char)('0' + num); + } + + return digitCount; + } + + /// + /// The unsigned long implementation of this method is much slower than the signed version above + /// due to optimization tricks that happen at the IL to ASM stage. Use the signed version unless + /// you definitely need to deal with numbers larger than long.MaxValue. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int WriteDigits(ulong value, int digitCount, ref char buffer, int index) + { + ulong left = value; + + for (var i = digitCount - 1; i >= 0; i--) + { + left = DivMod(left, 10, out ulong num); + Unsafe.Add(ref buffer, index + i) = (char)('0' + num); + } + + return digitCount; + } + + #endregion UTF-16 Helper methods + + #region Math Helper methods + + /// + /// We don't have access to Math.DivRem, so this is a copy of the implementation. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long DivMod(long numerator, long denominator, out long modulo) + { + long div = numerator / denominator; + modulo = numerator - (div * denominator); + return div; + } + + /// + /// We don't have access to Math.DivRem, so this is a copy of the implementation. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong DivMod(ulong numerator, ulong denominator, out ulong modulo) + { + ulong div = numerator / denominator; + modulo = numerator - (div * denominator); + return div; + } + + #endregion Math Helper methods + + #region Character counting helper methods + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int CountDigits(long n) + { + if (n == 0) return 1; + + int digits = 0; + while (n != 0) + { + n /= 10; + digits++; + } + + return digits; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int CountDigits(ulong n) + { + if (n == 0) return 1; + + int digits = 0; + while (n != 0) + { + n /= 10; + digits++; + } + + return digits; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int CountFractionDigits(long n) + { + Precondition.Require(n >= 0); + + long left = n; + long m = 0; + int count = FractionDigits; + + // Remove all the 0 (zero) values from the right. + while (left > 0 && m == 0 && count > 0) + { + left = DivMod(left, 10, out m); + count--; + } + + return count + 1; + } + + #endregion Character counting helper methods + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Formatting/Internal/GuidFormatter.Utf16.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Formatting/Internal/GuidFormatter.Utf16.cs new file mode 100644 index 000000000..1be711edd --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Formatting/Internal/GuidFormatter.Utf16.cs @@ -0,0 +1,92 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; + +namespace System.Text +{ + internal static class InvariantUtf16GuidFormatter + { + #region Constants + + private const int GuidChars = 32; + + private const char OpenBrace = '{'; + private const char CloseBrace = '}'; + + private const char OpenParen = '('; + private const char CloseParen = ')'; + + private const char Dash = '-'; + + #endregion Constants + + public static unsafe bool TryFormat(this Guid value, Span buffer, out int bytesWritten, ParsedFormat format) + { + bool dash = format.Symbol != 'N'; + bool bookEnds = (format.Symbol == 'B') || (format.Symbol == 'P'); + + bytesWritten = (GuidChars + (dash ? 4 : 0) + (bookEnds ? 2 : 0)) * sizeof(char); + if (buffer.Length < bytesWritten) + { + bytesWritten = 0; + return false; + } + + Span dst = buffer.NonPortableCast(); + ref char utf16Bytes = ref dst.DangerousGetPinnableReference(); + byte* bytes = (byte*)&value; + int idx = 0; + + if (bookEnds && format.Symbol == 'B') + Unsafe.Add(ref utf16Bytes, idx++) = OpenBrace; + else if (bookEnds && format.Symbol == (byte)'P') + Unsafe.Add(ref utf16Bytes, idx++) = OpenParen; + + FormattingHelpers.WriteHexByte(bytes[3], ref utf16Bytes, idx); + FormattingHelpers.WriteHexByte(bytes[2], ref utf16Bytes, idx + 2); + FormattingHelpers.WriteHexByte(bytes[1], ref utf16Bytes, idx + 4); + FormattingHelpers.WriteHexByte(bytes[0], ref utf16Bytes, idx + 6); + idx += 8; + + if (dash) + Unsafe.Add(ref utf16Bytes, idx++) = Dash; + + FormattingHelpers.WriteHexByte(bytes[5], ref utf16Bytes, idx); + FormattingHelpers.WriteHexByte(bytes[4], ref utf16Bytes, idx + 2); + idx += 4; + + if (dash) + Unsafe.Add(ref utf16Bytes, idx++) = Dash; + + FormattingHelpers.WriteHexByte(bytes[7], ref utf16Bytes, idx); + FormattingHelpers.WriteHexByte(bytes[6], ref utf16Bytes, idx + 2); + idx += 4; + + if (dash) + Unsafe.Add(ref utf16Bytes, idx++) = Dash; + + FormattingHelpers.WriteHexByte(bytes[8], ref utf16Bytes, idx); + FormattingHelpers.WriteHexByte(bytes[9], ref utf16Bytes, idx + 2); + idx += 4; + + if (dash) + Unsafe.Add(ref utf16Bytes, idx++) = Dash; + + FormattingHelpers.WriteHexByte(bytes[10], ref utf16Bytes, idx); + FormattingHelpers.WriteHexByte(bytes[11], ref utf16Bytes, idx + 2); + FormattingHelpers.WriteHexByte(bytes[12], ref utf16Bytes, idx + 4); + FormattingHelpers.WriteHexByte(bytes[13], ref utf16Bytes, idx + 6); + FormattingHelpers.WriteHexByte(bytes[14], ref utf16Bytes, idx + 8); + FormattingHelpers.WriteHexByte(bytes[15], ref utf16Bytes, idx + 10); + idx += 12; + + if (bookEnds && format.Symbol == 'B') + Unsafe.Add(ref utf16Bytes, idx++) = CloseBrace; + else if (bookEnds && format.Symbol == 'P') + Unsafe.Add(ref utf16Bytes, idx++) = CloseParen; + + return true; + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Formatting/Internal/GuidFormatter.Utf8.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Formatting/Internal/GuidFormatter.Utf8.cs new file mode 100644 index 000000000..64df88830 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Formatting/Internal/GuidFormatter.Utf8.cs @@ -0,0 +1,91 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; + +namespace System.Text +{ + internal static class InvariantUtf8GuidFormatter + { + #region Constants + + private const int GuidChars = 32; + + private const byte OpenBrace = (byte)'{'; + private const byte CloseBrace = (byte)'}'; + + private const byte OpenParen = (byte)'('; + private const byte CloseParen = (byte)')'; + + private const byte Dash = (byte)'-'; + + #endregion Constants + + public static unsafe bool TryFormat(this Guid value, Span buffer, out int bytesWritten, ParsedFormat format) + { + bool dash = format.Symbol != 'N'; + bool bookEnds = (format.Symbol == 'B') || (format.Symbol == 'P'); + + bytesWritten = GuidChars + (dash ? 4 : 0) + (bookEnds ? 2 : 0); + if (buffer.Length < bytesWritten) + { + bytesWritten = 0; + return false; + } + + ref byte utf8Bytes = ref buffer.DangerousGetPinnableReference(); + byte* bytes = (byte*)&value; + int idx = 0; + + if (bookEnds && format.Symbol == 'B') + Unsafe.Add(ref utf8Bytes, idx++) = OpenBrace; + else if (bookEnds && format.Symbol == (byte)'P') + Unsafe.Add(ref utf8Bytes, idx++) = OpenParen; + + FormattingHelpers.WriteHexByte(bytes[3], ref utf8Bytes, idx); + FormattingHelpers.WriteHexByte(bytes[2], ref utf8Bytes, idx + 2); + FormattingHelpers.WriteHexByte(bytes[1], ref utf8Bytes, idx + 4); + FormattingHelpers.WriteHexByte(bytes[0], ref utf8Bytes, idx + 6); + idx += 8; + + if (dash) + Unsafe.Add(ref utf8Bytes, idx++) = Dash; + + FormattingHelpers.WriteHexByte(bytes[5], ref utf8Bytes, idx); + FormattingHelpers.WriteHexByte(bytes[4], ref utf8Bytes, idx + 2); + idx += 4; + + if (dash) + Unsafe.Add(ref utf8Bytes, idx++) = Dash; + + FormattingHelpers.WriteHexByte(bytes[7], ref utf8Bytes, idx); + FormattingHelpers.WriteHexByte(bytes[6], ref utf8Bytes, idx + 2); + idx += 4; + + if (dash) + Unsafe.Add(ref utf8Bytes, idx++) = Dash; + + FormattingHelpers.WriteHexByte(bytes[8], ref utf8Bytes, idx); + FormattingHelpers.WriteHexByte(bytes[9], ref utf8Bytes, idx + 2); + idx += 4; + + if (dash) + Unsafe.Add(ref utf8Bytes, idx++) = Dash; + + FormattingHelpers.WriteHexByte(bytes[10], ref utf8Bytes, idx); + FormattingHelpers.WriteHexByte(bytes[11], ref utf8Bytes, idx + 2); + FormattingHelpers.WriteHexByte(bytes[12], ref utf8Bytes, idx + 4); + FormattingHelpers.WriteHexByte(bytes[13], ref utf8Bytes, idx + 6); + FormattingHelpers.WriteHexByte(bytes[14], ref utf8Bytes, idx + 8); + FormattingHelpers.WriteHexByte(bytes[15], ref utf8Bytes, idx + 10); + idx += 12; + + if (bookEnds && format.Symbol == 'B') + Unsafe.Add(ref utf8Bytes, idx++) = CloseBrace; + else if (bookEnds && format.Symbol == 'P') + Unsafe.Add(ref utf8Bytes, idx++) = CloseParen; + + return true; + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Formatting/Internal/IntegerFormatter.Utf16.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Formatting/Internal/IntegerFormatter.Utf16.cs new file mode 100644 index 000000000..715c5a5a8 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Formatting/Internal/IntegerFormatter.Utf16.cs @@ -0,0 +1,262 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; + +namespace System.Text +{ + internal static class InvariantUtf16IntegerFormatter + { + private const char Minus = '-'; + private const char Period = '.'; + private const char Seperator = ','; + + // Invariant formatting uses groups of 3 for each number group seperated by commas. + // ex. 1,234,567,890 + private const int GroupSize = 3; + + public static bool TryFormatDecimalInt64(long value, byte precision, Span buffer, out int bytesWritten) + { + int digitCount = FormattingHelpers.CountDigits(value); + int charsNeeded = digitCount + (int)((value >> 63) & 1); + Span span = buffer.NonPortableCast(); + + if (span.Length < charsNeeded) + { + bytesWritten = 0; + return false; + } + + ref char utf16Bytes = ref span.DangerousGetPinnableReference(); + int idx = 0; + + if (value < 0) + { + Unsafe.Add(ref utf16Bytes, idx++) = Minus; + + // Abs(long.MinValue) == long.MaxValue + 1, so we need to re-route to unsigned to handle value + if (value == long.MinValue) + { + if (!TryFormatDecimalUInt64((ulong)long.MaxValue + 1, precision, buffer.Slice(2), out bytesWritten)) + return false; + + bytesWritten += sizeof(char); // Add the minus sign + return true; + } + + value = -value; + } + + if (precision != ParsedFormat.NoPrecision) + { + int leadingZeros = (int)precision - digitCount; + while (leadingZeros-- > 0) + Unsafe.Add(ref utf16Bytes, idx++) = '0'; + } + + idx += FormattingHelpers.WriteDigits(value, digitCount, ref utf16Bytes, idx); + + bytesWritten = idx * sizeof(char); + return true; + } + + public static bool TryFormatDecimalUInt64(ulong value, byte precision, Span buffer, out int bytesWritten) + { + if (value <= long.MaxValue) + return TryFormatDecimalInt64((long)value, precision, buffer, out bytesWritten); + + // Remove a single digit from the number. This will get it below long.MaxValue + // Then we call the faster long version and follow-up with writing the last + // digit. This ends up being faster by a factor of 2 than to just do the entire + // operation using the unsigned versions. + value = FormattingHelpers.DivMod(value, 10, out ulong lastDigit); + + if (precision != ParsedFormat.NoPrecision && precision > 0) + precision -= 1; + + if (!TryFormatDecimalInt64((long)value, precision, buffer, out bytesWritten)) + return false; + + Span span = buffer.Slice(bytesWritten).NonPortableCast(); + + if (span.Length < sizeof(char)) + { + bytesWritten = 0; + return false; + } + + ref char utf16Bytes = ref span.DangerousGetPinnableReference(); + FormattingHelpers.WriteDigits(lastDigit, 1, ref utf16Bytes, 0); + bytesWritten += sizeof(char); + return true; + } + + public static bool TryFormatNumericInt64(long value, byte precision, Span buffer, out int bytesWritten) + { + int digitCount = FormattingHelpers.CountDigits(value); + int groupSeperators = (int)FormattingHelpers.DivMod(digitCount, GroupSize, out long firstGroup); + if (firstGroup == 0) + { + firstGroup = 3; + groupSeperators--; + } + + int trailingZeros = (precision == ParsedFormat.NoPrecision) ? 2 : precision; + int charsNeeded = (int)((value >> 63) & 1) + digitCount + groupSeperators; + int idx = charsNeeded; + + if (trailingZeros > 0) + charsNeeded += trailingZeros + 1; // +1 for period. + + Span span = buffer.NonPortableCast(); + + if (span.Length < charsNeeded) + { + bytesWritten = 0; + return false; + } + + ref char utf16Bytes = ref span.DangerousGetPinnableReference(); + long v = value; + + if (v < 0) + { + Unsafe.Add(ref utf16Bytes, 0) = Minus; + + // Abs(long.MinValue) == long.MaxValue + 1, so we need to re-route to unsigned to handle value + if (v == long.MinValue) + { + if (!TryFormatNumericUInt64((ulong)long.MaxValue + 1, precision, buffer.Slice(2), out bytesWritten)) + return false; + + bytesWritten += sizeof(char); // Add the minus sign + return true; + } + + v = -v; + } + + // Write out the trailing zeros + if (trailingZeros > 0) + { + Unsafe.Add(ref utf16Bytes, idx) = Period; + FormattingHelpers.WriteDigits(0, trailingZeros, ref utf16Bytes, idx + 1); + } + + // Starting from the back, write each group of digits except the first group + while (digitCount > 3) + { + idx -= 3; + v = FormattingHelpers.DivMod(v, 1000, out long groupValue); + FormattingHelpers.WriteDigits(groupValue, 3, ref utf16Bytes, idx); + Unsafe.Add(ref utf16Bytes, --idx) = Seperator; + digitCount -= 3; + } + + // Write the first group of digits. + FormattingHelpers.WriteDigits(v, (int)firstGroup, ref utf16Bytes, idx - (int)firstGroup); + + bytesWritten = charsNeeded * sizeof(char); + return true; + } + + public static bool TryFormatNumericUInt64(ulong value, byte precision, Span buffer, out int bytesWritten) + { + if (value <= long.MaxValue) + return TryFormatNumericInt64((long)value, precision, buffer, out bytesWritten); + + // The ulong path is much slower than the long path here, so we are doing the last group + // inside this method plus the zero padding but routing to the long version for the rest. + value = FormattingHelpers.DivMod(value, 1000, out ulong lastGroup); + + if (!TryFormatNumericInt64((long)value, 0, buffer, out bytesWritten)) + return false; + + if (precision == ParsedFormat.NoPrecision) + precision = 2; + + // Since this method routes entirely to the long version if the number is smaller than + // long.MaxValue, we are guaranteed to need to write 3 more digits here before the set + // of trailing zeros. + + int extraChars = 4; // 3 digits + group seperator + if (precision > 0) + extraChars += precision + 1; // +1 for period. + + Span span = buffer.Slice(bytesWritten).NonPortableCast(); + + if (span.Length < extraChars) + { + bytesWritten = 0; + return false; + } + + ref char utf16Bytes = ref span.DangerousGetPinnableReference(); + var idx = 0; + + // Write the last group + Unsafe.Add(ref utf16Bytes, idx++) = Seperator; + idx += FormattingHelpers.WriteDigits(lastGroup, 3, ref utf16Bytes, idx); + + // Write out the trailing zeros + if (precision > 0) + { + Unsafe.Add(ref utf16Bytes, idx++) = Period; + idx += FormattingHelpers.WriteDigits(0, precision, ref utf16Bytes, idx); + } + + bytesWritten += extraChars * sizeof(char); + return true; + } + + public static bool TryFormatHexUInt64(ulong value, byte precision, bool useLower, Span buffer, out int bytesWritten) + { + const string HexTableLower = "0123456789abcdef"; + const string HexTableUpper = "0123456789ABCDEF"; + + var digits = 1; + var v = value; + if (v > 0xFFFFFFFF) + { + digits += 8; + v >>= 0x20; + } + if (v > 0xFFFF) + { + digits += 4; + v >>= 0x10; + } + if (v > 0xFF) + { + digits += 2; + v >>= 0x8; + } + if (v > 0xF) digits++; + + int paddingCount = (precision == ParsedFormat.NoPrecision) ? 0 : precision - digits; + if (paddingCount < 0) paddingCount = 0; + + int charsNeeded = digits + paddingCount; + Span span = buffer.NonPortableCast(); + + if (span.Length < charsNeeded) + { + bytesWritten = 0; + return false; + } + + string hexTable = useLower ? HexTableLower : HexTableUpper; + ref char utf16Bytes = ref span.DangerousGetPinnableReference(); + int idx = charsNeeded; + + for (v = value; digits-- > 0; v >>= 4) + Unsafe.Add(ref utf16Bytes, --idx) = hexTable[(int)(v & 0xF)]; + + while (paddingCount-- > 0) + Unsafe.Add(ref utf16Bytes, --idx) = '0'; + + bytesWritten = charsNeeded * sizeof(char); + return true; + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Formatting/Internal/IntegerFormatter.Utf8.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Formatting/Internal/IntegerFormatter.Utf8.cs new file mode 100644 index 000000000..d319b7123 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Formatting/Internal/IntegerFormatter.Utf8.cs @@ -0,0 +1,250 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; + +namespace System.Text +{ + internal static class InvariantUtf8IntegerFormatter + { + private const byte Minus = (byte)'-'; + private const byte Period = (byte)'.'; + private const byte Seperator = (byte)','; + + // Invariant formatting uses groups of 3 for each number group seperated by commas. + // ex. 1,234,567,890 + private const int GroupSize = 3; + + public static bool TryFormatDecimalInt64(long value, byte precision, Span buffer, out int bytesWritten) + { + int digitCount = FormattingHelpers.CountDigits(value); + int bytesNeeded = digitCount + (int)((value >> 63) & 1); + + if (buffer.Length < bytesNeeded) + { + bytesWritten = 0; + return false; + } + + ref byte utf8Bytes = ref buffer.DangerousGetPinnableReference(); + int idx = 0; + + if (value < 0) + { + Unsafe.Add(ref utf8Bytes, idx++) = Minus; + + // Abs(long.MinValue) == long.MaxValue + 1, so we need to re-route to unsigned to handle value + if (value == long.MinValue) + { + if (!TryFormatDecimalUInt64((ulong)long.MaxValue + 1, precision, buffer.Slice(1), out bytesWritten)) + return false; + + bytesWritten += 1; // Add the minus sign + return true; + } + + value = -value; + } + + if (precision != ParsedFormat.NoPrecision) + { + int leadingZeros = (int)precision - digitCount; + while (leadingZeros-- > 0) + Unsafe.Add(ref utf8Bytes, idx++) = (byte)'0'; + } + + idx += FormattingHelpers.WriteDigits(value, digitCount, ref utf8Bytes, idx); + + bytesWritten = idx; + return true; + } + + public static bool TryFormatDecimalUInt64(ulong value, byte precision, Span buffer, out int bytesWritten) + { + if (value <= long.MaxValue) + return TryFormatDecimalInt64((long)value, precision, buffer, out bytesWritten); + + // Remove a single digit from the number. This will get it below long.MaxValue + // Then we call the faster long version and follow-up with writing the last + // digit. This ends up being faster by a factor of 2 than to just do the entire + // operation using the unsigned versions. + value = FormattingHelpers.DivMod(value, 10, out ulong lastDigit); + + if (precision != ParsedFormat.NoPrecision && precision > 0) + precision -= 1; + + if (!TryFormatDecimalInt64((long)value, precision, buffer, out bytesWritten)) + return false; + + if (buffer.Length - 1 < bytesWritten) + { + bytesWritten = 0; + return false; + } + + ref byte utf8Bytes = ref buffer.DangerousGetPinnableReference(); + bytesWritten += FormattingHelpers.WriteDigits(lastDigit, 1, ref utf8Bytes, bytesWritten); + return true; + } + + public static bool TryFormatNumericInt64(long value, byte precision, Span buffer, out int bytesWritten) + { + int digitCount = FormattingHelpers.CountDigits(value); + int groupSeperators = (int)FormattingHelpers.DivMod(digitCount, GroupSize, out long firstGroup); + if (firstGroup == 0) + { + firstGroup = 3; + groupSeperators--; + } + + int trailingZeros = (precision == ParsedFormat.NoPrecision) ? 2 : precision; + int idx = (int)((value >> 63) & 1) + digitCount + groupSeperators; + + bytesWritten = idx; + if (trailingZeros > 0) + bytesWritten += trailingZeros + 1; // +1 for period. + + if (buffer.Length < bytesWritten) + { + bytesWritten = 0; + return false; + } + + ref byte utf8Bytes = ref buffer.DangerousGetPinnableReference(); + long v = value; + + if (v < 0) + { + Unsafe.Add(ref utf8Bytes, 0) = Minus; + + // Abs(long.MinValue) == long.MaxValue + 1, so we need to re-route to unsigned to handle value + if (v == long.MinValue) + { + if (!TryFormatNumericUInt64((ulong)long.MaxValue + 1, precision, buffer.Slice(1), out bytesWritten)) + return false; + + bytesWritten += 1; // Add the minus sign + return true; + } + + v = -v; + } + + // Write out the trailing zeros + if (trailingZeros > 0) + { + Unsafe.Add(ref utf8Bytes, idx) = Period; + FormattingHelpers.WriteDigits(0, trailingZeros, ref utf8Bytes, idx + 1); + } + + // Starting from the back, write each group of digits except the first group + while (digitCount > 3) + { + digitCount -= 3; + idx -= 3; + v = FormattingHelpers.DivMod(v, 1000, out long groupValue); + FormattingHelpers.WriteDigits(groupValue, 3, ref utf8Bytes, idx); + Unsafe.Add(ref utf8Bytes, --idx) = Seperator; + } + + // Write the first group of digits. + FormattingHelpers.WriteDigits(v, (int)firstGroup, ref utf8Bytes, idx - (int)firstGroup); + + return true; + } + + public static bool TryFormatNumericUInt64(ulong value, byte precision, Span buffer, out int bytesWritten) + { + if (value <= long.MaxValue) + return TryFormatNumericInt64((long)value, precision, buffer, out bytesWritten); + + // The ulong path is much slower than the long path here, so we are doing the last group + // inside this method plus the zero padding but routing to the long version for the rest. + value = FormattingHelpers.DivMod(value, 1000, out ulong lastGroup); + + if (!TryFormatNumericInt64((long)value, 0, buffer, out bytesWritten)) + return false; + + if (precision == ParsedFormat.NoPrecision) + precision = 2; + + int idx = bytesWritten; + + // Since this method routes entirely to the long version if the number is smaller than + // long.MaxValue, we are guaranteed to need to write 3 more digits here before the set + // of trailing zeros. + + bytesWritten += 4; // 3 digits + group seperator + if (precision > 0) + bytesWritten += precision + 1; // +1 for period. + + if (buffer.Length < bytesWritten) + { + bytesWritten = 0; + return false; + } + + ref byte utf8Bytes = ref buffer.DangerousGetPinnableReference(); + + // Write the last group + Unsafe.Add(ref utf8Bytes, idx++) = Seperator; + idx += FormattingHelpers.WriteDigits(lastGroup, 3, ref utf8Bytes, idx); + + // Write out the trailing zeros + if (precision > 0) + { + Unsafe.Add(ref utf8Bytes, idx) = Period; + FormattingHelpers.WriteDigits(0, precision, ref utf8Bytes, idx + 1); + } + + return true; + } + + public static bool TryFormatHexUInt64(ulong value, byte precision, bool useLower, Span buffer, out int bytesWritten) + { + const string HexTableLower = "0123456789abcdef"; + const string HexTableUpper = "0123456789ABCDEF"; + + var digits = 1; + var v = value; + if (v > 0xFFFFFFFF) + { + digits += 8; + v >>= 0x20; + } + if (v > 0xFFFF) + { + digits += 4; + v >>= 0x10; + } + if (v > 0xFF) + { + digits += 2; + v >>= 0x8; + } + if (v > 0xF) digits++; + + int paddingCount = (precision == ParsedFormat.NoPrecision) ? 0 : precision - digits; + if (paddingCount < 0) paddingCount = 0; + + bytesWritten = digits + paddingCount; + if (buffer.Length < bytesWritten) + { + bytesWritten = 0; + return false; + } + + string hexTable = useLower ? HexTableLower : HexTableUpper; + ref byte utf8Bytes = ref buffer.DangerousGetPinnableReference(); + int idx = bytesWritten; + + for (v = value; digits-- > 0; v >>= 4) + Unsafe.Add(ref utf8Bytes, --idx) = (byte)hexTable[(int)(v & 0xF)]; + + while (paddingCount-- > 0) + Unsafe.Add(ref utf8Bytes, --idx) = (byte)'0'; + + return true; + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Formatting/Internal/IntegerFormatter.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Formatting/Internal/IntegerFormatter.cs new file mode 100644 index 000000000..6653dedd1 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Formatting/Internal/IntegerFormatter.cs @@ -0,0 +1,497 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Diagnostics; + +namespace System.Text +{ + internal static class IntegerFormatter + { + internal static bool TryFormatInt64(long value, ulong mask, Span buffer, out int bytesWritten, ParsedFormat format, SymbolTable symbolTable) + { + if (value >= 0) + { + return TryFormatUInt64(unchecked((ulong)value), buffer, out bytesWritten, format, symbolTable); + } + else if (format.Symbol == 'x' || format.Symbol == 'X') + { + return TryFormatUInt64(unchecked((ulong)value) & mask, buffer, out bytesWritten, format, symbolTable); + } + else + { + int minusSignBytes = 0; + if (!symbolTable.TryEncode(SymbolTable.Symbol.MinusSign, buffer, out minusSignBytes)) + { + bytesWritten = 0; + return false; + } + + int digitBytes = 0; + if (!TryFormatUInt64(unchecked((ulong)-value), buffer.Slice(minusSignBytes), out digitBytes, format, symbolTable)) + { + bytesWritten = 0; + return false; + } + bytesWritten = digitBytes + minusSignBytes; + return true; + } + } + + internal static bool TryFormatUInt64(ulong value, Span buffer, out int bytesWritten, ParsedFormat format, SymbolTable symbolTable) + { + switch (format.Symbol) + { + case 'x': + case 'X': + if (symbolTable == SymbolTable.InvariantUtf8) + return TryFormatHexadecimalInvariantCultureUtf8(value, buffer, out bytesWritten, format); + else if (symbolTable == SymbolTable.InvariantUtf16) + return TryFormatHexadecimalInvariantCultureUtf16(value, buffer, out bytesWritten, format); + else + throw new NotSupportedException(); + + case 'd': + case 'D': + case 'g': + case 'G': + if (symbolTable == SymbolTable.InvariantUtf8) + return TryFormatDecimalInvariantCultureUtf8(value, buffer, out bytesWritten, format); + else if (symbolTable == SymbolTable.InvariantUtf16) + return TryFormatDecimalInvariantCultureUtf16(value, buffer, out bytesWritten, format); + else + return TryFormatDecimal(value, buffer, out bytesWritten, format, symbolTable); + + case 'n': + case 'N': + return TryFormatDecimal(value, buffer, out bytesWritten, format, symbolTable); + + default: + throw new FormatException(); + } + } + + private static bool TryFormatDecimalInvariantCultureUtf16(ulong value, Span buffer, out int bytesWritten, ParsedFormat format) + { + char symbol = char.ToUpperInvariant(format.Symbol); + Precondition.Require(symbol == 'D' || symbol == 'G'); + + // Count digits + var valueToCountDigits = value; + var digitsCount = 1; + while (valueToCountDigits >= 10UL) + { + valueToCountDigits = valueToCountDigits / 10UL; + digitsCount++; + } + + var index = 0; + var bytesCount = digitsCount * 2; + + // If format is D and precision is greater than digits count, append leading zeros + if ((symbol == 'D') && format.HasPrecision) + { + var leadingZerosCount = format.Precision - digitsCount; + if (leadingZerosCount > 0) + { + bytesCount += leadingZerosCount * 2; + } + + if (bytesCount > buffer.Length) + { + bytesWritten = 0; + return false; + } + + while (leadingZerosCount-- > 0) + { + buffer[index++] = (byte)'0'; + buffer[index++] = 0; + } + } + else if (bytesCount > buffer.Length) + { + bytesWritten = 0; + return false; + } + + index = bytesCount; + while (digitsCount-- > 0) + { + ulong digit = value % 10UL; + value /= 10UL; + buffer[--index] = 0; + buffer[--index] = (byte)(digit + (ulong)'0'); + } + + bytesWritten = bytesCount; + return true; + } + + private static bool TryFormatDecimalInvariantCultureUtf8(ulong value, Span buffer, out int bytesWritten, ParsedFormat format) + { + char symbol = char.ToUpperInvariant(format.Symbol); + Precondition.Require(symbol == 'D' || symbol == 'G'); + + // Count digits + var valueToCountDigits = value; + var digitsCount = 1; + while (valueToCountDigits >= 10UL) + { + valueToCountDigits = valueToCountDigits / 10UL; + digitsCount++; + } + + var index = 0; + var bytesCount = digitsCount; + + // If format is D and precision is greater than digits count, append leading zeros + if ((symbol == 'D') && format.HasPrecision) + { + var leadingZerosCount = format.Precision - digitsCount; + if (leadingZerosCount > 0) + { + bytesCount += leadingZerosCount; + } + + if (bytesCount > buffer.Length) + { + bytesWritten = 0; + return false; + } + + while (leadingZerosCount-- > 0) + { + buffer[index++] = (byte)'0'; + } + } + else if (bytesCount > buffer.Length) + { + bytesWritten = 0; + return false; + } + + index = bytesCount; + while (digitsCount-- > 0) + { + ulong digit = value % 10UL; + value /= 10UL; + buffer[--index] = (byte)(digit + (ulong)'0'); + } + + bytesWritten = bytesCount; + return true; + } + + private static bool TryFormatHexadecimalInvariantCultureUtf16(ulong value, Span buffer, out int bytesWritten, ParsedFormat format) + { + Precondition.Require(format.Symbol == 'X' || format.Symbol == 'x'); + + byte firstDigitOffset = (byte)'0'; + byte firstHexCharOffset = format.Symbol == 'x' ? (byte)'a' : (byte)'A'; + firstHexCharOffset -= 10; + + // Count amount of hex digits + var hexDigitsCount = 1; + ulong valueToCount = value; + if (valueToCount > 0xFFFFFFFF) + { + hexDigitsCount += 8; + valueToCount >>= 0x20; + } + if (valueToCount > 0xFFFF) + { + hexDigitsCount += 4; + valueToCount >>= 0x10; + } + if (valueToCount > 0xFF) + { + hexDigitsCount += 2; + valueToCount >>= 0x8; + } + if (valueToCount > 0xF) + { + hexDigitsCount++; + } + + var bytesCount = hexDigitsCount * 2; + + // Count leading zeros + var leadingZerosCount = format.HasPrecision ? format.Precision - hexDigitsCount : 0; + bytesCount += leadingZerosCount > 0 ? leadingZerosCount * 2 : 0; + + if (bytesCount > buffer.Length) + { + bytesWritten = 0; + return false; + } + + var index = bytesCount; + while (hexDigitsCount-- > 0) + { + byte digit = (byte)(value & 0xF); + value >>= 0x4; + digit += digit < 10 ? firstDigitOffset : firstHexCharOffset; + + buffer[--index] = 0; + buffer[--index] = digit; + } + + // Write leading zeros if any + while (leadingZerosCount-- > 0) + { + buffer[--index] = 0; + buffer[--index] = firstDigitOffset; + } + + bytesWritten = bytesCount; + return true; + } + + private static bool TryFormatHexadecimalInvariantCultureUtf8(ulong value, Span buffer, out int bytesWritten, ParsedFormat format) + { + Precondition.Require(format.Symbol == 'X' || format.Symbol == 'x'); + + byte firstDigitOffset = (byte)'0'; + byte firstHexCharOffset = format.Symbol == 'X' ? (byte)'A' : (byte)'a'; + firstHexCharOffset -= 10; + + // Count amount of hex digits + var hexDigitsCount = 1; + ulong valueToCount = value; + if (valueToCount > 0xFFFFFFFF) + { + hexDigitsCount += 8; + valueToCount >>= 0x20; + } + if (valueToCount > 0xFFFF) + { + hexDigitsCount += 4; + valueToCount >>= 0x10; + } + if (valueToCount > 0xFF) + { + hexDigitsCount += 2; + valueToCount >>= 0x8; + } + if (valueToCount > 0xF) + { + hexDigitsCount++; + } + + var bytesCount = hexDigitsCount; + + // Count leading zeros + var leadingZerosCount = format.HasPrecision ? format.Precision - hexDigitsCount : 0; + bytesCount += leadingZerosCount > 0 ? leadingZerosCount : 0; + + if (bytesCount > buffer.Length) + { + bytesWritten = 0; + return false; + } + + var index = bytesCount; + while (hexDigitsCount-- > 0) + { + byte digit = (byte)(value & 0xF); + value >>= 0x4; + digit += digit < 10 ? firstDigitOffset : firstHexCharOffset; + buffer[--index] = digit; + } + + // Write leading zeros if any + while (leadingZerosCount-- > 0) + { + buffer[--index] = firstDigitOffset; + } + + bytesWritten = bytesCount; + return true; + } + + // TODO: this whole routine is too slow. It does div and mod twice, which are both costly (especially that some JITs cannot optimize it). + // It does it twice to avoid reversing the formatted buffer, which can be tricky given it should handle arbitrary cultures. + // One optimization I thought we could do is to do div/mod once and store digits in a temp buffer (but that would allocate). Modification to the idea would be to store the digits in a local struct + // Another idea possibly worth tying would be to special case cultures that have constant digit size, and go back to the format + reverse buffer approach. + private static bool TryFormatDecimal(ulong value, Span buffer, out int bytesWritten, ParsedFormat format, SymbolTable symbolTable) + { + char symbol = char.ToUpperInvariant(format.Symbol); + Precondition.Require(symbol == 'D' || format.Symbol == 'G' || format.Symbol == 'N'); + + // Reverse value on decimal basis, count digits and trailing zeros before the decimal separator + ulong reversedValueExceptFirst = 0; + var digitsCount = 1; + var trailingZerosCount = 0; + + // We reverse the digits in numeric form because reversing encoded digits is hard and/or costly. + // If value contains 20 digits, its reversed value will not fit into ulong size. + // So reverse it till last digit (reversedValueExceptFirst will have all the digits except the first one). + while (value >= 10) + { + var digit = value % 10UL; + value = value / 10UL; + + if (reversedValueExceptFirst == 0 && digit == 0) + { + trailingZerosCount++; + } + else + { + reversedValueExceptFirst = reversedValueExceptFirst * 10UL + digit; + digitsCount++; + } + } + + bytesWritten = 0; + int digitBytes; + // If format is D and precision is greater than digitsCount + trailingZerosCount, append leading zeros + if (symbol == 'D' && format.HasPrecision) + { + var leadingZerosCount = format.Precision - digitsCount - trailingZerosCount; + while (leadingZerosCount-- > 0) + { + if (!symbolTable.TryEncode(SymbolTable.Symbol.D0, buffer.Slice(bytesWritten), out digitBytes)) + { + bytesWritten = 0; + return false; + } + bytesWritten += digitBytes; + } + } + + // Append first digit + if (!symbolTable.TryEncode((SymbolTable.Symbol)value, buffer.Slice(bytesWritten), out digitBytes)) + { + bytesWritten = 0; + return false; + } + bytesWritten += digitBytes; + digitsCount--; + + if (symbol == 'N') + { + const int GroupSize = 3; + + // Count amount of digits before first group separator. It will be reset to groupSize every time digitsLeftInGroup == zero + var digitsLeftInGroup = (digitsCount + trailingZerosCount) % GroupSize; + if (digitsLeftInGroup == 0) + { + if (digitsCount + trailingZerosCount > 0) + { + // There is a new group immediately after the first digit + if (!symbolTable.TryEncode(SymbolTable.Symbol.GroupSeparator, buffer.Slice(bytesWritten), out digitBytes)) + { + bytesWritten = 0; + return false; + } + bytesWritten += digitBytes; + } + digitsLeftInGroup = GroupSize; + } + + // Append digits + while (reversedValueExceptFirst > 0) + { + if (digitsLeftInGroup == 0) + { + if (!symbolTable.TryEncode(SymbolTable.Symbol.GroupSeparator, buffer.Slice(bytesWritten), out digitBytes)) + { + bytesWritten = 0; + return false; + } + bytesWritten += digitBytes; + digitsLeftInGroup = GroupSize; + } + + var nextDigit = reversedValueExceptFirst % 10UL; + reversedValueExceptFirst = reversedValueExceptFirst / 10UL; + + if (!symbolTable.TryEncode((SymbolTable.Symbol)nextDigit, buffer.Slice(bytesWritten), out digitBytes)) + { + bytesWritten = 0; + return false; + } + bytesWritten += digitBytes; + digitsLeftInGroup--; + } + + // Append trailing zeros if any + while (trailingZerosCount-- > 0) + { + if (digitsLeftInGroup == 0) + { + if (!symbolTable.TryEncode(SymbolTable.Symbol.GroupSeparator, buffer.Slice(bytesWritten), out digitBytes)) + { + bytesWritten = 0; + return false; + } + bytesWritten += digitBytes; + digitsLeftInGroup = GroupSize; + } + + if (!symbolTable.TryEncode(SymbolTable.Symbol.D0, buffer.Slice(bytesWritten), out digitBytes)) + { + bytesWritten = 0; + return false; + } + bytesWritten += digitBytes; + digitsLeftInGroup--; + } + } + else + { + while (reversedValueExceptFirst > 0) + { + var bufferSlice = buffer.Slice(bytesWritten); + var nextDigit = reversedValueExceptFirst % 10UL; + reversedValueExceptFirst = reversedValueExceptFirst / 10UL; + if (!symbolTable.TryEncode((SymbolTable.Symbol)nextDigit, bufferSlice, out digitBytes)) + { + bytesWritten = 0; + return false; + } + bytesWritten += digitBytes; + } + + // Append trailing zeros if any + while (trailingZerosCount-- > 0) + { + if (!symbolTable.TryEncode(SymbolTable.Symbol.D0, buffer.Slice(bytesWritten), out digitBytes)) + { + bytesWritten = 0; + return false; + } + bytesWritten += digitBytes; + } + } + + // If format is N and precision is not defined or is greater than zero, append trailing zeros after decimal point + if (symbol == 'N') + { + int trailingZerosAfterDecimalCount = format.HasPrecision ? format.Precision : 2; + + if (trailingZerosAfterDecimalCount > 0) + { + if (!symbolTable.TryEncode(SymbolTable.Symbol.DecimalSeparator, buffer.Slice(bytesWritten), out digitBytes)) + { + bytesWritten = 0; + return false; + } + bytesWritten += digitBytes; + + while (trailingZerosAfterDecimalCount-- > 0) + { + if (!symbolTable.TryEncode(SymbolTable.Symbol.D0, buffer.Slice(bytesWritten), out digitBytes)) + { + bytesWritten = 0; + return false; + } + bytesWritten += digitBytes; + } + } + } + + return true; + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Formatting/Internal/TimeFormatter.Utf16.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Formatting/Internal/TimeFormatter.Utf16.cs new file mode 100644 index 000000000..ee31a8ae1 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Formatting/Internal/TimeFormatter.Utf16.cs @@ -0,0 +1,402 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; + +namespace System.Text +{ + internal static class InvariantUtf16TimeFormatter + { + #region Constants + + private const int DefaultFractionDigits = 7; + + private const char Colon = ':'; + private const char Comma = ','; + private const char Minus = '-'; + private const char Period = '.'; + private const char Plus = '+'; + private const char Slash = '/'; + private const char Space = ' '; + + private const char TimeMarker = 'T'; + private const char UtcMarker = 'Z'; + + private const char GMT1 = 'G'; + private const char GMT2 = 'M'; + private const char GMT3 = 'T'; + + private const char GMT1Lowercase = 'g'; + private const char GMT2Lowercase = 'm'; + private const char GMT3Lowercase = 't'; + + private static readonly char[][] DayAbbreviations = new char[][] + { + new char[] { 'S', 'u', 'n' }, + new char[] { 'M', 'o', 'n' }, + new char[] { 'T', 'u', 'e' }, + new char[] { 'W', 'e', 'd' }, + new char[] { 'T', 'h', 'u' }, + new char[] { 'F', 'r', 'i' }, + new char[] { 'S', 'a', 't' }, + }; + + private static readonly char[][] MonthAbbreviations = new char[][] + { + new char[] { 'J', 'a', 'n' }, + new char[] { 'F', 'e', 'b' }, + new char[] { 'M', 'a', 'r' }, + new char[] { 'A', 'p', 'r' }, + new char[] { 'M', 'a', 'y' }, + new char[] { 'J', 'u', 'n' }, + new char[] { 'J', 'u', 'l' }, + new char[] { 'A', 'u', 'g' }, + new char[] { 'S', 'e', 'p' }, + new char[] { 'O', 'c', 't' }, + new char[] { 'N', 'o', 'v' }, + new char[] { 'D', 'e', 'c' }, + }; + + private static readonly char[][] DayAbbreviationsLowercase = new char[][] + { + new char[] { 's', 'u', 'n' }, + new char[] { 'm', 'o', 'n' }, + new char[] { 't', 'u', 'e' }, + new char[] { 'w', 'e', 'd' }, + new char[] { 't', 'h', 'u' }, + new char[] { 'f', 'r', 'i' }, + new char[] { 's', 'a', 't' }, + }; + + private static readonly char[][] MonthAbbreviationsLowercase = new char[][] + { + new char[] { 'j', 'a', 'n' }, + new char[] { 'f', 'e', 'b' }, + new char[] { 'm', 'a', 'r' }, + new char[] { 'a', 'p', 'r' }, + new char[] { 'm', 'a', 'y' }, + new char[] { 'j', 'u', 'n' }, + new char[] { 'j', 'u', 'l' }, + new char[] { 'a', 'u', 'g' }, + new char[] { 's', 'e', 'p' }, + new char[] { 'o', 'c', 't' }, + new char[] { 'n', 'o', 'v' }, + new char[] { 'd', 'e', 'c' }, + }; + + #endregion Constants + + public static bool TryFormatG(DateTime value, TimeSpan offset, Span buffer, out int bytesWritten) + { + const int MinimumCharsNeeded = 19; + + int charsNeeded = MinimumCharsNeeded; + if (offset != PrimitiveFormatter.NullOffset) + { + charsNeeded += 7; // Space['+'|'-']hh:ss + } + + bytesWritten = charsNeeded * sizeof(char); + if (buffer.Length < bytesWritten) + { + bytesWritten = 0; + return false; + } + + Span dst = buffer.NonPortableCast(); + ref char utf16Bytes = ref dst.DangerousGetPinnableReference(); + + FormattingHelpers.WriteDigits(value.Month, 2, ref utf16Bytes, 0); + Unsafe.Add(ref utf16Bytes, 2) = Slash; + + FormattingHelpers.WriteDigits(value.Day, 2, ref utf16Bytes, 3); + Unsafe.Add(ref utf16Bytes, 5) = Slash; + + FormattingHelpers.WriteDigits(value.Year, 4, ref utf16Bytes, 6); + Unsafe.Add(ref utf16Bytes, 10) = Space; + + FormattingHelpers.WriteDigits(value.Hour, 2, ref utf16Bytes, 11); + Unsafe.Add(ref utf16Bytes, 13) = Colon; + + FormattingHelpers.WriteDigits(value.Minute, 2, ref utf16Bytes, 14); + Unsafe.Add(ref utf16Bytes, 16) = Colon; + + FormattingHelpers.WriteDigits(value.Second, 2, ref utf16Bytes, 17); + + if (offset != PrimitiveFormatter.NullOffset) + { + Unsafe.Add(ref utf16Bytes, 19) = Space; + + long ticks = value.Ticks; + if (ticks < 0) + { + Unsafe.Add(ref utf16Bytes, 20) = Minus; + ticks = -ticks; + } + else + { + Unsafe.Add(ref utf16Bytes, 20) = Plus; + } + + FormattingHelpers.WriteDigits(offset.Hours, 2, ref utf16Bytes, 21); + Unsafe.Add(ref utf16Bytes, 23) = Colon; + FormattingHelpers.WriteDigits(offset.Minutes, 2, ref utf16Bytes, 24); + } + + return true; + } + + public static bool TryFormatO(DateTime value, TimeSpan offset, Span buffer, out int bytesWritten) + { + const int MinimumCharsNeeded = 27; + + int charsNeeded = MinimumCharsNeeded; + DateTimeKind kind = DateTimeKind.Local; + + if (offset == PrimitiveFormatter.NullOffset) + { + kind = value.Kind; + if (kind == DateTimeKind.Local) + { + offset = TimeZoneInfo.Local.GetUtcOffset(value); + charsNeeded += 6; + } + else if (kind == DateTimeKind.Utc) + { + charsNeeded += 1; + } + } + else + { + charsNeeded += 6; + } + + bytesWritten = charsNeeded * sizeof(char); + if (buffer.Length < bytesWritten) + { + bytesWritten = 0; + return false; + } + + Span dst = buffer.NonPortableCast(); + ref char utf16Bytes = ref dst.DangerousGetPinnableReference(); + + FormattingHelpers.WriteDigits(value.Year, 4, ref utf16Bytes, 0); + Unsafe.Add(ref utf16Bytes, 4) = Minus; + + FormattingHelpers.WriteDigits(value.Month, 2, ref utf16Bytes, 5); + Unsafe.Add(ref utf16Bytes, 7) = Minus; + + FormattingHelpers.WriteDigits(value.Day, 2, ref utf16Bytes, 8); + Unsafe.Add(ref utf16Bytes, 10) = TimeMarker; + + FormattingHelpers.WriteDigits(value.Hour, 2, ref utf16Bytes, 11); + Unsafe.Add(ref utf16Bytes, 13) = Colon; + + FormattingHelpers.WriteDigits(value.Minute, 2, ref utf16Bytes, 14); + Unsafe.Add(ref utf16Bytes, 16) = Colon; + + FormattingHelpers.WriteDigits(value.Second, 2, ref utf16Bytes, 17); + Unsafe.Add(ref utf16Bytes, 19) = Period; + + FormattingHelpers.DivMod(value.Ticks, TimeSpan.TicksPerSecond, out long fraction); + FormattingHelpers.WriteFractionDigits(fraction, DefaultFractionDigits, ref utf16Bytes, 20); + + if (kind == DateTimeKind.Local) + { + int hours = offset.Hours; + char sign = Plus; + + if (offset.Hours < 0) + { + hours = -offset.Hours; + sign = Minus; + } + + Unsafe.Add(ref utf16Bytes, 27) = sign; + FormattingHelpers.WriteDigits(hours, 2, ref utf16Bytes, 28); + Unsafe.Add(ref utf16Bytes, 30) = Colon; + FormattingHelpers.WriteDigits(offset.Minutes, 2, ref utf16Bytes, 31); + } + else if (kind == DateTimeKind.Utc) + { + Unsafe.Add(ref utf16Bytes, 27) = UtcMarker; + } + + return true; + } + + public static bool TryFormatRfc1123(DateTime value, Span buffer, out int bytesWritten) + { + const int CharsNeeded = 29; + + bytesWritten = CharsNeeded * sizeof(char); + if (buffer.Length < bytesWritten) + { + bytesWritten = 0; + return false; + } + + Span dst = buffer.NonPortableCast(); + ref char utf16Bytes = ref dst.DangerousGetPinnableReference(); + + var dayAbbrev = DayAbbreviations[(int)value.DayOfWeek]; + Unsafe.Add(ref utf16Bytes, 0) = dayAbbrev[0]; + Unsafe.Add(ref utf16Bytes, 1) = dayAbbrev[1]; + Unsafe.Add(ref utf16Bytes, 2) = dayAbbrev[2]; + Unsafe.Add(ref utf16Bytes, 3) = Comma; + Unsafe.Add(ref utf16Bytes, 4) = Space; + + FormattingHelpers.WriteDigits(value.Day, 2, ref utf16Bytes, 5); + Unsafe.Add(ref utf16Bytes, 7) = ' '; + + var monthAbbrev = MonthAbbreviations[value.Month - 1]; + Unsafe.Add(ref utf16Bytes, 8) = monthAbbrev[0]; + Unsafe.Add(ref utf16Bytes, 9) = monthAbbrev[1]; + Unsafe.Add(ref utf16Bytes, 10) = monthAbbrev[2]; + Unsafe.Add(ref utf16Bytes, 11) = Space; + + FormattingHelpers.WriteDigits(value.Year, 4, ref utf16Bytes, 12); + Unsafe.Add(ref utf16Bytes, 16) = Space; + + FormattingHelpers.WriteDigits(value.Hour, 2, ref utf16Bytes, 17); + Unsafe.Add(ref utf16Bytes, 19) = Colon; + + FormattingHelpers.WriteDigits(value.Minute, 2, ref utf16Bytes, 20); + Unsafe.Add(ref utf16Bytes, 22) = Colon; + + FormattingHelpers.WriteDigits(value.Second, 2, ref utf16Bytes, 23); + Unsafe.Add(ref utf16Bytes, 25) = Space; + + Unsafe.Add(ref utf16Bytes, 26) = GMT1; + Unsafe.Add(ref utf16Bytes, 27) = GMT2; + Unsafe.Add(ref utf16Bytes, 28) = GMT3; + + return true; + } + + public static bool TryFormatRfc1123Lowercase(DateTime value, Span buffer, out int bytesWritten) + { + const int CharsNeeded = 29; + + bytesWritten = CharsNeeded * sizeof(char); + if (buffer.Length < bytesWritten) + { + bytesWritten = 0; + return false; + } + + Span dst = buffer.NonPortableCast(); + ref char utf16Bytes = ref dst.DangerousGetPinnableReference(); + + var dayAbbrev = DayAbbreviationsLowercase[(int)value.DayOfWeek]; + Unsafe.Add(ref utf16Bytes, 0) = dayAbbrev[0]; + Unsafe.Add(ref utf16Bytes, 1) = dayAbbrev[1]; + Unsafe.Add(ref utf16Bytes, 2) = dayAbbrev[2]; + Unsafe.Add(ref utf16Bytes, 3) = Comma; + Unsafe.Add(ref utf16Bytes, 4) = Space; + + FormattingHelpers.WriteDigits(value.Day, 2, ref utf16Bytes, 5); + Unsafe.Add(ref utf16Bytes, 7) = ' '; + + var monthAbbrev = MonthAbbreviationsLowercase[value.Month - 1]; + Unsafe.Add(ref utf16Bytes, 8) = monthAbbrev[0]; + Unsafe.Add(ref utf16Bytes, 9) = monthAbbrev[1]; + Unsafe.Add(ref utf16Bytes, 10) = monthAbbrev[2]; + Unsafe.Add(ref utf16Bytes, 11) = Space; + + FormattingHelpers.WriteDigits(value.Year, 4, ref utf16Bytes, 12); + Unsafe.Add(ref utf16Bytes, 16) = Space; + + FormattingHelpers.WriteDigits(value.Hour, 2, ref utf16Bytes, 17); + Unsafe.Add(ref utf16Bytes, 19) = Colon; + + FormattingHelpers.WriteDigits(value.Minute, 2, ref utf16Bytes, 20); + Unsafe.Add(ref utf16Bytes, 22) = Colon; + + FormattingHelpers.WriteDigits(value.Second, 2, ref utf16Bytes, 23); + Unsafe.Add(ref utf16Bytes, 25) = Space; + + Unsafe.Add(ref utf16Bytes, 26) = GMT1Lowercase; + Unsafe.Add(ref utf16Bytes, 27) = GMT2Lowercase; + Unsafe.Add(ref utf16Bytes, 28) = GMT3Lowercase; + + return true; + } + + public static bool TryFormat(TimeSpan value, char format, Span buffer, out int bytesWritten) + { + bool longForm = (format == 'G'); + bool constant = (format == 't' || format == 'T' || format == 'c'); + + long ticks = value.Ticks; + int days = (int)FormattingHelpers.DivMod(ticks, TimeSpan.TicksPerDay, out long timeLeft); + + bool showSign = false; + if (ticks < 0) + { + showSign = true; + days = -days; + timeLeft = -timeLeft; + } + + int hours = (int)FormattingHelpers.DivMod(timeLeft, TimeSpan.TicksPerHour, out timeLeft); + int minutes = (int)FormattingHelpers.DivMod(timeLeft, TimeSpan.TicksPerMinute, out timeLeft); + int seconds = (int)FormattingHelpers.DivMod(timeLeft, TimeSpan.TicksPerSecond, out long fraction); + + int dayDigits = 0; + int hourDigits = (constant || longForm || hours > 9) ? 2 : 1; + int fractionDigits = 0; + + bytesWritten = hourDigits + 6; // [h]h:mm:ss + if (showSign) + bytesWritten += 1; // [-] + if (longForm || days > 0) + { + dayDigits = FormattingHelpers.CountDigits(days); + bytesWritten += dayDigits + 1; // [d'.'] + } + if (longForm || fraction > 0) + { + fractionDigits = (longForm || constant) ? DefaultFractionDigits : FormattingHelpers.CountFractionDigits(fraction); + bytesWritten += fractionDigits + 1; // ['.'fffffff] or ['.'FFFFFFF] for short-form + } + + bytesWritten *= sizeof(char); + if (buffer.Length < bytesWritten) + { + bytesWritten = 0; + return false; + } + + Span dst = buffer.NonPortableCast(); + ref char utf16Bytes = ref dst.DangerousGetPinnableReference(); + int idx = 0; + + if (showSign) + Unsafe.Add(ref utf16Bytes, idx++) = Minus; + + if (dayDigits > 0) + { + idx += FormattingHelpers.WriteDigits(days, dayDigits, ref utf16Bytes, idx); + Unsafe.Add(ref utf16Bytes, idx++) = constant ? Period : Colon; + } + + idx += FormattingHelpers.WriteDigits(hours, hourDigits, ref utf16Bytes, idx); + Unsafe.Add(ref utf16Bytes, idx++) = Colon; + + idx += FormattingHelpers.WriteDigits(minutes, 2, ref utf16Bytes, idx); + Unsafe.Add(ref utf16Bytes, idx++) = Colon; + + idx += FormattingHelpers.WriteDigits(seconds, 2, ref utf16Bytes, idx); + + if (fractionDigits > 0) + { + Unsafe.Add(ref utf16Bytes, idx++) = Period; + idx += FormattingHelpers.WriteFractionDigits(fraction, fractionDigits, ref utf16Bytes, idx); + } + + return true; + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Formatting/Internal/TimeFormatter.Utf8.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Formatting/Internal/TimeFormatter.Utf8.cs new file mode 100644 index 000000000..1e69e768e --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Formatting/Internal/TimeFormatter.Utf8.cs @@ -0,0 +1,394 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; + +namespace System.Text +{ + internal static class InvariantUtf8TimeFormatter + { + #region Constants + + private const int DefaultFractionDigits = 7; + + private const byte Colon = (byte)':'; + private const byte Comma = (byte)','; + private const byte Minus = (byte)'-'; + private const byte Period = (byte)'.'; + private const byte Plus = (byte)'+'; + private const byte Slash = (byte)'/'; + private const byte Space = (byte)' '; + + private const byte TimeMarker = (byte)'T'; + private const byte UtcMarker = (byte)'Z'; + + private const byte GMT1 = (byte)'G'; + private const byte GMT2 = (byte)'M'; + private const byte GMT3 = (byte)'T'; + + private const byte GMT1Lowercase = (byte)'g'; + private const byte GMT2Lowercase = (byte)'m'; + private const byte GMT3Lowercase = (byte)'t'; + + private static readonly byte[][] DayAbbreviations = new byte[][] + { + new byte[] { (byte)'S', (byte)'u', (byte)'n' }, + new byte[] { (byte)'M', (byte)'o', (byte)'n' }, + new byte[] { (byte)'T', (byte)'u', (byte)'e' }, + new byte[] { (byte)'W', (byte)'e', (byte)'d' }, + new byte[] { (byte)'T', (byte)'h', (byte)'u' }, + new byte[] { (byte)'F', (byte)'r', (byte)'i' }, + new byte[] { (byte)'S', (byte)'a', (byte)'t' }, + }; + + private static readonly byte[][] DayAbbreviationsLowercase = new byte[][] + { + new byte[] { (byte)'s', (byte)'u', (byte)'n' }, + new byte[] { (byte)'m', (byte)'o', (byte)'n' }, + new byte[] { (byte)'t', (byte)'u', (byte)'e' }, + new byte[] { (byte)'w', (byte)'e', (byte)'d' }, + new byte[] { (byte)'t', (byte)'h', (byte)'u' }, + new byte[] { (byte)'f', (byte)'r', (byte)'i' }, + new byte[] { (byte)'s', (byte)'a', (byte)'t' }, + }; + + private static readonly byte[][] MonthAbbreviations = new byte[][] + { + new byte[] { (byte)'J', (byte)'a', (byte)'n' }, + new byte[] { (byte)'F', (byte)'e', (byte)'b' }, + new byte[] { (byte)'M', (byte)'a', (byte)'r' }, + new byte[] { (byte)'A', (byte)'p', (byte)'r' }, + new byte[] { (byte)'M', (byte)'a', (byte)'y' }, + new byte[] { (byte)'J', (byte)'u', (byte)'n' }, + new byte[] { (byte)'J', (byte)'u', (byte)'l' }, + new byte[] { (byte)'A', (byte)'u', (byte)'g' }, + new byte[] { (byte)'S', (byte)'e', (byte)'p' }, + new byte[] { (byte)'O', (byte)'c', (byte)'t' }, + new byte[] { (byte)'N', (byte)'o', (byte)'v' }, + new byte[] { (byte)'D', (byte)'e', (byte)'c' }, + }; + + private static readonly byte[][] MonthAbbreviationsLowercase = new byte[][] +{ + new byte[] { (byte)'j', (byte)'a', (byte)'n' }, + new byte[] { (byte)'f', (byte)'e', (byte)'b' }, + new byte[] { (byte)'m', (byte)'a', (byte)'r' }, + new byte[] { (byte)'a', (byte)'p', (byte)'r' }, + new byte[] { (byte)'m', (byte)'a', (byte)'y' }, + new byte[] { (byte)'j', (byte)'u', (byte)'n' }, + new byte[] { (byte)'j', (byte)'u', (byte)'l' }, + new byte[] { (byte)'a', (byte)'u', (byte)'g' }, + new byte[] { (byte)'s', (byte)'e', (byte)'p' }, + new byte[] { (byte)'o', (byte)'c', (byte)'t' }, + new byte[] { (byte)'n', (byte)'o', (byte)'v' }, + new byte[] { (byte)'d', (byte)'e', (byte)'c' }, +}; + + #endregion Constants + + public static bool TryFormatG(DateTime value, TimeSpan offset, Span buffer, out int bytesWritten) + { + const int MinimumBytesNeeded = 19; + + bytesWritten = MinimumBytesNeeded; + if (offset != PrimitiveFormatter.NullOffset) + { + bytesWritten += 7; // Space['+'|'-']hh:ss + } + + if (buffer.Length < bytesWritten) + { + bytesWritten = 0; + return false; + } + + ref byte utf8Bytes = ref buffer.DangerousGetPinnableReference(); + + FormattingHelpers.WriteDigits(value.Month, 2, ref utf8Bytes, 0); + Unsafe.Add(ref utf8Bytes, 2) = Slash; + + FormattingHelpers.WriteDigits(value.Day, 2, ref utf8Bytes, 3); + Unsafe.Add(ref utf8Bytes, 5) = Slash; + + FormattingHelpers.WriteDigits(value.Year, 4, ref utf8Bytes, 6); + Unsafe.Add(ref utf8Bytes, 10) = Space; + + FormattingHelpers.WriteDigits(value.Hour, 2, ref utf8Bytes, 11); + Unsafe.Add(ref utf8Bytes, 13) = Colon; + + FormattingHelpers.WriteDigits(value.Minute, 2, ref utf8Bytes, 14); + Unsafe.Add(ref utf8Bytes, 16) = Colon; + + FormattingHelpers.WriteDigits(value.Second, 2, ref utf8Bytes, 17); + + if (offset != PrimitiveFormatter.NullOffset) + { + Unsafe.Add(ref utf8Bytes, 19) = Space; + + long ticks = value.Ticks; + if (ticks < 0) + { + Unsafe.Add(ref utf8Bytes, 20) = Minus; + ticks = -ticks; + } + else + { + Unsafe.Add(ref utf8Bytes, 20) = Plus; + } + + FormattingHelpers.WriteDigits(offset.Hours, 2, ref utf8Bytes, 21); + Unsafe.Add(ref utf8Bytes, 23) = Colon; + FormattingHelpers.WriteDigits(offset.Minutes, 2, ref utf8Bytes, 24); + } + + return true; + } + + public static bool TryFormatO(DateTime value, TimeSpan offset, Span buffer, out int bytesWritten) + { + const int MinimumBytesNeeded = 27; + + bytesWritten = MinimumBytesNeeded; + DateTimeKind kind = DateTimeKind.Local; + + if (offset == PrimitiveFormatter.NullOffset) + { + kind = value.Kind; + if (kind == DateTimeKind.Local) + { + offset = TimeZoneInfo.Local.GetUtcOffset(value); + bytesWritten += 6; + } + else if (kind == DateTimeKind.Utc) + { + bytesWritten += 1; + } + } + else + { + bytesWritten += 6; + } + + if (buffer.Length < bytesWritten) + { + bytesWritten = 0; + return false; + } + + ref byte utf8Bytes = ref buffer.DangerousGetPinnableReference(); + + FormattingHelpers.WriteDigits(value.Year, 4, ref utf8Bytes, 0); + Unsafe.Add(ref utf8Bytes, 4) = Minus; + + FormattingHelpers.WriteDigits(value.Month, 2, ref utf8Bytes, 5); + Unsafe.Add(ref utf8Bytes, 7) = Minus; + + FormattingHelpers.WriteDigits(value.Day, 2, ref utf8Bytes, 8); + Unsafe.Add(ref utf8Bytes, 10) = TimeMarker; + + FormattingHelpers.WriteDigits(value.Hour, 2, ref utf8Bytes, 11); + Unsafe.Add(ref utf8Bytes, 13) = Colon; + + FormattingHelpers.WriteDigits(value.Minute, 2, ref utf8Bytes, 14); + Unsafe.Add(ref utf8Bytes, 16) = Colon; + + FormattingHelpers.WriteDigits(value.Second, 2, ref utf8Bytes, 17); + Unsafe.Add(ref utf8Bytes, 19) = Period; + + FormattingHelpers.DivMod(value.Ticks, TimeSpan.TicksPerSecond, out long fraction); + FormattingHelpers.WriteDigits(fraction, DefaultFractionDigits, ref utf8Bytes, 20); + + if (kind == DateTimeKind.Local) + { + int hours = offset.Hours; + byte sign = Plus; + + if (offset.Hours < 0) + { + hours = -offset.Hours; + sign = Minus; + } + + Unsafe.Add(ref utf8Bytes, 27) = sign; + FormattingHelpers.WriteDigits(hours, 2, ref utf8Bytes, 28); + Unsafe.Add(ref utf8Bytes, 30) = Colon; + FormattingHelpers.WriteDigits(offset.Minutes, 2, ref utf8Bytes, 31); + } + else if (kind == DateTimeKind.Utc) + { + Unsafe.Add(ref utf8Bytes, 27) = UtcMarker; + } + + return true; + } + + public static bool TryFormatRfc1123(DateTime value, Span buffer, out int bytesWritten) + { + const int BytesNeeded = 29; + + bytesWritten = BytesNeeded; + if (buffer.Length < bytesWritten) + { + bytesWritten = 0; + return false; + } + + ref byte utf8Bytes = ref buffer.DangerousGetPinnableReference(); + + var dayAbbrev = DayAbbreviations[(int)value.DayOfWeek]; + Unsafe.Add(ref utf8Bytes, 0) = dayAbbrev[0]; + Unsafe.Add(ref utf8Bytes, 1) = dayAbbrev[1]; + Unsafe.Add(ref utf8Bytes, 2) = dayAbbrev[2]; + Unsafe.Add(ref utf8Bytes, 3) = Comma; + Unsafe.Add(ref utf8Bytes, 4) = Space; + + FormattingHelpers.WriteDigits(value.Day, 2, ref utf8Bytes, 5); + Unsafe.Add(ref utf8Bytes, 7) = (byte)' '; + + var monthAbbrev = MonthAbbreviations[value.Month - 1]; + Unsafe.Add(ref utf8Bytes, 8) = monthAbbrev[0]; + Unsafe.Add(ref utf8Bytes, 9) = monthAbbrev[1]; + Unsafe.Add(ref utf8Bytes, 10) = monthAbbrev[2]; + Unsafe.Add(ref utf8Bytes, 11) = Space; + + FormattingHelpers.WriteDigits(value.Year, 4, ref utf8Bytes, 12); + Unsafe.Add(ref utf8Bytes, 16) = Space; + + FormattingHelpers.WriteDigits(value.Hour, 2, ref utf8Bytes, 17); + Unsafe.Add(ref utf8Bytes, 19) = Colon; + + FormattingHelpers.WriteDigits(value.Minute, 2, ref utf8Bytes, 20); + Unsafe.Add(ref utf8Bytes, 22) = Colon; + + FormattingHelpers.WriteDigits(value.Second, 2, ref utf8Bytes, 23); + Unsafe.Add(ref utf8Bytes, 25) = Space; + + Unsafe.Add(ref utf8Bytes, 26) = GMT1; + Unsafe.Add(ref utf8Bytes, 27) = GMT2; + Unsafe.Add(ref utf8Bytes, 28) = GMT3; + + return true; + } + + public static bool TryFormatRfc1123Lowercase(DateTime value, Span buffer, out int bytesWritten) + { + const int BytesNeeded = 29; + + bytesWritten = BytesNeeded; + if (buffer.Length < bytesWritten) + { + bytesWritten = 0; + return false; + } + + ref byte utf8Bytes = ref buffer.DangerousGetPinnableReference(); + + var dayAbbrev = DayAbbreviationsLowercase[(int)value.DayOfWeek]; + Unsafe.Add(ref utf8Bytes, 0) = dayAbbrev[0]; + Unsafe.Add(ref utf8Bytes, 1) = dayAbbrev[1]; + Unsafe.Add(ref utf8Bytes, 2) = dayAbbrev[2]; + Unsafe.Add(ref utf8Bytes, 3) = Comma; + Unsafe.Add(ref utf8Bytes, 4) = Space; + + FormattingHelpers.WriteDigits(value.Day, 2, ref utf8Bytes, 5); + Unsafe.Add(ref utf8Bytes, 7) = (byte)' '; + + var monthAbbrev = MonthAbbreviationsLowercase[value.Month - 1]; + Unsafe.Add(ref utf8Bytes, 8) = monthAbbrev[0]; + Unsafe.Add(ref utf8Bytes, 9) = monthAbbrev[1]; + Unsafe.Add(ref utf8Bytes, 10) = monthAbbrev[2]; + Unsafe.Add(ref utf8Bytes, 11) = Space; + + FormattingHelpers.WriteDigits(value.Year, 4, ref utf8Bytes, 12); + Unsafe.Add(ref utf8Bytes, 16) = Space; + + FormattingHelpers.WriteDigits(value.Hour, 2, ref utf8Bytes, 17); + Unsafe.Add(ref utf8Bytes, 19) = Colon; + + FormattingHelpers.WriteDigits(value.Minute, 2, ref utf8Bytes, 20); + Unsafe.Add(ref utf8Bytes, 22) = Colon; + + FormattingHelpers.WriteDigits(value.Second, 2, ref utf8Bytes, 23); + Unsafe.Add(ref utf8Bytes, 25) = Space; + + Unsafe.Add(ref utf8Bytes, 26) = GMT1Lowercase; + Unsafe.Add(ref utf8Bytes, 27) = GMT2Lowercase; + Unsafe.Add(ref utf8Bytes, 28) = GMT3Lowercase; + + return true; + } + + public static bool TryFormat(TimeSpan value, char format, Span buffer, out int bytesWritten) + { + bool longForm = (format == 'G'); + bool constant = (format == 't' || format == 'T' || format == 'c'); + + long ticks = value.Ticks; + int days = (int)FormattingHelpers.DivMod(ticks, TimeSpan.TicksPerDay, out long timeLeft); + + bool showSign = false; + if (ticks < 0) + { + showSign = true; + days = -days; + timeLeft = -timeLeft; + } + + int hours = (int)FormattingHelpers.DivMod(timeLeft, TimeSpan.TicksPerHour, out timeLeft); + int minutes = (int)FormattingHelpers.DivMod(timeLeft, TimeSpan.TicksPerMinute, out timeLeft); + int seconds = (int)FormattingHelpers.DivMod(timeLeft, TimeSpan.TicksPerSecond, out long fraction); + + int dayDigits = 0; + int hourDigits = (constant || longForm || hours > 9) ? 2 : 1; + int fractionDigits = 0; + + bytesWritten = hourDigits + 6; // [h]h:mm:ss + if (showSign) + bytesWritten += 1; // [-] + if (longForm || days > 0) + { + dayDigits = FormattingHelpers.CountDigits(days); + bytesWritten += dayDigits + 1; // [d'.'] + } + if (longForm || fraction > 0) + { + fractionDigits = (longForm || constant) ? DefaultFractionDigits : FormattingHelpers.CountFractionDigits(fraction); + bytesWritten += fractionDigits + 1; // ['.'fffffff] or ['.'FFFFFFF] for short-form + } + + if (buffer.Length < bytesWritten) + { + bytesWritten = 0; + return false; + } + + ref byte utf8Bytes = ref buffer.DangerousGetPinnableReference(); + int idx = 0; + + if (showSign) + Unsafe.Add(ref utf8Bytes, idx++) = Minus; + + if (dayDigits > 0) + { + idx += FormattingHelpers.WriteDigits(days, dayDigits, ref utf8Bytes, idx); + Unsafe.Add(ref utf8Bytes, idx++) = constant ? Period : Colon; + } + + idx += FormattingHelpers.WriteDigits(hours, hourDigits, ref utf8Bytes, idx); + Unsafe.Add(ref utf8Bytes, idx++) = Colon; + + idx += FormattingHelpers.WriteDigits(minutes, 2, ref utf8Bytes, idx); + Unsafe.Add(ref utf8Bytes, idx++) = Colon; + + idx += FormattingHelpers.WriteDigits(seconds, 2, ref utf8Bytes, idx); + + if (fractionDigits > 0) + { + Unsafe.Add(ref utf8Bytes, idx++) = Period; + idx += FormattingHelpers.WriteFractionDigits(fraction, fractionDigits, ref utf8Bytes, idx); + } + + return true; + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Formatting/PrimitiveFormatter.Guid.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Formatting/PrimitiveFormatter.Guid.cs new file mode 100644 index 000000000..af8e5a309 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Formatting/PrimitiveFormatter.Guid.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace System.Text +{ + public static partial class PrimitiveFormatter + { + public static bool TryFormat(this Guid value, Span buffer, out int bytesWritten, ParsedFormat format = default, SymbolTable symbolTable = null) + { + symbolTable = symbolTable ?? SymbolTable.InvariantUtf8; + + if (symbolTable == SymbolTable.InvariantUtf8) + return InvariantUtf8GuidFormatter.TryFormat(value, buffer, out bytesWritten, format); + else if (symbolTable == SymbolTable.InvariantUtf16) + return InvariantUtf16GuidFormatter.TryFormat(value, buffer, out bytesWritten, format); + else + throw new NotImplementedException(); + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Formatting/PrimitiveFormatter.Integer.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Formatting/PrimitiveFormatter.Integer.cs new file mode 100644 index 000000000..91e16f8a9 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Formatting/PrimitiveFormatter.Integer.cs @@ -0,0 +1,187 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace System.Text +{ + /// + /// Pseudo-implementations of IBufferFormattable interface for primitive types + /// + /// + /// Holds extension methods for formatting types that cannot implement IBufferFormattable for layering reasons. + /// + public static partial class PrimitiveFormatter + { + public static bool TryFormat(this byte value, Span buffer, out int bytesWritten, ParsedFormat format = default, SymbolTable symbolTable = null) + { + return TryFormatCore(value, buffer, out bytesWritten, format, symbolTable); + } + + public static bool TryFormat(this sbyte value, Span buffer, out int bytesWritten, ParsedFormat format = default, SymbolTable symbolTable = null) + { + return TryFormatCore(value, 0xff, buffer, out bytesWritten, format, symbolTable); + } + + public static bool TryFormat(this ushort value, Span buffer, out int bytesWritten, ParsedFormat format = default, SymbolTable symbolTable = null) + { + return TryFormatCore(value, buffer, out bytesWritten, format, symbolTable); + } + + public static bool TryFormat(this short value, Span buffer, out int bytesWritten, ParsedFormat format = default, SymbolTable symbolTable = null) + { + return TryFormatCore(value, 0xffff, buffer, out bytesWritten, format, symbolTable); + } + + public static bool TryFormat(this uint value, Span buffer, out int bytesWritten, ParsedFormat format = default, SymbolTable symbolTable = null) + { + return TryFormatCore(value, buffer, out bytesWritten, format, symbolTable); + } + + public static bool TryFormat(this int value, Span buffer, out int bytesWritten, ParsedFormat format = default, SymbolTable symbolTable = null) + { + return TryFormatCore(value, 0xffffffff, buffer, out bytesWritten, format, symbolTable); + } + + public static bool TryFormat(this ulong value, Span buffer, out int bytesWritten, ParsedFormat format = default, SymbolTable symbolTable = null) + { + return TryFormatCore(value, buffer, out bytesWritten, format, symbolTable); + } + + public static bool TryFormat(this long value, Span buffer, out int bytesWritten, ParsedFormat format = default, SymbolTable symbolTable = null) + { + return TryFormatCore(value, 0xffffffffffffffff, buffer, out bytesWritten, format, symbolTable); + } + + static bool TryFormatCore(long value, ulong mask, Span buffer, out int bytesWritten, ParsedFormat format, SymbolTable symbolTable) + { + symbolTable = symbolTable ?? SymbolTable.InvariantUtf8; + + if (format.IsDefault) + { + format = 'G'; + } + + if (symbolTable == SymbolTable.InvariantUtf8) + return TryFormatInvariantUtf8(value, mask, buffer, out bytesWritten, format); + else if (symbolTable == SymbolTable.InvariantUtf16) + return TryFormatInvariantUtf16(value, mask, buffer, out bytesWritten, format); + else + return IntegerFormatter.TryFormatInt64(value, mask, buffer, out bytesWritten, format, symbolTable); + } + + static bool TryFormatCore(ulong value, Span buffer, out int bytesWritten, ParsedFormat format, SymbolTable symbolTable) + { + symbolTable = symbolTable ?? SymbolTable.InvariantUtf8; + + if (symbolTable == SymbolTable.InvariantUtf8) + return TryFormatInvariantUtf8(value, buffer, out bytesWritten, format); + else if (symbolTable == SymbolTable.InvariantUtf16) + return TryFormatInvariantUtf16(value, buffer, out bytesWritten, format); + else + return IntegerFormatter.TryFormatUInt64(value, buffer, out bytesWritten, format, symbolTable); + } + + static bool TryFormatInvariantUtf8(long value, ulong mask, Span buffer, out int bytesWritten, ParsedFormat format) + { + switch (format.Symbol) + { + case (char)0: + case 'd': + case 'D': + case 'G': + case 'g': + return InvariantUtf8IntegerFormatter.TryFormatDecimalInt64(value, format.Precision, buffer, out bytesWritten); + + case 'n': + case 'N': + return InvariantUtf8IntegerFormatter.TryFormatNumericInt64(value, format.Precision, buffer, out bytesWritten); + + case 'x': + return InvariantUtf8IntegerFormatter.TryFormatHexUInt64((ulong)value & mask, format.Precision, true, buffer, out bytesWritten); + + case 'X': + return InvariantUtf8IntegerFormatter.TryFormatHexUInt64((ulong)value & mask, format.Precision, false, buffer, out bytesWritten); + + default: + throw new NotSupportedException(); + } + } + + static bool TryFormatInvariantUtf8(ulong value, Span buffer, out int bytesWritten, ParsedFormat format) + { + switch (format.Symbol) + { + case (char)0: + case 'd': + case 'D': + case 'G': + case 'g': + return InvariantUtf8IntegerFormatter.TryFormatDecimalUInt64(value, format.Precision, buffer, out bytesWritten); + + case 'n': + case 'N': + return InvariantUtf8IntegerFormatter.TryFormatNumericUInt64(value, format.Precision, buffer, out bytesWritten); + + case 'x': + return InvariantUtf8IntegerFormatter.TryFormatHexUInt64(value, format.Precision, true, buffer, out bytesWritten); + + case 'X': + return InvariantUtf8IntegerFormatter.TryFormatHexUInt64(value, format.Precision, false, buffer, out bytesWritten); + + default: + throw new NotSupportedException(); + } + } + + static bool TryFormatInvariantUtf16(long value, ulong mask, Span buffer, out int bytesWritten, ParsedFormat format) + { + switch (format.Symbol) + { + case (char)0: + case 'd': + case 'D': + case 'G': + case 'g': + return InvariantUtf16IntegerFormatter.TryFormatDecimalInt64(value, format.Precision, buffer, out bytesWritten); + + case 'n': + case 'N': + return InvariantUtf16IntegerFormatter.TryFormatNumericInt64(value, format.Precision, buffer, out bytesWritten); + + case 'x': + return InvariantUtf16IntegerFormatter.TryFormatHexUInt64((ulong)value & mask, format.Precision, true, buffer, out bytesWritten); + + case 'X': + return InvariantUtf16IntegerFormatter.TryFormatHexUInt64((ulong)value & mask, format.Precision, false, buffer, out bytesWritten); + + default: + throw new NotSupportedException(); + } + } + + static bool TryFormatInvariantUtf16(ulong value, Span buffer, out int bytesWritten, ParsedFormat format) + { + switch (format.Symbol) + { + case (char)0: + case 'd': + case 'D': + case 'G': + case 'g': + return InvariantUtf16IntegerFormatter.TryFormatDecimalUInt64(value, format.Precision, buffer, out bytesWritten); + + case 'n': + case 'N': + return InvariantUtf16IntegerFormatter.TryFormatNumericUInt64(value, format.Precision, buffer, out bytesWritten); + + case 'x': + return InvariantUtf16IntegerFormatter.TryFormatHexUInt64(value, format.Precision, true, buffer, out bytesWritten); + + case 'X': + return InvariantUtf16IntegerFormatter.TryFormatHexUInt64(value, format.Precision, false, buffer, out bytesWritten); + + default: + throw new NotSupportedException(); + } + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Formatting/PrimitiveFormatter.Time.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Formatting/PrimitiveFormatter.Time.cs new file mode 100644 index 000000000..72b30efa2 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Formatting/PrimitiveFormatter.Time.cs @@ -0,0 +1,160 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace System.Text +{ + public static partial class PrimitiveFormatter + { + internal static readonly TimeSpan NullOffset = TimeSpan.MinValue; + + public static bool TryFormat(this DateTimeOffset value, Span buffer, out int bytesWritten, ParsedFormat format = default, SymbolTable symbolTable = null) + { + TimeSpan offset = NullOffset; + char symbol = format.Symbol; + if (format.IsDefault) + { + symbol = 'G'; + offset = value.Offset; + } + + symbolTable = symbolTable ?? SymbolTable.InvariantUtf8; + + switch (symbol) + { + case 'R': + return TryFormatDateTimeRfc1123(value.UtcDateTime, buffer, out bytesWritten, symbolTable); + + case 'l': + return TryFormatDateTimeRfc1123Lowercase(value.UtcDateTime, buffer, out bytesWritten, symbolTable); + + case 'O': + return TryFormatDateTimeFormatO(value.DateTime, value.Offset, buffer, out bytesWritten, symbolTable); + + case 'G': + return TryFormatDateTimeFormatG(value.DateTime, offset, buffer, out bytesWritten, symbolTable); + + default: + ThrowNotImplemented(); + bytesWritten = 0; + return false; + } + } + + public static bool TryFormat(this DateTime value, Span buffer, out int bytesWritten, ParsedFormat format = default, SymbolTable symbolTable = null) + { + char symbol = format.IsDefault ? 'G' : format.Symbol; + + symbolTable = symbolTable ?? SymbolTable.InvariantUtf8; + + switch (symbol) + { + case 'R': + return TryFormatDateTimeRfc1123(value, buffer, out bytesWritten, symbolTable); + + case 'l': + return TryFormatDateTimeRfc1123Lowercase(value, buffer, out bytesWritten, symbolTable); + + case 'O': + return TryFormatDateTimeFormatO(value, NullOffset, buffer, out bytesWritten, symbolTable); + + case 'G': + return TryFormatDateTimeFormatG(value, NullOffset, buffer, out bytesWritten, symbolTable); + + default: + ThrowNotImplemented(); + bytesWritten = 0; + return false; + } + } + + public static bool TryFormat(this TimeSpan value, Span buffer, out int bytesWritten, ParsedFormat format = default, SymbolTable symbolTable = null) + { + char symbol = format.IsDefault ? 'c' : format.Symbol; + + Precondition.Require(symbol == 'G' || symbol == 'g' || symbol == 'c' || symbol == 't' || symbol == 'T'); + + symbolTable = symbolTable ?? SymbolTable.InvariantUtf8; + + return TryFormatTimeSpan(value, symbol, buffer, out bytesWritten, symbolTable); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static bool TryFormatDateTimeFormatG(DateTime value, TimeSpan offset, Span buffer, out int bytesWritten, SymbolTable symbolTable) + { + // for now it only works for invariant culture + if (symbolTable == SymbolTable.InvariantUtf8) + return InvariantUtf8TimeFormatter.TryFormatG(value, offset, buffer, out bytesWritten); + else if (symbolTable == SymbolTable.InvariantUtf16) + return InvariantUtf16TimeFormatter.TryFormatG(value, offset, buffer, out bytesWritten); + + ThrowNotImplemented(); + bytesWritten = 0; + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static bool TryFormatDateTimeFormatO(DateTime value, TimeSpan offset, Span buffer, out int bytesWritten, SymbolTable symbolTable) + { + // for now it only works for invariant culture + if (symbolTable == SymbolTable.InvariantUtf8) + return InvariantUtf8TimeFormatter.TryFormatO(value, offset, buffer, out bytesWritten); + else if (symbolTable == SymbolTable.InvariantUtf16) + return InvariantUtf16TimeFormatter.TryFormatO(value, offset, buffer, out bytesWritten); + + ThrowNotImplemented(); + bytesWritten = 0; + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static bool TryFormatDateTimeRfc1123(DateTime value, Span buffer, out int bytesWritten, SymbolTable symbolTable) + { + // for now it only works for invariant culture + if (symbolTable == SymbolTable.InvariantUtf8) + return InvariantUtf8TimeFormatter.TryFormatRfc1123(value, buffer, out bytesWritten); + else if (symbolTable == SymbolTable.InvariantUtf16) + return InvariantUtf16TimeFormatter.TryFormatRfc1123(value, buffer, out bytesWritten); + + ThrowNotImplemented(); + bytesWritten = 0; + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static bool TryFormatDateTimeRfc1123Lowercase(DateTime value, Span buffer, out int bytesWritten, SymbolTable symbolTable) + { + // for now it only works for invariant culture + if (symbolTable == SymbolTable.InvariantUtf8) + return InvariantUtf8TimeFormatter.TryFormatRfc1123Lowercase(value, buffer, out bytesWritten); + else if (symbolTable == SymbolTable.InvariantUtf16) + return InvariantUtf16TimeFormatter.TryFormatRfc1123Lowercase(value, buffer, out bytesWritten); + + ThrowNotImplemented(); + bytesWritten = 0; + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static bool TryFormatTimeSpan(TimeSpan value, char format, Span buffer, out int bytesWritten, SymbolTable symbolTable) + { + // for now it only works for invariant culture + if (symbolTable == SymbolTable.InvariantUtf8) + return InvariantUtf8TimeFormatter.TryFormat(value, format, buffer, out bytesWritten); + else if (symbolTable == SymbolTable.InvariantUtf16) + return InvariantUtf16TimeFormatter.TryFormat(value, format, buffer, out bytesWritten); + + ThrowNotImplemented(); + bytesWritten = 0; + return false; + } + + // Methods won't be inlined if they contain a throw, so we factor out the throw to a separate method. + static void ThrowNotImplemented() + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Formatting/PrimitiveFormatter_float.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Formatting/PrimitiveFormatter_float.cs new file mode 100644 index 000000000..bd84e341a --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Formatting/PrimitiveFormatter_float.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Diagnostics; + +namespace System.Text +{ + public static partial class PrimitiveFormatter + { + public static bool TryFormat(this double value, Span buffer, out int bytesWritten, ParsedFormat format = default, SymbolTable symbolTable = null) + { + if (format.IsDefault) + { + format = 'G'; + } + Precondition.Require(format.Symbol == 'G'); + symbolTable = symbolTable ?? SymbolTable.InvariantUtf8; + return FloatFormatter.TryFormatNumber(value, false, buffer, out bytesWritten, format, symbolTable); + } + + public static bool TryFormat(this float value, Span buffer, out int bytesWritten, ParsedFormat format = default, SymbolTable symbolTable = null) + { + if (format.IsDefault) + { + format = 'G'; + } + Precondition.Require(format.Symbol == 'G'); + symbolTable = symbolTable ?? SymbolTable.InvariantUtf8; + return FloatFormatter.TryFormatNumber(value, true, buffer, out bytesWritten, format, symbolTable); + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/ParsedFormat.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/ParsedFormat.cs new file mode 100644 index 000000000..12760d626 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/ParsedFormat.cs @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace System.Text +{ + public struct ParsedFormat + { + public const byte NoPrecision = byte.MaxValue; + public const byte MaxPrecision = 99; + + private byte _format; + private byte _precision; + + public char Symbol => (char)_format; + public byte Precision => _precision; + public bool HasPrecision => _precision != NoPrecision; + public bool IsDefault => _format == 0 && _precision == 0; + + public ParsedFormat(char symbol, byte precision = NoPrecision) + { + if (precision != NoPrecision && precision > MaxPrecision) + throw new ArgumentOutOfRangeException("precision"); + if (symbol != (byte)symbol) + throw new ArgumentOutOfRangeException("symbol"); + + _format = (byte)symbol; + _precision = precision; + } + + public static implicit operator ParsedFormat(char symbol) => new ParsedFormat(symbol); + + public static ParsedFormat Parse(ReadOnlySpan format) + { + if (format.IsEmpty) + return default; + + char specifier = format[0]; + byte precision = NoPrecision; + + if (format.Length > 1) + { + var span = format.Slice(1); + + if (!PrimitiveParser.InvariantUtf16.TryParseByte(span, out precision)) + throw new FormatException("format"); + + if (precision > MaxPrecision) + throw new FormatException("precision"); + } + + return new ParsedFormat(specifier, precision); + } + + public static ParsedFormat Parse(string format) + { + if (string.IsNullOrEmpty(format)) + return default; + + return Parse(format.AsSpan()); + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Parsing/InvariantBool.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Parsing/InvariantBool.cs new file mode 100644 index 000000000..c32b22034 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Parsing/InvariantBool.cs @@ -0,0 +1,268 @@ +// 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. + + +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace System.Text +{ + public static partial class PrimitiveParser + { + public static partial class InvariantUtf8 + { + public unsafe static bool TryParseBoolean(byte* text, int length, out bool value) + { + if (length >= 4) + { + if ((text[0] == 'T' || text[0] == 't') && + (text[1] == 'R' || text[1] == 'r') && + (text[2] == 'U' || text[2] == 'u') && + (text[3] == 'E' || text[3] == 'e')) + { + // No need to set consumed + value = true; + return true; + } + if (length >= 5) + { + if ((text[0] == 'F' || text[0] == 'f') && + (text[1] == 'A' || text[1] == 'a') && + (text[2] == 'L' || text[2] == 'l') && + (text[3] == 'S' || text[3] == 's') && + (text[4] == 'E' || text[4] == 'e')) + { + // No need to set consumed + value = false; + return true; + } + } + } + // No need to set consumed + value = default; + return false; + } + public unsafe static bool TryParseBoolean(byte* text, int length, out bool value, out int bytesConsumed) + { + if (length >= 4) + { + if ((text[0] == 'T' || text[0] == 't') && + (text[1] == 'R' || text[1] == 'r') && + (text[2] == 'U' || text[2] == 'u') && + (text[3] == 'E' || text[3] == 'e')) + { + bytesConsumed = 4; + value = true; + return true; + } + if (length >= 5) + { + if ((text[0] == 'F' || text[0] == 'f') && + (text[1] == 'A' || text[1] == 'a') && + (text[2] == 'L' || text[2] == 'l') && + (text[3] == 'S' || text[3] == 's') && + (text[4] == 'E' || text[4] == 'e')) + { + bytesConsumed = 5; + value = false; + return true; + } + } + } + bytesConsumed = 0; + value = default; + return false; + } + public static bool TryParseBoolean(ReadOnlySpan text, out bool value) + { + if (text.Length >= 4) + { + if ((text[0] == 'T' || text[0] == 't') && + (text[1] == 'R' || text[1] == 'r') && + (text[2] == 'U' || text[2] == 'u') && + (text[3] == 'E' || text[3] == 'e')) + { + // No need to set consumed + value = true; + return true; + } + if (text.Length >= 5) + { + if ((text[0] == 'F' || text[0] == 'f') && + (text[1] == 'A' || text[1] == 'a') && + (text[2] == 'L' || text[2] == 'l') && + (text[3] == 'S' || text[3] == 's') && + (text[4] == 'E' || text[4] == 'e')) + { + // No need to set consumed + value = false; + return true; + } + } + } + // No need to set consumed + value = default; + return false; + } + public static bool TryParseBoolean(ReadOnlySpan text, out bool value, out int bytesConsumed) + { + if (text.Length >= 4) + { + if ((text[0] == 'T' || text[0] == 't') && + (text[1] == 'R' || text[1] == 'r') && + (text[2] == 'U' || text[2] == 'u') && + (text[3] == 'E' || text[3] == 'e')) + { + bytesConsumed = 4; + value = true; + return true; + } + if (text.Length >= 5) + { + if ((text[0] == 'F' || text[0] == 'f') && + (text[1] == 'A' || text[1] == 'a') && + (text[2] == 'L' || text[2] == 'l') && + (text[3] == 'S' || text[3] == 's') && + (text[4] == 'E' || text[4] == 'e')) + { + bytesConsumed = 5; + value = false; + return true; + } + } + } + bytesConsumed = 0; + value = default; + return false; + } + } + public static partial class InvariantUtf16 + { + public unsafe static bool TryParseBoolean(char* text, int length, out bool value) + { + if (length >= 4) + { + if ((text[0] == 'T' || text[0] == 't') && + (text[1] == 'R' || text[1] == 'r') && + (text[2] == 'U' || text[2] == 'u') && + (text[3] == 'E' || text[3] == 'e')) + { + // No need to set consumed + value = true; + return true; + } + if (length >= 5) + { + if ((text[0] == 'F' || text[0] == 'f') && + (text[1] == 'A' || text[1] == 'a') && + (text[2] == 'L' || text[2] == 'l') && + (text[3] == 'S' || text[3] == 's') && + (text[4] == 'E' || text[4] == 'e')) + { + // No need to set consumed + value = false; + return true; + } + } + } + // No need to set consumed + value = default; + return false; + } + public unsafe static bool TryParseBoolean(char* text, int length, out bool value, out int charsConsumed) + { + if (length >= 4) + { + if ((text[0] == 'T' || text[0] == 't') && + (text[1] == 'R' || text[1] == 'r') && + (text[2] == 'U' || text[2] == 'u') && + (text[3] == 'E' || text[3] == 'e')) + { + charsConsumed = 4; + value = true; + return true; + } + if (length >= 5) + { + if ((text[0] == 'F' || text[0] == 'f') && + (text[1] == 'A' || text[1] == 'a') && + (text[2] == 'L' || text[2] == 'l') && + (text[3] == 'S' || text[3] == 's') && + (text[4] == 'E' || text[4] == 'e')) + { + charsConsumed = 5; + value = false; + return true; + } + } + } + charsConsumed = 0; + value = default; + return false; + } + public static bool TryParseBoolean(ReadOnlySpan text, out bool value) + { + if (text.Length >= 4) + { + if ((text[0] == 'T' || text[0] == 't') && + (text[1] == 'R' || text[1] == 'r') && + (text[2] == 'U' || text[2] == 'u') && + (text[3] == 'E' || text[3] == 'e')) + { + // No need to set consumed + value = true; + return true; + } + if (text.Length >= 5) + { + if ((text[0] == 'F' || text[0] == 'f') && + (text[1] == 'A' || text[1] == 'a') && + (text[2] == 'L' || text[2] == 'l') && + (text[3] == 'S' || text[3] == 's') && + (text[4] == 'E' || text[4] == 'e')) + { + // No need to set consumed + value = false; + return true; + } + } + } + // No need to set consumed + value = default; + return false; + } + public static bool TryParseBoolean(ReadOnlySpan text, out bool value, out int charsConsumed) + { + if (text.Length >= 4) + { + if ((text[0] == 'T' || text[0] == 't') && + (text[1] == 'R' || text[1] == 'r') && + (text[2] == 'U' || text[2] == 'u') && + (text[3] == 'E' || text[3] == 'e')) + { + charsConsumed = 4; + value = true; + return true; + } + if (text.Length >= 5) + { + if ((text[0] == 'F' || text[0] == 'f') && + (text[1] == 'A' || text[1] == 'a') && + (text[2] == 'L' || text[2] == 'l') && + (text[3] == 'S' || text[3] == 's') && + (text[4] == 'E' || text[4] == 'e')) + { + charsConsumed = 5; + value = false; + return true; + } + } + } + charsConsumed = 0; + value = default; + return false; + } + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Parsing/InvariantSigned.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Parsing/InvariantSigned.cs new file mode 100644 index 000000000..75b95792f --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Parsing/InvariantSigned.cs @@ -0,0 +1,2395 @@ +// 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. + + +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace System.Text +{ + public static partial class PrimitiveParser + { + public static partial class InvariantUtf8 + { + #region SByte + public unsafe static bool TryParseSByte(byte* text, int length, out sbyte value) + { + if (length < 1) + { + value = default; + return false; + } + + int indexOfFirstDigit = 0; + int sign = 1; + if (text[0] == '-') + { + indexOfFirstDigit = 1; + sign = -1; + } + else if (text[0] == '+') + { + indexOfFirstDigit = 1; + } + + int overflowLength = SByteOverflowLength + indexOfFirstDigit; + + // Parse the first digit separately. If invalid here, we need to return false. + int firstDigit = text[indexOfFirstDigit] - 48; // '0' + if (firstDigit < 0 || firstDigit > 9) + { + value = default; + return false; + } + int parsedValue = firstDigit; + + if (length < overflowLength) + { + // Length is less than SByteOverflowLength; overflow is not possible + for (int index = indexOfFirstDigit + 1; index < length; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + value = (sbyte)(parsedValue * sign); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than SByteOverflowLength; overflow is only possible after SByteOverflowLength + // digits. There may be no overflow after SByteOverflowLength if there are leading zeroes. + for (int index = indexOfFirstDigit + 1; index < overflowLength - 1; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + value = (sbyte)(parsedValue * sign); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = overflowLength - 1; index < length; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + value = (sbyte)(parsedValue * sign); + return true; + } + // If parsedValue > (sbyte.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (sbyte.MaxValue / 10), any nextDigit greater than 7 or 8 (depending on sign) implies overflow. + bool positive = sign > 0; + bool nextDigitTooLarge = nextDigit > 8 || (positive && nextDigit > 7); + if (parsedValue > sbyte.MaxValue / 10 || parsedValue == sbyte.MaxValue / 10 && nextDigitTooLarge) + { + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + value = (sbyte)(parsedValue * sign); + return true; + } + + public unsafe static bool TryParseSByte(byte* text, int length, out sbyte value, out int bytesConsumed) + { + if (length < 1) + { + bytesConsumed = 0; + value = default; + return false; + } + + int indexOfFirstDigit = 0; + int sign = 1; + if (text[0] == '-') + { + indexOfFirstDigit = 1; + sign = -1; + } + else if (text[0] == '+') + { + indexOfFirstDigit = 1; + } + + int overflowLength = SByteOverflowLength + indexOfFirstDigit; + + // Parse the first digit separately. If invalid here, we need to return false. + int firstDigit = text[indexOfFirstDigit] - 48; // '0' + if (firstDigit < 0 || firstDigit > 9) + { + bytesConsumed = 0; + value = default; + return false; + } + int parsedValue = firstDigit; + + if (length < overflowLength) + { + // Length is less than SByteOverflowLength; overflow is not possible + for (int index = indexOfFirstDigit + 1; index < length; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + bytesConsumed = index; + value = (sbyte)(parsedValue * sign); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than SByteOverflowLength; overflow is only possible after SByteOverflowLength + // digits. There may be no overflow after SByteOverflowLength if there are leading zeroes. + for (int index = indexOfFirstDigit + 1; index < overflowLength - 1; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + bytesConsumed = index; + value = (sbyte)(parsedValue * sign); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = overflowLength - 1; index < length; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + bytesConsumed = index; + value = (sbyte)(parsedValue * sign); + return true; + } + // If parsedValue > (sbyte.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (sbyte.MaxValue / 10), any nextDigit greater than 7 or 8 (depending on sign) implies overflow. + bool positive = sign > 0; + bool nextDigitTooLarge = nextDigit > 8 || (positive && nextDigit > 7); + if (parsedValue > sbyte.MaxValue / 10 || parsedValue == sbyte.MaxValue / 10 && nextDigitTooLarge) + { + bytesConsumed = 0; + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + bytesConsumed = length; + value = (sbyte)(parsedValue * sign); + return true; + } + + public static bool TryParseSByte(ReadOnlySpan text, out sbyte value) + { + return TryParseSByte(text, out value, out int bytesConsumed); + } + + public static bool TryParseSByte(ReadOnlySpan text, out sbyte value, out int bytesConsumed) + { + if (text.Length < 1) goto FalseExit; + + int sign = 1; + int index = 0; + int num = text[index]; + if (num == '-') + { + sign = -1; + index++; + if ((uint)index >= (uint)text.Length) goto FalseExit; + num = text[index]; + } + else if (num == '+') + { + index++; + if ((uint)index >= (uint)text.Length) goto FalseExit; + num = text[index]; + } + + int answer = 0; + + if (IsDigit(num)) + { + if (num == '0') + { + do + { + index++; + if ((uint)index >= (uint)text.Length) goto Done; + num = text[index]; + } while (num == '0'); + if (!IsDigit(num)) goto Done; + } + + answer = num - '0'; + index++; + + if ((uint)index >= (uint)text.Length) goto Done; + num = text[index]; + if (!IsDigit(num)) goto Done; + index++; + answer = 10 * answer + num - '0'; + + // Potential overflow + if ((uint)index >= (uint)text.Length) goto Done; + num = text[index]; + if (!IsDigit(num)) goto Done; + if (answer > SByte.MaxValue / 10 + 1) goto FalseExit; // Overflow + answer = answer * 10 + num - '0'; + + // if sign < 0, (-1 * sign + 1) / 2 = 1 + // else, (-1 * sign + 1) / 2 = 0 + if ((uint)answer > (uint)SByte.MaxValue + (-1 * sign + 1) / 2) goto FalseExit; // Overflow + index++; + if ((uint)index >= (uint)text.Length) goto Done; + if (!IsDigit(text[index])) goto Done; + + // Guaranteed overflow + goto FalseExit; + } + + FalseExit: + bytesConsumed = default; + value = default; + return false; + + Done: + bytesConsumed = index; + value = (SByte)(answer * sign); + return true; + } + + + #endregion + + #region Int16 + public unsafe static bool TryParseInt16(byte* text, int length, out short value) + { + if (length < 1) + { + value = default; + return false; + } + + int indexOfFirstDigit = 0; + int sign = 1; + if (text[0] == '-') + { + indexOfFirstDigit = 1; + sign = -1; + } + else if (text[0] == '+') + { + indexOfFirstDigit = 1; + } + + int overflowLength = Int16OverflowLength + indexOfFirstDigit; + + // Parse the first digit separately. If invalid here, we need to return false. + int firstDigit = text[indexOfFirstDigit] - 48; // '0' + if (firstDigit < 0 || firstDigit > 9) + { + value = default; + return false; + } + int parsedValue = firstDigit; + + if (length < overflowLength) + { + // Length is less than Int16OverflowLength; overflow is not possible + for (int index = indexOfFirstDigit + 1; index < length; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + value = (short)(parsedValue * sign); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than Int16OverflowLength; overflow is only possible after Int16OverflowLength + // digits. There may be no overflow after Int16OverflowLength if there are leading zeroes. + for (int index = indexOfFirstDigit + 1; index < overflowLength - 1; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + value = (short)(parsedValue * sign); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = overflowLength - 1; index < length; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + value = (short)(parsedValue * sign); + return true; + } + // If parsedValue > (short.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (short.MaxValue / 10), any nextDigit greater than 7 or 8 (depending on sign) implies overflow. + bool positive = sign > 0; + bool nextDigitTooLarge = nextDigit > 8 || (positive && nextDigit > 7); + if (parsedValue > short.MaxValue / 10 || parsedValue == short.MaxValue / 10 && nextDigitTooLarge) + { + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + value = (short)(parsedValue * sign); + return true; + } + + public unsafe static bool TryParseInt16(byte* text, int length, out short value, out int bytesConsumed) + { + if (length < 1) + { + bytesConsumed = 0; + value = default; + return false; + } + + int indexOfFirstDigit = 0; + int sign = 1; + if (text[0] == '-') + { + indexOfFirstDigit = 1; + sign = -1; + } + else if (text[0] == '+') + { + indexOfFirstDigit = 1; + } + + int overflowLength = Int16OverflowLength + indexOfFirstDigit; + + // Parse the first digit separately. If invalid here, we need to return false. + int firstDigit = text[indexOfFirstDigit] - 48; // '0' + if (firstDigit < 0 || firstDigit > 9) + { + bytesConsumed = 0; + value = default; + return false; + } + int parsedValue = firstDigit; + + if (length < overflowLength) + { + // Length is less than Int16OverflowLength; overflow is not possible + for (int index = indexOfFirstDigit + 1; index < length; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + bytesConsumed = index; + value = (short)(parsedValue * sign); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than Int16OverflowLength; overflow is only possible after Int16OverflowLength + // digits. There may be no overflow after Int16OverflowLength if there are leading zeroes. + for (int index = indexOfFirstDigit + 1; index < overflowLength - 1; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + bytesConsumed = index; + value = (short)(parsedValue * sign); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = overflowLength - 1; index < length; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + bytesConsumed = index; + value = (short)(parsedValue * sign); + return true; + } + // If parsedValue > (short.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (short.MaxValue / 10), any nextDigit greater than 7 or 8 (depending on sign) implies overflow. + bool positive = sign > 0; + bool nextDigitTooLarge = nextDigit > 8 || (positive && nextDigit > 7); + if (parsedValue > short.MaxValue / 10 || parsedValue == short.MaxValue / 10 && nextDigitTooLarge) + { + bytesConsumed = 0; + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + bytesConsumed = length; + value = (short)(parsedValue * sign); + return true; + } + + public static bool TryParseInt16(ReadOnlySpan text, out short value) + { + return TryParseInt16(text, out value, out int bytesConsumed); + } + + public static bool TryParseInt16(ReadOnlySpan text, out short value, out int bytesConsumed) + { + if (text.Length < 1) goto FalseExit; + + int sign = 1; + int index = 0; + int num = text[index]; + if (num == '-') + { + sign = -1; + index++; + if ((uint)index >= (uint)text.Length) goto FalseExit; + num = text[index]; + } + else if (num == '+') + { + index++; + if ((uint)index >= (uint)text.Length) goto FalseExit; + num = text[index]; + } + + int answer = 0; + + if (IsDigit(num)) + { + if (num == '0') + { + do + { + index++; + if ((uint)index >= (uint)text.Length) goto Done; + num = text[index]; + } while (num == '0'); + if (!IsDigit(num)) goto Done; + } + + answer = num - '0'; + index++; + + if ((uint)index >= (uint)text.Length) goto Done; + num = text[index]; + if (!IsDigit(num)) goto Done; + index++; + answer = 10 * answer + num - '0'; + + if ((uint)index >= (uint)text.Length) goto Done; + num = text[index]; + if (!IsDigit(num)) goto Done; + index++; + answer = 10 * answer + num - '0'; + + if ((uint)index >= (uint)text.Length) goto Done; + num = text[index]; + if (!IsDigit(num)) goto Done; + index++; + answer = 10 * answer + num - '0'; + + // Potential overflow + if ((uint)index >= (uint)text.Length) goto Done; + num = text[index]; + if (!IsDigit(num)) goto Done; + if (answer > Int16.MaxValue / 10 + 1) goto FalseExit; // Overflow + answer = answer * 10 + num - '0'; + + // if sign < 0, (-1 * sign + 1) / 2 = 1 + // else, (-1 * sign + 1) / 2 = 0 + if ((uint)answer > (uint)Int16.MaxValue + (-1 * sign + 1) / 2) goto FalseExit; // Overflow + index++; + if ((uint)index >= (uint)text.Length) goto Done; + if (!IsDigit(text[index])) goto Done; + + // Guaranteed overflow + goto FalseExit; + } + + FalseExit: + bytesConsumed = default; + value = default; + return false; + + Done: + bytesConsumed = index; + value = (short)(answer * sign); + return true; + } + + #endregion + + #region Int32 + public unsafe static bool TryParseInt32(byte* text, int length, out int value) + { + if (length < 1) + { + value = default; + return false; + } + + int indexOfFirstDigit = 0; + int sign = 1; + if (text[0] == '-') + { + indexOfFirstDigit = 1; + sign = -1; + } + else if (text[0] == '+') + { + indexOfFirstDigit = 1; + } + + int overflowLength = Int32OverflowLength + indexOfFirstDigit; + + // Parse the first digit separately. If invalid here, we need to return false. + int firstDigit = text[indexOfFirstDigit] - 48; // '0' + if (firstDigit < 0 || firstDigit > 9) + { + value = default; + return false; + } + int parsedValue = firstDigit; + + if (length < overflowLength) + { + // Length is less than Int32OverflowLength; overflow is not possible + for (int index = indexOfFirstDigit + 1; index < length; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + value = parsedValue * sign; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than Int32OverflowLength; overflow is only possible after Int32OverflowLength + // digits. There may be no overflow after Int32OverflowLength if there are leading zeroes. + for (int index = indexOfFirstDigit + 1; index < overflowLength - 1; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + value = parsedValue * sign; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = overflowLength - 1; index < length; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + value = parsedValue * sign; + return true; + } + // If parsedValue > (int.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (int.MaxValue / 10), any nextDigit greater than 7 or 8 (depending on sign) implies overflow. + bool positive = sign > 0; + bool nextDigitTooLarge = nextDigit > 8 || (positive && nextDigit > 7); + if (parsedValue > int.MaxValue / 10 || parsedValue == int.MaxValue / 10 && nextDigitTooLarge) + { + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + value = parsedValue * sign; + return true; + } + + public unsafe static bool TryParseInt32(byte* text, int length, out int value, out int bytesConsumed) + { + if (length < 1) + { + bytesConsumed = 0; + value = default; + return false; + } + + int indexOfFirstDigit = 0; + int sign = 1; + if (text[0] == '-') + { + indexOfFirstDigit = 1; + sign = -1; + } + else if (text[0] == '+') + { + indexOfFirstDigit = 1; + } + + int overflowLength = Int32OverflowLength + indexOfFirstDigit; + + // Parse the first digit separately. If invalid here, we need to return false. + int firstDigit = text[indexOfFirstDigit] - 48; // '0' + if (firstDigit < 0 || firstDigit > 9) + { + bytesConsumed = 0; + value = default; + return false; + } + int parsedValue = firstDigit; + + if (length < overflowLength) + { + // Length is less than Int32OverflowLength; overflow is not possible + for (int index = indexOfFirstDigit + 1; index < length; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + bytesConsumed = index; + value = parsedValue * sign; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than Int32OverflowLength; overflow is only possible after Int32OverflowLength + // digits. There may be no overflow after Int32OverflowLength if there are leading zeroes. + for (int index = indexOfFirstDigit + 1; index < overflowLength - 1; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + bytesConsumed = index; + value = parsedValue * sign; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = overflowLength - 1; index < length; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + bytesConsumed = index; + value = parsedValue * sign; + return true; + } + // If parsedValue > (int.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (int.MaxValue / 10), any nextDigit greater than 7 or 8 (depending on sign) implies overflow. + bool positive = sign > 0; + bool nextDigitTooLarge = nextDigit > 8 || (positive && nextDigit > 7); + if (parsedValue > int.MaxValue / 10 || parsedValue == int.MaxValue / 10 && nextDigitTooLarge) + { + bytesConsumed = 0; + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + bytesConsumed = length; + value = parsedValue * sign; + return true; + } + + public static bool TryParseInt32(ReadOnlySpan text, out int value) + { + return TryParseInt32(text, out value, out int bytesConsumed); + } + + public static bool TryParseInt32(ReadOnlySpan text, out int value, out int bytesConsumed) + { + if (text.Length < 1) goto FalseExit; + + int sign = 1; + int index = 0; + int num = text[index]; + if (num == '-') + { + sign = -1; + index++; + if ((uint)index >= (uint)text.Length) goto FalseExit; + num = text[index]; + } + else if (num == '+') + { + index++; + if ((uint)index >= (uint)text.Length) goto FalseExit; + num = text[index]; + } + + int answer = 0; + + if (IsDigit(num)) + { + if (num == '0') + { + do + { + index++; + if ((uint)index >= (uint)text.Length) goto Done; + num = text[index]; + } while (num == '0'); + if (!IsDigit(num)) goto Done; + } + + answer = num - '0'; + index++; + + if ((uint)index >= (uint)text.Length) goto Done; + num = text[index]; + if (!IsDigit(num)) goto Done; + index++; + answer = 10 * answer + num - '0'; + + if ((uint)index >= (uint)text.Length) goto Done; + num = text[index]; + if (!IsDigit(num)) goto Done; + index++; + answer = 10 * answer + num - '0'; + + if ((uint)index >= (uint)text.Length) goto Done; + num = text[index]; + if (!IsDigit(num)) goto Done; + index++; + answer = 10 * answer + num - '0'; + + if ((uint)index >= (uint)text.Length) goto Done; + num = text[index]; + if (!IsDigit(num)) goto Done; + index++; + answer = 10 * answer + num - '0'; + + if ((uint)index >= (uint)text.Length) goto Done; + num = text[index]; + if (!IsDigit(num)) goto Done; + index++; + answer = 10 * answer + num - '0'; + + if ((uint)index >= (uint)text.Length) goto Done; + num = text[index]; + if (!IsDigit(num)) goto Done; + index++; + answer = 10 * answer + num - '0'; + + if ((uint)index >= (uint)text.Length) goto Done; + num = text[index]; + if (!IsDigit(num)) goto Done; + index++; + answer = 10 * answer + num - '0'; + + if ((uint)index >= (uint)text.Length) goto Done; + num = text[index]; + if (!IsDigit(num)) goto Done; + index++; + answer = 10 * answer + num - '0'; + + // Potential overflow + if ((uint)index >= (uint)text.Length) goto Done; + num = text[index]; + if (!IsDigit(num)) goto Done; + if (answer > Int32.MaxValue / 10 + 1) goto FalseExit; // Overflow + answer = answer * 10 + num - '0'; + + // if sign < 0, (-1 * sign + 1) / 2 = 1 + // else, (-1 * sign + 1) / 2 = 0 + if ((uint)answer > (uint)Int32.MaxValue + (-1 * sign + 1) / 2) goto FalseExit; // Overflow + index++; + if ((uint)index >= (uint)text.Length) goto Done; + if (!IsDigit(text[index])) goto Done; + + // Guaranteed overflow + goto FalseExit; + } + + FalseExit: + bytesConsumed = default; + value = default; + return false; + + Done: + bytesConsumed = index; + value = answer * sign; + return true; + } + + #endregion + + #region Int64 + public unsafe static bool TryParseInt64(byte* text, int length, out long value) + { + if (length < 1) + { + value = default; + return false; + } + + int indexOfFirstDigit = 0; + int sign = 1; + if (text[0] == '-') + { + indexOfFirstDigit = 1; + sign = -1; + } + else if (text[0] == '+') + { + indexOfFirstDigit = 1; + } + + int overflowLength = Int64OverflowLength + indexOfFirstDigit; + + // Parse the first digit separately. If invalid here, we need to return false. + long firstDigit = text[indexOfFirstDigit] - 48; // '0' + if (firstDigit < 0 || firstDigit > 9) + { + value = default; + return false; + } + long parsedValue = firstDigit; + + if (length < overflowLength) + { + // Length is less than Int64OverflowLength; overflow is not possible + for (int index = indexOfFirstDigit + 1; index < length; index++) + { + long nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + value = parsedValue * sign; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than Int64OverflowLength; overflow is only possible after Int64OverflowLength + // digits. There may be no overflow after Int64OverflowLength if there are leading zeroes. + for (int index = indexOfFirstDigit + 1; index < overflowLength - 1; index++) + { + long nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + value = parsedValue * sign; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = overflowLength - 1; index < length; index++) + { + long nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + value = parsedValue * sign; + return true; + } + // If parsedValue > (long.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (long.MaxValue / 10), any nextDigit greater than 7 or 8 (depending on sign) implies overflow. + bool positive = sign > 0; + bool nextDigitTooLarge = nextDigit > 8 || (positive && nextDigit > 7); + if (parsedValue > long.MaxValue / 10 || parsedValue == long.MaxValue / 10 && nextDigitTooLarge) + { + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + value = parsedValue * sign; + return true; + } + + public unsafe static bool TryParseInt64(byte* text, int length, out long value, out int bytesConsumed) + { + if (length < 1) + { + bytesConsumed = 0; + value = default; + return false; + } + + int indexOfFirstDigit = 0; + int sign = 1; + if (text[0] == '-') + { + indexOfFirstDigit = 1; + sign = -1; + } + else if (text[0] == '+') + { + indexOfFirstDigit = 1; + } + + int overflowLength = Int64OverflowLength + indexOfFirstDigit; + + // Parse the first digit separately. If invalid here, we need to return false. + long firstDigit = text[indexOfFirstDigit] - 48; // '0' + if (firstDigit < 0 || firstDigit > 9) + { + bytesConsumed = 0; + value = default; + return false; + } + long parsedValue = firstDigit; + + if (length < overflowLength) + { + // Length is less than Int64OverflowLength; overflow is not possible + for (int index = indexOfFirstDigit + 1; index < length; index++) + { + long nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + bytesConsumed = index; + value = parsedValue * sign; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than Int64OverflowLength; overflow is only possible after Int64OverflowLength + // digits. There may be no overflow after Int64OverflowLength if there are leading zeroes. + for (int index = indexOfFirstDigit + 1; index < overflowLength - 1; index++) + { + long nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + bytesConsumed = index; + value = parsedValue * sign; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = overflowLength - 1; index < length; index++) + { + long nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + bytesConsumed = index; + value = parsedValue * sign; + return true; + } + // If parsedValue > (long.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (long.MaxValue / 10), any nextDigit greater than 7 or 8 (depending on sign) implies overflow. + bool positive = sign > 0; + bool nextDigitTooLarge = nextDigit > 8 || (positive && nextDigit > 7); + if (parsedValue > long.MaxValue / 10 || parsedValue == long.MaxValue / 10 && nextDigitTooLarge) + { + bytesConsumed = 0; + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + bytesConsumed = length; + value = parsedValue * sign; + return true; + } + + public static bool TryParseInt64(ReadOnlySpan text, out long value) + { + if (text.Length < 1) + { + value = default; + return false; + } + + int indexOfFirstDigit = 0; + int sign = 1; + if (text[0] == '-') + { + indexOfFirstDigit = 1; + sign = -1; + } + else if (text[0] == '+') + { + indexOfFirstDigit = 1; + } + + int overflowLength = Int64OverflowLength + indexOfFirstDigit; + + // Parse the first digit separately. If invalid here, we need to return false. + long firstDigit = text[indexOfFirstDigit] - 48; // '0' + if (firstDigit < 0 || firstDigit > 9) + { + value = default; + return false; + } + long parsedValue = firstDigit; + + if (text.Length < overflowLength) + { + // Length is less than Int64OverflowLength; overflow is not possible + for (int index = indexOfFirstDigit + 1; index < text.Length; index++) + { + long nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + value = parsedValue * sign; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than Int64OverflowLength; overflow is only possible after Int64OverflowLength + // digits. There may be no overflow after Int64OverflowLength if there are leading zeroes. + for (int index = indexOfFirstDigit + 1; index < overflowLength - 1; index++) + { + long nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + value = parsedValue * sign; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = overflowLength - 1; index < text.Length; index++) + { + long nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + value = parsedValue * sign; + return true; + } + // If parsedValue > (long.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (long.MaxValue / 10), any nextDigit greater than 7 or 8 (depending on sign) implies overflow. + bool positive = sign > 0; + bool nextDigitTooLarge = nextDigit > 8 || (positive && nextDigit > 7); + if (parsedValue > long.MaxValue / 10 || parsedValue == long.MaxValue / 10 && nextDigitTooLarge) + { + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + value = parsedValue * sign; + return true; + } + + public static bool TryParseInt64(ReadOnlySpan text, out long value, out int bytesConsumed) + { + if (text.Length < 1) + { + bytesConsumed = 0; + value = default; + return false; + } + + int indexOfFirstDigit = 0; + int sign = 1; + if (text[0] == '-') + { + indexOfFirstDigit = 1; + sign = -1; + } + else if (text[0] == '+') + { + indexOfFirstDigit = 1; + } + + int overflowLength = Int64OverflowLength + indexOfFirstDigit; + + // Parse the first digit separately. If invalid here, we need to return false. + long firstDigit = text[indexOfFirstDigit] - 48; // '0' + if (firstDigit < 0 || firstDigit > 9) + { + bytesConsumed = 0; + value = default; + return false; + } + long parsedValue = firstDigit; + + if (text.Length < overflowLength) + { + // Length is less than Int64OverflowLength; overflow is not possible + for (int index = indexOfFirstDigit + 1; index < text.Length; index++) + { + long nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + bytesConsumed = index; + value = parsedValue * sign; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than Int64OverflowLength; overflow is only possible after Int64OverflowLength + // digits. There may be no overflow after Int64OverflowLength if there are leading zeroes. + for (int index = indexOfFirstDigit + 1; index < overflowLength - 1; index++) + { + long nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + bytesConsumed = index; + value = parsedValue * sign; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = overflowLength - 1; index < text.Length; index++) + { + long nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + bytesConsumed = index; + value = parsedValue * sign; + return true; + } + // If parsedValue > (long.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (long.MaxValue / 10), any nextDigit greater than 7 or 8 (depending on sign) implies overflow. + bool positive = sign > 0; + bool nextDigitTooLarge = nextDigit > 8 || (positive && nextDigit > 7); + if (parsedValue > long.MaxValue / 10 || parsedValue == long.MaxValue / 10 && nextDigitTooLarge) + { + bytesConsumed = 0; + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + bytesConsumed = text.Length; + value = parsedValue * sign; + return true; + } + + #endregion + + } + public static partial class InvariantUtf16 + { + #region SByte + public unsafe static bool TryParseSByte(char* text, int length, out sbyte value) + { + if (length < 1) + { + value = default; + return false; + } + + int indexOfFirstDigit = 0; + int sign = 1; + if (text[0] == '-') + { + indexOfFirstDigit = 1; + sign = -1; + } + else if (text[0] == '+') + { + indexOfFirstDigit = 1; + } + + int overflowLength = SByteOverflowLength + indexOfFirstDigit; + + // Parse the first digit separately. If invalid here, we need to return false. + int firstDigit = text[indexOfFirstDigit] - 48; // '0' + if (firstDigit < 0 || firstDigit > 9) + { + value = default; + return false; + } + int parsedValue = firstDigit; + + if (length < overflowLength) + { + // Length is less than SByteOverflowLength; overflow is not possible + for (int index = indexOfFirstDigit + 1; index < length; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + value = (sbyte)(parsedValue * sign); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than SByteOverflowLength; overflow is only possible after SByteOverflowLength + // digits. There may be no overflow after SByteOverflowLength if there are leading zeroes. + for (int index = indexOfFirstDigit + 1; index < overflowLength - 1; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + value = (sbyte)(parsedValue * sign); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = overflowLength - 1; index < length; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + value = (sbyte)(parsedValue * sign); + return true; + } + // If parsedValue > (sbyte.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (sbyte.MaxValue / 10), any nextDigit greater than 7 or 8 (depending on sign) implies overflow. + bool positive = sign > 0; + bool nextDigitTooLarge = nextDigit > 8 || (positive && nextDigit > 7); + if (parsedValue > sbyte.MaxValue / 10 || parsedValue == sbyte.MaxValue / 10 && nextDigitTooLarge) + { + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + value = (sbyte)(parsedValue * sign); + return true; + } + + public unsafe static bool TryParseSByte(char* text, int length, out sbyte value, out int charsConsumed) + { + if (length < 1) + { + charsConsumed = 0; + value = default; + return false; + } + + int indexOfFirstDigit = 0; + int sign = 1; + if (text[0] == '-') + { + indexOfFirstDigit = 1; + sign = -1; + } + else if (text[0] == '+') + { + indexOfFirstDigit = 1; + } + + int overflowLength = SByteOverflowLength + indexOfFirstDigit; + + // Parse the first digit separately. If invalid here, we need to return false. + int firstDigit = text[indexOfFirstDigit] - 48; // '0' + if (firstDigit < 0 || firstDigit > 9) + { + charsConsumed = 0; + value = default; + return false; + } + int parsedValue = firstDigit; + + if (length < overflowLength) + { + // Length is less than SByteOverflowLength; overflow is not possible + for (int index = indexOfFirstDigit + 1; index < length; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + charsConsumed = index; + value = (sbyte)(parsedValue * sign); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than SByteOverflowLength; overflow is only possible after SByteOverflowLength + // digits. There may be no overflow after SByteOverflowLength if there are leading zeroes. + for (int index = indexOfFirstDigit + 1; index < overflowLength - 1; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + charsConsumed = index; + value = (sbyte)(parsedValue * sign); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = overflowLength - 1; index < length; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + charsConsumed = index; + value = (sbyte)(parsedValue * sign); + return true; + } + // If parsedValue > (sbyte.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (sbyte.MaxValue / 10), any nextDigit greater than 7 or 8 (depending on sign) implies overflow. + bool positive = sign > 0; + bool nextDigitTooLarge = nextDigit > 8 || (positive && nextDigit > 7); + if (parsedValue > sbyte.MaxValue / 10 || parsedValue == sbyte.MaxValue / 10 && nextDigitTooLarge) + { + charsConsumed = 0; + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + charsConsumed = length; + value = (sbyte)(parsedValue * sign); + return true; + } + + public static bool TryParseSByte(ReadOnlySpan text, out sbyte value) + { + return TryParseSByte(text, out value, out int bytesConsumed); + } + + public static bool TryParseSByte(ReadOnlySpan text, out sbyte value, out int charsConsumed) + { + if (text.Length < 1) + { + charsConsumed = 0; + value = default; + return false; + } + + int indexOfFirstDigit = 0; + int sign = 1; + if (text[0] == '-') + { + indexOfFirstDigit = 1; + sign = -1; + } + else if (text[0] == '+') + { + indexOfFirstDigit = 1; + } + + if (indexOfFirstDigit >= text.Length) + { + charsConsumed = 0; + value = default; + return false; + } + + int overflowLength = SByteOverflowLength + indexOfFirstDigit; + + // Parse the first digit separately. If invalid here, we need to return false. + int firstDigit = text[indexOfFirstDigit] - 48; // '0' + if (firstDigit < 0 || firstDigit > 9) + { + charsConsumed = 0; + value = default; + return false; + } + int parsedValue = firstDigit; + + if (text.Length < overflowLength) + { + // Length is less than SByteOverflowLength; overflow is not possible + for (int index = indexOfFirstDigit + 1; index < text.Length; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + charsConsumed = index; + value = (sbyte)(parsedValue * sign); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than SByteOverflowLength; overflow is only possible after SByteOverflowLength + // digits. There may be no overflow after SByteOverflowLength if there are leading zeroes. + for (int index = indexOfFirstDigit + 1; index < overflowLength - 1; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + charsConsumed = index; + value = (sbyte)(parsedValue * sign); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = overflowLength - 1; index < text.Length; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + charsConsumed = index; + value = (sbyte)(parsedValue * sign); + return true; + } + // If parsedValue > (sbyte.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (sbyte.MaxValue / 10), any nextDigit greater than 7 or 8 (depending on sign) implies overflow. + bool positive = sign > 0; + bool nextDigitTooLarge = nextDigit > 8 || (positive && nextDigit > 7); + if (parsedValue > sbyte.MaxValue / 10 || parsedValue == sbyte.MaxValue / 10 && nextDigitTooLarge) + { + charsConsumed = 0; + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + charsConsumed = text.Length; + value = (sbyte)(parsedValue * sign); + return true; + } + + #endregion + + #region Int16 + public unsafe static bool TryParseInt16(char* text, int length, out short value) + { + if (length < 1) + { + value = default; + return false; + } + + int indexOfFirstDigit = 0; + int sign = 1; + if (text[0] == '-') + { + indexOfFirstDigit = 1; + sign = -1; + } + else if (text[0] == '+') + { + indexOfFirstDigit = 1; + } + + int overflowLength = Int16OverflowLength + indexOfFirstDigit; + + // Parse the first digit separately. If invalid here, we need to return false. + int firstDigit = text[indexOfFirstDigit] - 48; // '0' + if (firstDigit < 0 || firstDigit > 9) + { + value = default; + return false; + } + int parsedValue = firstDigit; + + if (length < overflowLength) + { + // Length is less than Int16OverflowLength; overflow is not possible + for (int index = indexOfFirstDigit + 1; index < length; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + value = (short)(parsedValue * sign); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than Int16OverflowLength; overflow is only possible after Int16OverflowLength + // digits. There may be no overflow after Int16OverflowLength if there are leading zeroes. + for (int index = indexOfFirstDigit + 1; index < overflowLength - 1; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + value = (short)(parsedValue * sign); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = overflowLength - 1; index < length; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + value = (short)(parsedValue * sign); + return true; + } + // If parsedValue > (short.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (short.MaxValue / 10), any nextDigit greater than 7 or 8 (depending on sign) implies overflow. + bool positive = sign > 0; + bool nextDigitTooLarge = nextDigit > 8 || (positive && nextDigit > 7); + if (parsedValue > short.MaxValue / 10 || parsedValue == short.MaxValue / 10 && nextDigitTooLarge) + { + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + value = (short)(parsedValue * sign); + return true; + } + + public unsafe static bool TryParseInt16(char* text, int length, out short value, out int charsConsumed) + { + if (length < 1) + { + charsConsumed = 0; + value = default; + return false; + } + + int indexOfFirstDigit = 0; + int sign = 1; + if (text[0] == '-') + { + indexOfFirstDigit = 1; + sign = -1; + } + else if (text[0] == '+') + { + indexOfFirstDigit = 1; + } + + int overflowLength = Int16OverflowLength + indexOfFirstDigit; + + // Parse the first digit separately. If invalid here, we need to return false. + int firstDigit = text[indexOfFirstDigit] - 48; // '0' + if (firstDigit < 0 || firstDigit > 9) + { + charsConsumed = 0; + value = default; + return false; + } + int parsedValue = firstDigit; + + if (length < overflowLength) + { + // Length is less than Int16OverflowLength; overflow is not possible + for (int index = indexOfFirstDigit + 1; index < length; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + charsConsumed = index; + value = (short)(parsedValue * sign); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than Int16OverflowLength; overflow is only possible after Int16OverflowLength + // digits. There may be no overflow after Int16OverflowLength if there are leading zeroes. + for (int index = indexOfFirstDigit + 1; index < overflowLength - 1; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + charsConsumed = index; + value = (short)(parsedValue * sign); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = overflowLength - 1; index < length; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + charsConsumed = index; + value = (short)(parsedValue * sign); + return true; + } + // If parsedValue > (short.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (short.MaxValue / 10), any nextDigit greater than 7 or 8 (depending on sign) implies overflow. + bool positive = sign > 0; + bool nextDigitTooLarge = nextDigit > 8 || (positive && nextDigit > 7); + if (parsedValue > short.MaxValue / 10 || parsedValue == short.MaxValue / 10 && nextDigitTooLarge) + { + charsConsumed = 0; + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + charsConsumed = length; + value = (short)(parsedValue * sign); + return true; + } + + public static bool TryParseInt16(ReadOnlySpan text, out short value) + { + return TryParseInt16(text, out value, out int charsConsumed); + } + + public static bool TryParseInt16(ReadOnlySpan text, out short value, out int charsConsumed) + { + if (text.Length < 1) + { + charsConsumed = 0; + value = default; + return false; + } + + int indexOfFirstDigit = 0; + int sign = 1; + if (text[0] == '-') + { + indexOfFirstDigit = 1; + sign = -1; + } + else if (text[0] == '+') + { + indexOfFirstDigit = 1; + } + + if (indexOfFirstDigit >= text.Length) + { + charsConsumed = 0; + value = default; + return false; + } + + int overflowLength = Int16OverflowLength + indexOfFirstDigit; + + // Parse the first digit separately. If invalid here, we need to return false. + int firstDigit = text[indexOfFirstDigit] - 48; // '0' + if (firstDigit < 0 || firstDigit > 9) + { + charsConsumed = 0; + value = default; + return false; + } + int parsedValue = firstDigit; + + if (text.Length < overflowLength) + { + // Length is less than Int16OverflowLength; overflow is not possible + for (int index = indexOfFirstDigit + 1; index < text.Length; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + charsConsumed = index; + value = (short)(parsedValue * sign); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than Int16OverflowLength; overflow is only possible after Int16OverflowLength + // digits. There may be no overflow after Int16OverflowLength if there are leading zeroes. + for (int index = indexOfFirstDigit + 1; index < overflowLength - 1; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + charsConsumed = index; + value = (short)(parsedValue * sign); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = overflowLength - 1; index < text.Length; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + charsConsumed = index; + value = (short)(parsedValue * sign); + return true; + } + // If parsedValue > (short.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (short.MaxValue / 10), any nextDigit greater than 7 or 8 (depending on sign) implies overflow. + bool positive = sign > 0; + bool nextDigitTooLarge = nextDigit > 8 || (positive && nextDigit > 7); + if (parsedValue > short.MaxValue / 10 || parsedValue == short.MaxValue / 10 && nextDigitTooLarge) + { + charsConsumed = 0; + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + charsConsumed = text.Length; + value = (short)(parsedValue * sign); + return true; + } + + #endregion + + #region Int32 + public unsafe static bool TryParseInt32(char* text, int length, out int value) + { + if (length < 1) + { + value = default; + return false; + } + + int indexOfFirstDigit = 0; + int sign = 1; + if (text[0] == '-') + { + indexOfFirstDigit = 1; + sign = -1; + } + else if (text[0] == '+') + { + indexOfFirstDigit = 1; + } + + int overflowLength = Int32OverflowLength + indexOfFirstDigit; + + // Parse the first digit separately. If invalid here, we need to return false. + int firstDigit = text[indexOfFirstDigit] - 48; // '0' + if (firstDigit < 0 || firstDigit > 9) + { + value = default; + return false; + } + int parsedValue = firstDigit; + + if (length < overflowLength) + { + // Length is less than Int32OverflowLength; overflow is not possible + for (int index = indexOfFirstDigit + 1; index < length; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + value = parsedValue * sign; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than Int32OverflowLength; overflow is only possible after Int32OverflowLength + // digits. There may be no overflow after Int32OverflowLength if there are leading zeroes. + for (int index = indexOfFirstDigit + 1; index < overflowLength - 1; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + value = parsedValue * sign; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = overflowLength - 1; index < length; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + value = parsedValue * sign; + return true; + } + // If parsedValue > (int.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (int.MaxValue / 10), any nextDigit greater than 7 or 8 (depending on sign) implies overflow. + bool positive = sign > 0; + bool nextDigitTooLarge = nextDigit > 8 || (positive && nextDigit > 7); + if (parsedValue > int.MaxValue / 10 || parsedValue == int.MaxValue / 10 && nextDigitTooLarge) + { + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + value = parsedValue * sign; + return true; + } + + public unsafe static bool TryParseInt32(char* text, int length, out int value, out int charsConsumed) + { + if (length < 1) + { + charsConsumed = 0; + value = default; + return false; + } + + int indexOfFirstDigit = 0; + int sign = 1; + if (text[0] == '-') + { + indexOfFirstDigit = 1; + sign = -1; + } + else if (text[0] == '+') + { + indexOfFirstDigit = 1; + } + + int overflowLength = Int32OverflowLength + indexOfFirstDigit; + + // Parse the first digit separately. If invalid here, we need to return false. + int firstDigit = text[indexOfFirstDigit] - 48; // '0' + if (firstDigit < 0 || firstDigit > 9) + { + charsConsumed = 0; + value = default; + return false; + } + int parsedValue = firstDigit; + + if (length < overflowLength) + { + // Length is less than Int32OverflowLength; overflow is not possible + for (int index = indexOfFirstDigit + 1; index < length; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + charsConsumed = index; + value = parsedValue * sign; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than Int32OverflowLength; overflow is only possible after Int32OverflowLength + // digits. There may be no overflow after Int32OverflowLength if there are leading zeroes. + for (int index = indexOfFirstDigit + 1; index < overflowLength - 1; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + charsConsumed = index; + value = parsedValue * sign; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = overflowLength - 1; index < length; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + charsConsumed = index; + value = parsedValue * sign; + return true; + } + // If parsedValue > (int.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (int.MaxValue / 10), any nextDigit greater than 7 or 8 (depending on sign) implies overflow. + bool positive = sign > 0; + bool nextDigitTooLarge = nextDigit > 8 || (positive && nextDigit > 7); + if (parsedValue > int.MaxValue / 10 || parsedValue == int.MaxValue / 10 && nextDigitTooLarge) + { + charsConsumed = 0; + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + charsConsumed = length; + value = parsedValue * sign; + return true; + } + + public static bool TryParseInt32(ReadOnlySpan text, out int value) + { + return TryParseInt32(text, out value, out int charsConsumed); + } + + public static bool TryParseInt32(ReadOnlySpan text, out int value, out int charsConsumed) + { + if (text.Length < 1) + { + charsConsumed = 0; + value = default; + return false; + } + + int indexOfFirstDigit = 0; + int sign = 1; + if (text[0] == '-') + { + indexOfFirstDigit = 1; + sign = -1; + } + else if (text[0] == '+') + { + indexOfFirstDigit = 1; + } + + if (indexOfFirstDigit >= text.Length) + { + charsConsumed = 0; + value = default; + return false; + } + + int overflowLength = Int32OverflowLength + indexOfFirstDigit; + + // Parse the first digit separately. If invalid here, we need to return false. + int firstDigit = text[indexOfFirstDigit] - 48; // '0' + if (firstDigit < 0 || firstDigit > 9) + { + charsConsumed = 0; + value = default; + return false; + } + int parsedValue = firstDigit; + + if (text.Length < overflowLength) + { + // Length is less than Int32OverflowLength; overflow is not possible + for (int index = indexOfFirstDigit + 1; index < text.Length; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + charsConsumed = index; + value = parsedValue * sign; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than Int32OverflowLength; overflow is only possible after Int32OverflowLength + // digits. There may be no overflow after Int32OverflowLength if there are leading zeroes. + for (int index = indexOfFirstDigit + 1; index < overflowLength - 1; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + charsConsumed = index; + value = parsedValue * sign; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = overflowLength - 1; index < text.Length; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + charsConsumed = index; + value = parsedValue * sign; + return true; + } + // If parsedValue > (int.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (int.MaxValue / 10), any nextDigit greater than 7 or 8 (depending on sign) implies overflow. + bool positive = sign > 0; + bool nextDigitTooLarge = nextDigit > 8 || (positive && nextDigit > 7); + if (parsedValue > int.MaxValue / 10 || parsedValue == int.MaxValue / 10 && nextDigitTooLarge) + { + charsConsumed = 0; + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + charsConsumed = text.Length; + value = parsedValue * sign; + return true; + } + + #endregion + + #region Int64 + public unsafe static bool TryParseInt64(char* text, int length, out long value) + { + if (length < 1) + { + value = default; + return false; + } + + int indexOfFirstDigit = 0; + int sign = 1; + if (text[0] == '-') + { + indexOfFirstDigit = 1; + sign = -1; + } + else if (text[0] == '+') + { + indexOfFirstDigit = 1; + } + + int overflowLength = Int64OverflowLength + indexOfFirstDigit; + + // Parse the first digit separately. If invalid here, we need to return false. + long firstDigit = text[indexOfFirstDigit] - 48; // '0' + if (firstDigit < 0 || firstDigit > 9) + { + value = default; + return false; + } + long parsedValue = firstDigit; + + if (length < overflowLength) + { + // Length is less than Int64OverflowLength; overflow is not possible + for (int index = indexOfFirstDigit + 1; index < length; index++) + { + long nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + value = parsedValue * sign; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than Int64OverflowLength; overflow is only possible after Int64OverflowLength + // digits. There may be no overflow after Int64OverflowLength if there are leading zeroes. + for (int index = indexOfFirstDigit + 1; index < overflowLength - 1; index++) + { + long nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + value = parsedValue * sign; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = overflowLength - 1; index < length; index++) + { + long nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + value = parsedValue * sign; + return true; + } + // If parsedValue > (long.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (long.MaxValue / 10), any nextDigit greater than 7 or 8 (depending on sign) implies overflow. + bool positive = sign > 0; + bool nextDigitTooLarge = nextDigit > 8 || (positive && nextDigit > 7); + if (parsedValue > long.MaxValue / 10 || parsedValue == long.MaxValue / 10 && nextDigitTooLarge) + { + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + value = parsedValue * sign; + return true; + } + + public unsafe static bool TryParseInt64(char* text, int length, out long value, out int charsConsumed) + { + if (length < 1) + { + charsConsumed = 0; + value = default; + return false; + } + + int indexOfFirstDigit = 0; + int sign = 1; + if (text[0] == '-') + { + indexOfFirstDigit = 1; + sign = -1; + } + else if (text[0] == '+') + { + indexOfFirstDigit = 1; + } + + int overflowLength = Int64OverflowLength + indexOfFirstDigit; + + // Parse the first digit separately. If invalid here, we need to return false. + long firstDigit = text[indexOfFirstDigit] - 48; // '0' + if (firstDigit < 0 || firstDigit > 9) + { + charsConsumed = 0; + value = default; + return false; + } + long parsedValue = firstDigit; + + if (length < overflowLength) + { + // Length is less than Int64OverflowLength; overflow is not possible + for (int index = indexOfFirstDigit + 1; index < length; index++) + { + long nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + charsConsumed = index; + value = parsedValue * sign; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than Int64OverflowLength; overflow is only possible after Int64OverflowLength + // digits. There may be no overflow after Int64OverflowLength if there are leading zeroes. + for (int index = indexOfFirstDigit + 1; index < overflowLength - 1; index++) + { + long nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + charsConsumed = index; + value = parsedValue * sign; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = overflowLength - 1; index < length; index++) + { + long nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + charsConsumed = index; + value = parsedValue * sign; + return true; + } + // If parsedValue > (long.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (long.MaxValue / 10), any nextDigit greater than 7 or 8 (depending on sign) implies overflow. + bool positive = sign > 0; + bool nextDigitTooLarge = nextDigit > 8 || (positive && nextDigit > 7); + if (parsedValue > long.MaxValue / 10 || parsedValue == long.MaxValue / 10 && nextDigitTooLarge) + { + charsConsumed = 0; + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + charsConsumed = length; + value = parsedValue * sign; + return true; + } + + public static bool TryParseInt64(ReadOnlySpan text, out long value) + { + if (text.Length < 1) + { + value = default; + return false; + } + + int indexOfFirstDigit = 0; + int sign = 1; + if (text[0] == '-') + { + indexOfFirstDigit = 1; + sign = -1; + } + else if (text[0] == '+') + { + indexOfFirstDigit = 1; + } + + int overflowLength = Int64OverflowLength + indexOfFirstDigit; + + // Parse the first digit separately. If invalid here, we need to return false. + long firstDigit = text[indexOfFirstDigit] - 48; // '0' + if (firstDigit < 0 || firstDigit > 9) + { + value = default; + return false; + } + long parsedValue = firstDigit; + + if (text.Length < overflowLength) + { + // Length is less than Int64OverflowLength; overflow is not possible + for (int index = indexOfFirstDigit + 1; index < text.Length; index++) + { + long nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + value = parsedValue * sign; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than Int64OverflowLength; overflow is only possible after Int64OverflowLength + // digits. There may be no overflow after Int64OverflowLength if there are leading zeroes. + for (int index = indexOfFirstDigit + 1; index < overflowLength - 1; index++) + { + long nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + value = parsedValue * sign; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = overflowLength - 1; index < text.Length; index++) + { + long nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + value = parsedValue * sign; + return true; + } + // If parsedValue > (long.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (long.MaxValue / 10), any nextDigit greater than 7 or 8 (depending on sign) implies overflow. + bool positive = sign > 0; + bool nextDigitTooLarge = nextDigit > 8 || (positive && nextDigit > 7); + if (parsedValue > long.MaxValue / 10 || parsedValue == long.MaxValue / 10 && nextDigitTooLarge) + { + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + value = parsedValue * sign; + return true; + } + + public static bool TryParseInt64(ReadOnlySpan text, out long value, out int charsConsumed) + { + if (text.Length < 1) + { + charsConsumed = 0; + value = default; + return false; + } + + int indexOfFirstDigit = 0; + int sign = 1; + if (text[0] == '-') + { + indexOfFirstDigit = 1; + sign = -1; + } + else if (text[0] == '+') + { + indexOfFirstDigit = 1; + } + + int overflowLength = Int64OverflowLength + indexOfFirstDigit; + + // Parse the first digit separately. If invalid here, we need to return false. + long firstDigit = text[indexOfFirstDigit] - 48; // '0' + if (firstDigit < 0 || firstDigit > 9) + { + charsConsumed = 0; + value = default; + return false; + } + long parsedValue = firstDigit; + + if (text.Length < overflowLength) + { + // Length is less than Int64OverflowLength; overflow is not possible + for (int index = indexOfFirstDigit + 1; index < text.Length; index++) + { + long nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + charsConsumed = index; + value = parsedValue * sign; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than Int64OverflowLength; overflow is only possible after Int64OverflowLength + // digits. There may be no overflow after Int64OverflowLength if there are leading zeroes. + for (int index = indexOfFirstDigit + 1; index < overflowLength - 1; index++) + { + long nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + charsConsumed = index; + value = parsedValue * sign; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = overflowLength - 1; index < text.Length; index++) + { + long nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + charsConsumed = index; + value = parsedValue * sign; + return true; + } + // If parsedValue > (long.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (long.MaxValue / 10), any nextDigit greater than 7 or 8 (depending on sign) implies overflow. + bool positive = sign > 0; + bool nextDigitTooLarge = nextDigit > 8 || (positive && nextDigit > 7); + if (parsedValue > long.MaxValue / 10 || parsedValue == long.MaxValue / 10 && nextDigitTooLarge) + { + charsConsumed = 0; + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + charsConsumed = text.Length; + value = parsedValue * sign; + return true; + } + + #endregion + + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Parsing/InvariantSignedHex.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Parsing/InvariantSignedHex.cs new file mode 100644 index 000000000..62684ab43 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Parsing/InvariantSignedHex.cs @@ -0,0 +1,2594 @@ +// 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. + + +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace System.Text +{ + public static partial class PrimitiveParser + { + public static partial class InvariantUtf8 + { + public static partial class Hex + { + #region SByte + public unsafe static bool TryParseSByte(byte* text, int length, out sbyte value) + { + if (length < 1) + { + value = default; + return false; + } + byte nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = default; + return false; + } + uint parsedValue = nextDigit; + + if (length <= SByteOverflowLengthHex) + { + // Length is less than or equal to SByteOverflowLengthHex; overflow is not possible + for (int index = 1; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = (sbyte)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than SByteOverflowLengthHex; overflow is only possible after SByteOverflowLengthHex + // digits. There may be no overflow after SByteOverflowLengthHex if there are leading zeroes. + for (int index = 1; index < SByteOverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = (sbyte)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = SByteOverflowLengthHex; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = (sbyte)(parsedValue); + return true; + } + // If we try to append a digit to anything larger than -(sbyte.MinValue / 0x08), there will be overflow + if (parsedValue >= -(sbyte.MinValue / 0x08)) + { + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + value = (sbyte)(parsedValue); + return true; + } + + public unsafe static bool TryParseSByte(byte* text, int length, out sbyte value, out int bytesConsumed) + { + if (length < 1) + { + bytesConsumed = 0; + value = default; + return false; + } + byte nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = 0; + value = default; + return false; + } + uint parsedValue = nextDigit; + + if (length <= SByteOverflowLengthHex) + { + // Length is less than or equal to SByteOverflowLengthHex; overflow is not possible + for (int index = 1; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = index; + value = (sbyte)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than SByteOverflowLengthHex; overflow is only possible after SByteOverflowLengthHex + // digits. There may be no overflow after SByteOverflowLengthHex if there are leading zeroes. + for (int index = 1; index < SByteOverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = index; + value = (sbyte)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = SByteOverflowLengthHex; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = index; + value = (sbyte)(parsedValue); + return true; + } + // If we try to append a digit to anything larger than -(sbyte.MinValue / 0x08), there will be overflow + if (parsedValue >= -(sbyte.MinValue / 0x08)) + { + bytesConsumed = 0; + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + bytesConsumed = length; + value = (sbyte)(parsedValue); + return true; + } + + public static bool TryParseSByte(ReadOnlySpan text, out sbyte value) + { + if (text.Length < 1) + { + value = default; + return false; + } + byte nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = default; + return false; + } + uint parsedValue = nextDigit; + + if (text.Length <= SByteOverflowLengthHex) + { + // Length is less than or equal to SByteOverflowLengthHex; overflow is not possible + for (int index = 1; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = (sbyte)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than SByteOverflowLengthHex; overflow is only possible after SByteOverflowLengthHex + // digits. There may be no overflow after SByteOverflowLengthHex if there are leading zeroes. + for (int index = 1; index < SByteOverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = (sbyte)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = SByteOverflowLengthHex; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = (sbyte)(parsedValue); + return true; + } + // If we try to append a digit to anything larger than -(sbyte.MinValue / 0x08), there will be overflow + if (parsedValue >= -(sbyte.MinValue / 0x08)) + { + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + value = (sbyte)(parsedValue); + return true; + } + + public static bool TryParseSByte(ReadOnlySpan text, out sbyte value, out int bytesConsumed) + { + if (text.Length < 1) + { + bytesConsumed = 0; + value = default; + return false; + } + byte nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = 0; + value = default; + return false; + } + uint parsedValue = nextDigit; + + if (text.Length <= SByteOverflowLengthHex) + { + // Length is less than or equal to SByteOverflowLengthHex; overflow is not possible + for (int index = 1; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = index; + value = (sbyte)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than SByteOverflowLengthHex; overflow is only possible after SByteOverflowLengthHex + // digits. There may be no overflow after SByteOverflowLengthHex if there are leading zeroes. + for (int index = 1; index < SByteOverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = index; + value = (sbyte)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = SByteOverflowLengthHex; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = index; + value = (sbyte)(parsedValue); + return true; + } + // If we try to append a digit to anything larger than -(sbyte.MinValue / 0x08), there will be overflow + if (parsedValue >= -(sbyte.MinValue / 0x08)) + { + bytesConsumed = 0; + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + bytesConsumed = text.Length; + value = (sbyte)(parsedValue); + return true; + } + + #endregion + + #region Int16 + public unsafe static bool TryParseInt16(byte* text, int length, out short value) + { + if (length < 1) + { + value = default; + return false; + } + byte nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = default; + return false; + } + uint parsedValue = nextDigit; + + if (length <= Int16OverflowLengthHex) + { + // Length is less than or equal to Int16OverflowLengthHex; overflow is not possible + for (int index = 1; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = (short)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than Int16OverflowLengthHex; overflow is only possible after Int16OverflowLengthHex + // digits. There may be no overflow after Int16OverflowLengthHex if there are leading zeroes. + for (int index = 1; index < Int16OverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = (short)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = Int16OverflowLengthHex; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = (short)(parsedValue); + return true; + } + // If we try to append a digit to anything larger than -(short.MinValue / 0x08), there will be overflow + if (parsedValue >= -(short.MinValue / 0x08)) + { + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + value = (short)(parsedValue); + return true; + } + + public unsafe static bool TryParseInt16(byte* text, int length, out short value, out int bytesConsumed) + { + if (length < 1) + { + bytesConsumed = 0; + value = default; + return false; + } + byte nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = 0; + value = default; + return false; + } + uint parsedValue = nextDigit; + + if (length <= Int16OverflowLengthHex) + { + // Length is less than or equal to Int16OverflowLengthHex; overflow is not possible + for (int index = 1; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = index; + value = (short)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than Int16OverflowLengthHex; overflow is only possible after Int16OverflowLengthHex + // digits. There may be no overflow after Int16OverflowLengthHex if there are leading zeroes. + for (int index = 1; index < Int16OverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = index; + value = (short)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = Int16OverflowLengthHex; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = index; + value = (short)(parsedValue); + return true; + } + // If we try to append a digit to anything larger than -(short.MinValue / 0x08), there will be overflow + if (parsedValue >= -(short.MinValue / 0x08)) + { + bytesConsumed = 0; + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + bytesConsumed = length; + value = (short)(parsedValue); + return true; + } + + public static bool TryParseInt16(ReadOnlySpan text, out short value) + { + if (text.Length < 1) + { + value = default; + return false; + } + byte nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = default; + return false; + } + uint parsedValue = nextDigit; + + if (text.Length <= Int16OverflowLengthHex) + { + // Length is less than or equal to Int16OverflowLengthHex; overflow is not possible + for (int index = 1; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = (short)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than Int16OverflowLengthHex; overflow is only possible after Int16OverflowLengthHex + // digits. There may be no overflow after Int16OverflowLengthHex if there are leading zeroes. + for (int index = 1; index < Int16OverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = (short)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = Int16OverflowLengthHex; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = (short)(parsedValue); + return true; + } + // If we try to append a digit to anything larger than -(short.MinValue / 0x08), there will be overflow + if (parsedValue >= -(short.MinValue / 0x08)) + { + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + value = (short)(parsedValue); + return true; + } + + public static bool TryParseInt16(ReadOnlySpan text, out short value, out int bytesConsumed) + { + if (text.Length < 1) + { + bytesConsumed = 0; + value = default; + return false; + } + byte nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = 0; + value = default; + return false; + } + uint parsedValue = nextDigit; + + if (text.Length <= Int16OverflowLengthHex) + { + // Length is less than or equal to Int16OverflowLengthHex; overflow is not possible + for (int index = 1; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = index; + value = (short)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than Int16OverflowLengthHex; overflow is only possible after Int16OverflowLengthHex + // digits. There may be no overflow after Int16OverflowLengthHex if there are leading zeroes. + for (int index = 1; index < Int16OverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = index; + value = (short)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = Int16OverflowLengthHex; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = index; + value = (short)(parsedValue); + return true; + } + // If we try to append a digit to anything larger than -(short.MinValue / 0x08), there will be overflow + if (parsedValue >= -(short.MinValue / 0x08)) + { + bytesConsumed = 0; + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + bytesConsumed = text.Length; + value = (short)(parsedValue); + return true; + } + + #endregion + + #region Int32 + public unsafe static bool TryParseInt32(byte* text, int length, out int value) + { + if (length < 1) + { + value = default; + return false; + } + byte nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = default; + return false; + } + uint parsedValue = nextDigit; + + if (length <= Int32OverflowLengthHex) + { + // Length is less than or equal to Int32OverflowLengthHex; overflow is not possible + for (int index = 1; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = (int)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than Int32OverflowLengthHex; overflow is only possible after Int32OverflowLengthHex + // digits. There may be no overflow after Int32OverflowLengthHex if there are leading zeroes. + for (int index = 1; index < Int32OverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = (int)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = Int32OverflowLengthHex; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = (int)(parsedValue); + return true; + } + // If we try to append a digit to anything larger than -(int.MinValue / 0x08), there will be overflow + if (parsedValue >= -(int.MinValue / 0x08)) + { + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + value = (int)(parsedValue); + return true; + } + + public unsafe static bool TryParseInt32(byte* text, int length, out int value, out int bytesConsumed) + { + if (length < 1) + { + bytesConsumed = 0; + value = default; + return false; + } + byte nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = 0; + value = default; + return false; + } + uint parsedValue = nextDigit; + + if (length <= Int32OverflowLengthHex) + { + // Length is less than or equal to Int32OverflowLengthHex; overflow is not possible + for (int index = 1; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = index; + value = (int)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than Int32OverflowLengthHex; overflow is only possible after Int32OverflowLengthHex + // digits. There may be no overflow after Int32OverflowLengthHex if there are leading zeroes. + for (int index = 1; index < Int32OverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = index; + value = (int)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = Int32OverflowLengthHex; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = index; + value = (int)(parsedValue); + return true; + } + // If we try to append a digit to anything larger than -(int.MinValue / 0x08), there will be overflow + if (parsedValue >= -(int.MinValue / 0x08)) + { + bytesConsumed = 0; + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + bytesConsumed = length; + value = (int)(parsedValue); + return true; + } + + public static bool TryParseInt32(ReadOnlySpan text, out int value) + { + if (text.Length < 1) + { + value = default; + return false; + } + byte nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = default; + return false; + } + uint parsedValue = nextDigit; + + if (text.Length <= Int32OverflowLengthHex) + { + // Length is less than or equal to Int32OverflowLengthHex; overflow is not possible + for (int index = 1; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = (int)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than Int32OverflowLengthHex; overflow is only possible after Int32OverflowLengthHex + // digits. There may be no overflow after Int32OverflowLengthHex if there are leading zeroes. + for (int index = 1; index < Int32OverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = (int)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = Int32OverflowLengthHex; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = (int)(parsedValue); + return true; + } + // If we try to append a digit to anything larger than -(int.MinValue / 0x08), there will be overflow + if (parsedValue >= -(int.MinValue / 0x08)) + { + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + value = (int)(parsedValue); + return true; + } + + public static bool TryParseInt32(ReadOnlySpan text, out int value, out int bytesConsumed) + { + if (text.Length < 1) + { + bytesConsumed = 0; + value = default; + return false; + } + byte nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = 0; + value = default; + return false; + } + uint parsedValue = nextDigit; + + if (text.Length <= Int32OverflowLengthHex) + { + // Length is less than or equal to Int32OverflowLengthHex; overflow is not possible + for (int index = 1; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = index; + value = (int)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than Int32OverflowLengthHex; overflow is only possible after Int32OverflowLengthHex + // digits. There may be no overflow after Int32OverflowLengthHex if there are leading zeroes. + for (int index = 1; index < Int32OverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = index; + value = (int)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = Int32OverflowLengthHex; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = index; + value = (int)(parsedValue); + return true; + } + // If we try to append a digit to anything larger than -(int.MinValue / 0x08), there will be overflow + if (parsedValue >= -(int.MinValue / 0x08)) + { + bytesConsumed = 0; + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + bytesConsumed = text.Length; + value = (int)(parsedValue); + return true; + } + + #endregion + + #region Int64 + public unsafe static bool TryParseInt64(byte* text, int length, out long value) + { + if (length < 1) + { + value = default; + return false; + } + byte nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = default; + return false; + } + ulong parsedValue = nextDigit; + + if (length <= Int64OverflowLengthHex) + { + // Length is less than or equal to Int64OverflowLengthHex; overflow is not possible + for (int index = 1; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = (long)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than Int64OverflowLengthHex; overflow is only possible after Int64OverflowLengthHex + // digits. There may be no overflow after Int64OverflowLengthHex if there are leading zeroes. + for (int index = 1; index < Int64OverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = (long)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = Int64OverflowLengthHex; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = (long)(parsedValue); + return true; + } + // If we try to append a digit to anything larger than -(long.MinValue / 0x08), there will be overflow + if (parsedValue >= -(long.MinValue / 0x08)) + { + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + value = (long)(parsedValue); + return true; + } + + public unsafe static bool TryParseInt64(byte* text, int length, out long value, out int bytesConsumed) + { + if (length < 1) + { + bytesConsumed = 0; + value = default; + return false; + } + byte nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = 0; + value = default; + return false; + } + ulong parsedValue = nextDigit; + + if (length <= Int64OverflowLengthHex) + { + // Length is less than or equal to Int64OverflowLengthHex; overflow is not possible + for (int index = 1; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = index; + value = (long)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than Int64OverflowLengthHex; overflow is only possible after Int64OverflowLengthHex + // digits. There may be no overflow after Int64OverflowLengthHex if there are leading zeroes. + for (int index = 1; index < Int64OverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = index; + value = (long)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = Int64OverflowLengthHex; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = index; + value = (long)(parsedValue); + return true; + } + // If we try to append a digit to anything larger than -(long.MinValue / 0x08), there will be overflow + if (parsedValue >= -(long.MinValue / 0x08)) + { + bytesConsumed = 0; + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + bytesConsumed = length; + value = (long)(parsedValue); + return true; + } + + public static bool TryParseInt64(ReadOnlySpan text, out long value) + { + if (text.Length < 1) + { + value = default; + return false; + } + byte nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = default; + return false; + } + ulong parsedValue = nextDigit; + + if (text.Length <= Int64OverflowLengthHex) + { + // Length is less than or equal to Int64OverflowLengthHex; overflow is not possible + for (int index = 1; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = (long)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than Int64OverflowLengthHex; overflow is only possible after Int64OverflowLengthHex + // digits. There may be no overflow after Int64OverflowLengthHex if there are leading zeroes. + for (int index = 1; index < Int64OverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = (long)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = Int64OverflowLengthHex; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = (long)(parsedValue); + return true; + } + // If we try to append a digit to anything larger than -(long.MinValue / 0x08), there will be overflow + if (parsedValue >= -(long.MinValue / 0x08)) + { + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + value = (long)(parsedValue); + return true; + } + + public static bool TryParseInt64(ReadOnlySpan text, out long value, out int bytesConsumed) + { + if (text.Length < 1) + { + bytesConsumed = 0; + value = default; + return false; + } + byte nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = 0; + value = default; + return false; + } + ulong parsedValue = nextDigit; + + if (text.Length <= Int64OverflowLengthHex) + { + // Length is less than or equal to Int64OverflowLengthHex; overflow is not possible + for (int index = 1; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = index; + value = (long)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than Int64OverflowLengthHex; overflow is only possible after Int64OverflowLengthHex + // digits. There may be no overflow after Int64OverflowLengthHex if there are leading zeroes. + for (int index = 1; index < Int64OverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = index; + value = (long)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = Int64OverflowLengthHex; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = index; + value = (long)(parsedValue); + return true; + } + // If we try to append a digit to anything larger than -(long.MinValue / 0x08), there will be overflow + if (parsedValue >= -(long.MinValue / 0x08)) + { + bytesConsumed = 0; + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + bytesConsumed = text.Length; + value = (long)(parsedValue); + return true; + } + + #endregion + + } + } + public static partial class InvariantUtf16 + { + public static partial class Hex + { + #region SByte + public unsafe static bool TryParseSByte(char* text, int length, out sbyte value) + { + if (length < 1) + { + value = default; + return false; + } + char nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = default; + return false; + } + uint parsedValue = nextDigit; + + if (length <= SByteOverflowLengthHex) + { + // Length is less than or equal to SByteOverflowLengthHex; overflow is not possible + for (int index = 1; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = (sbyte)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than SByteOverflowLengthHex; overflow is only possible after SByteOverflowLengthHex + // digits. There may be no overflow after SByteOverflowLengthHex if there are leading zeroes. + for (int index = 1; index < SByteOverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = (sbyte)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = SByteOverflowLengthHex; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = (sbyte)(parsedValue); + return true; + } + // If we try to append a digit to anything larger than -(sbyte.MinValue / 0x08), there will be overflow + if (parsedValue >= -(sbyte.MinValue / 0x08)) + { + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + value = (sbyte)(parsedValue); + return true; + } + + public unsafe static bool TryParseSByte(char* text, int length, out sbyte value, out int charsConsumed) + { + if (length < 1) + { + charsConsumed = 0; + value = default; + return false; + } + char nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = 0; + value = default; + return false; + } + uint parsedValue = nextDigit; + + if (length <= SByteOverflowLengthHex) + { + // Length is less than or equal to SByteOverflowLengthHex; overflow is not possible + for (int index = 1; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = index; + value = (sbyte)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than SByteOverflowLengthHex; overflow is only possible after SByteOverflowLengthHex + // digits. There may be no overflow after SByteOverflowLengthHex if there are leading zeroes. + for (int index = 1; index < SByteOverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = index; + value = (sbyte)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = SByteOverflowLengthHex; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = index; + value = (sbyte)(parsedValue); + return true; + } + // If we try to append a digit to anything larger than -(sbyte.MinValue / 0x08), there will be overflow + if (parsedValue >= -(sbyte.MinValue / 0x08)) + { + charsConsumed = 0; + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + charsConsumed = length; + value = (sbyte)(parsedValue); + return true; + } + + public static bool TryParseSByte(ReadOnlySpan text, out sbyte value) + { + if (text.Length < 1) + { + value = default; + return false; + } + char nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = default; + return false; + } + uint parsedValue = nextDigit; + + if (text.Length <= SByteOverflowLengthHex) + { + // Length is less than or equal to SByteOverflowLengthHex; overflow is not possible + for (int index = 1; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = (sbyte)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than SByteOverflowLengthHex; overflow is only possible after SByteOverflowLengthHex + // digits. There may be no overflow after SByteOverflowLengthHex if there are leading zeroes. + for (int index = 1; index < SByteOverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = (sbyte)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = SByteOverflowLengthHex; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = (sbyte)(parsedValue); + return true; + } + // If we try to append a digit to anything larger than -(sbyte.MinValue / 0x08), there will be overflow + if (parsedValue >= -(sbyte.MinValue / 0x08)) + { + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + value = (sbyte)(parsedValue); + return true; + } + + public static bool TryParseSByte(ReadOnlySpan text, out sbyte value, out int charsConsumed) + { + if (text.Length < 1) + { + charsConsumed = 0; + value = default; + return false; + } + char nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = 0; + value = default; + return false; + } + uint parsedValue = nextDigit; + + if (text.Length <= SByteOverflowLengthHex) + { + // Length is less than or equal to SByteOverflowLengthHex; overflow is not possible + for (int index = 1; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = index; + value = (sbyte)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than SByteOverflowLengthHex; overflow is only possible after SByteOverflowLengthHex + // digits. There may be no overflow after SByteOverflowLengthHex if there are leading zeroes. + for (int index = 1; index < SByteOverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = index; + value = (sbyte)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = SByteOverflowLengthHex; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = index; + value = (sbyte)(parsedValue); + return true; + } + // If we try to append a digit to anything larger than -(sbyte.MinValue / 0x08), there will be overflow + if (parsedValue >= -(sbyte.MinValue / 0x08)) + { + charsConsumed = 0; + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + charsConsumed = text.Length; + value = (sbyte)(parsedValue); + return true; + } + + #endregion + + #region Int16 + public unsafe static bool TryParseInt16(char* text, int length, out short value) + { + if (length < 1) + { + value = default; + return false; + } + char nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = default; + return false; + } + uint parsedValue = nextDigit; + + if (length <= Int16OverflowLengthHex) + { + // Length is less than or equal to Int16OverflowLengthHex; overflow is not possible + for (int index = 1; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = (short)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than Int16OverflowLengthHex; overflow is only possible after Int16OverflowLengthHex + // digits. There may be no overflow after Int16OverflowLengthHex if there are leading zeroes. + for (int index = 1; index < Int16OverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = (short)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = Int16OverflowLengthHex; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = (short)(parsedValue); + return true; + } + // If we try to append a digit to anything larger than -(short.MinValue / 0x08), there will be overflow + if (parsedValue >= -(short.MinValue / 0x08)) + { + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + value = (short)(parsedValue); + return true; + } + + public unsafe static bool TryParseInt16(char* text, int length, out short value, out int charsConsumed) + { + if (length < 1) + { + charsConsumed = 0; + value = default; + return false; + } + char nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = 0; + value = default; + return false; + } + uint parsedValue = nextDigit; + + if (length <= Int16OverflowLengthHex) + { + // Length is less than or equal to Int16OverflowLengthHex; overflow is not possible + for (int index = 1; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = index; + value = (short)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than Int16OverflowLengthHex; overflow is only possible after Int16OverflowLengthHex + // digits. There may be no overflow after Int16OverflowLengthHex if there are leading zeroes. + for (int index = 1; index < Int16OverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = index; + value = (short)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = Int16OverflowLengthHex; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = index; + value = (short)(parsedValue); + return true; + } + // If we try to append a digit to anything larger than -(short.MinValue / 0x08), there will be overflow + if (parsedValue >= -(short.MinValue / 0x08)) + { + charsConsumed = 0; + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + charsConsumed = length; + value = (short)(parsedValue); + return true; + } + + public static bool TryParseInt16(ReadOnlySpan text, out short value) + { + if (text.Length < 1) + { + value = default; + return false; + } + char nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = default; + return false; + } + uint parsedValue = nextDigit; + + if (text.Length <= Int16OverflowLengthHex) + { + // Length is less than or equal to Int16OverflowLengthHex; overflow is not possible + for (int index = 1; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = (short)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than Int16OverflowLengthHex; overflow is only possible after Int16OverflowLengthHex + // digits. There may be no overflow after Int16OverflowLengthHex if there are leading zeroes. + for (int index = 1; index < Int16OverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = (short)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = Int16OverflowLengthHex; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = (short)(parsedValue); + return true; + } + // If we try to append a digit to anything larger than -(short.MinValue / 0x08), there will be overflow + if (parsedValue >= -(short.MinValue / 0x08)) + { + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + value = (short)(parsedValue); + return true; + } + + public static bool TryParseInt16(ReadOnlySpan text, out short value, out int charsConsumed) + { + if (text.Length < 1) + { + charsConsumed = 0; + value = default; + return false; + } + char nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = 0; + value = default; + return false; + } + uint parsedValue = nextDigit; + + if (text.Length <= Int16OverflowLengthHex) + { + // Length is less than or equal to Int16OverflowLengthHex; overflow is not possible + for (int index = 1; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = index; + value = (short)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than Int16OverflowLengthHex; overflow is only possible after Int16OverflowLengthHex + // digits. There may be no overflow after Int16OverflowLengthHex if there are leading zeroes. + for (int index = 1; index < Int16OverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = index; + value = (short)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = Int16OverflowLengthHex; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = index; + value = (short)(parsedValue); + return true; + } + // If we try to append a digit to anything larger than -(short.MinValue / 0x08), there will be overflow + if (parsedValue >= -(short.MinValue / 0x08)) + { + charsConsumed = 0; + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + charsConsumed = text.Length; + value = (short)(parsedValue); + return true; + } + + #endregion + + #region Int32 + public unsafe static bool TryParseInt32(char* text, int length, out int value) + { + if (length < 1) + { + value = default; + return false; + } + char nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = default; + return false; + } + uint parsedValue = nextDigit; + + if (length <= Int32OverflowLengthHex) + { + // Length is less than or equal to Int32OverflowLengthHex; overflow is not possible + for (int index = 1; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = (int)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than Int32OverflowLengthHex; overflow is only possible after Int32OverflowLengthHex + // digits. There may be no overflow after Int32OverflowLengthHex if there are leading zeroes. + for (int index = 1; index < Int32OverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = (int)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = Int32OverflowLengthHex; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = (int)(parsedValue); + return true; + } + // If we try to append a digit to anything larger than -(int.MinValue / 0x08), there will be overflow + if (parsedValue >= -(int.MinValue / 0x08)) + { + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + value = (int)(parsedValue); + return true; + } + + public unsafe static bool TryParseInt32(char* text, int length, out int value, out int charsConsumed) + { + if (length < 1) + { + charsConsumed = 0; + value = default; + return false; + } + char nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = 0; + value = default; + return false; + } + uint parsedValue = nextDigit; + + if (length <= Int32OverflowLengthHex) + { + // Length is less than or equal to Int32OverflowLengthHex; overflow is not possible + for (int index = 1; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = index; + value = (int)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than Int32OverflowLengthHex; overflow is only possible after Int32OverflowLengthHex + // digits. There may be no overflow after Int32OverflowLengthHex if there are leading zeroes. + for (int index = 1; index < Int32OverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = index; + value = (int)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = Int32OverflowLengthHex; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = index; + value = (int)(parsedValue); + return true; + } + // If we try to append a digit to anything larger than -(int.MinValue / 0x08), there will be overflow + if (parsedValue >= -(int.MinValue / 0x08)) + { + charsConsumed = 0; + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + charsConsumed = length; + value = (int)(parsedValue); + return true; + } + + public static bool TryParseInt32(ReadOnlySpan text, out int value) + { + if (text.Length < 1) + { + value = default; + return false; + } + char nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = default; + return false; + } + uint parsedValue = nextDigit; + + if (text.Length <= Int32OverflowLengthHex) + { + // Length is less than or equal to Int32OverflowLengthHex; overflow is not possible + for (int index = 1; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = (int)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than Int32OverflowLengthHex; overflow is only possible after Int32OverflowLengthHex + // digits. There may be no overflow after Int32OverflowLengthHex if there are leading zeroes. + for (int index = 1; index < Int32OverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = (int)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = Int32OverflowLengthHex; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = (int)(parsedValue); + return true; + } + // If we try to append a digit to anything larger than -(int.MinValue / 0x08), there will be overflow + if (parsedValue >= -(int.MinValue / 0x08)) + { + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + value = (int)(parsedValue); + return true; + } + + public static bool TryParseInt32(ReadOnlySpan text, out int value, out int charsConsumed) + { + if (text.Length < 1) + { + charsConsumed = 0; + value = default; + return false; + } + char nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = 0; + value = default; + return false; + } + uint parsedValue = nextDigit; + + if (text.Length <= Int32OverflowLengthHex) + { + // Length is less than or equal to Int32OverflowLengthHex; overflow is not possible + for (int index = 1; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = index; + value = (int)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than Int32OverflowLengthHex; overflow is only possible after Int32OverflowLengthHex + // digits. There may be no overflow after Int32OverflowLengthHex if there are leading zeroes. + for (int index = 1; index < Int32OverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = index; + value = (int)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = Int32OverflowLengthHex; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = index; + value = (int)(parsedValue); + return true; + } + // If we try to append a digit to anything larger than -(int.MinValue / 0x08), there will be overflow + if (parsedValue >= -(int.MinValue / 0x08)) + { + charsConsumed = 0; + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + charsConsumed = text.Length; + value = (int)(parsedValue); + return true; + } + + #endregion + + #region Int64 + public unsafe static bool TryParseInt64(char* text, int length, out long value) + { + if (length < 1) + { + value = default; + return false; + } + char nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = default; + return false; + } + ulong parsedValue = nextDigit; + + if (length <= Int64OverflowLengthHex) + { + // Length is less than or equal to Int64OverflowLengthHex; overflow is not possible + for (int index = 1; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = (long)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than Int64OverflowLengthHex; overflow is only possible after Int64OverflowLengthHex + // digits. There may be no overflow after Int64OverflowLengthHex if there are leading zeroes. + for (int index = 1; index < Int64OverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = (long)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = Int64OverflowLengthHex; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = (long)(parsedValue); + return true; + } + // If we try to append a digit to anything larger than -(long.MinValue / 0x08), there will be overflow + if (parsedValue >= -(long.MinValue / 0x08)) + { + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + value = (long)(parsedValue); + return true; + } + + public unsafe static bool TryParseInt64(char* text, int length, out long value, out int charsConsumed) + { + if (length < 1) + { + charsConsumed = 0; + value = default; + return false; + } + char nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = 0; + value = default; + return false; + } + ulong parsedValue = nextDigit; + + if (length <= Int64OverflowLengthHex) + { + // Length is less than or equal to Int64OverflowLengthHex; overflow is not possible + for (int index = 1; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = index; + value = (long)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than Int64OverflowLengthHex; overflow is only possible after Int64OverflowLengthHex + // digits. There may be no overflow after Int64OverflowLengthHex if there are leading zeroes. + for (int index = 1; index < Int64OverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = index; + value = (long)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = Int64OverflowLengthHex; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = index; + value = (long)(parsedValue); + return true; + } + // If we try to append a digit to anything larger than -(long.MinValue / 0x08), there will be overflow + if (parsedValue >= -(long.MinValue / 0x08)) + { + charsConsumed = 0; + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + charsConsumed = length; + value = (long)(parsedValue); + return true; + } + + public static bool TryParseInt64(ReadOnlySpan text, out long value) + { + if (text.Length < 1) + { + value = default; + return false; + } + char nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = default; + return false; + } + ulong parsedValue = nextDigit; + + if (text.Length <= Int64OverflowLengthHex) + { + // Length is less than or equal to Int64OverflowLengthHex; overflow is not possible + for (int index = 1; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = (long)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than Int64OverflowLengthHex; overflow is only possible after Int64OverflowLengthHex + // digits. There may be no overflow after Int64OverflowLengthHex if there are leading zeroes. + for (int index = 1; index < Int64OverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = (long)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = Int64OverflowLengthHex; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = (long)(parsedValue); + return true; + } + // If we try to append a digit to anything larger than -(long.MinValue / 0x08), there will be overflow + if (parsedValue >= -(long.MinValue / 0x08)) + { + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + value = (long)(parsedValue); + return true; + } + + public static bool TryParseInt64(ReadOnlySpan text, out long value, out int charsConsumed) + { + if (text.Length < 1) + { + charsConsumed = 0; + value = default; + return false; + } + char nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = 0; + value = default; + return false; + } + ulong parsedValue = nextDigit; + + if (text.Length <= Int64OverflowLengthHex) + { + // Length is less than or equal to Int64OverflowLengthHex; overflow is not possible + for (int index = 1; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = index; + value = (long)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than Int64OverflowLengthHex; overflow is only possible after Int64OverflowLengthHex + // digits. There may be no overflow after Int64OverflowLengthHex if there are leading zeroes. + for (int index = 1; index < Int64OverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = index; + value = (long)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = Int64OverflowLengthHex; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = index; + value = (long)(parsedValue); + return true; + } + // If we try to append a digit to anything larger than -(long.MinValue / 0x08), there will be overflow + if (parsedValue >= -(long.MinValue / 0x08)) + { + charsConsumed = 0; + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + charsConsumed = text.Length; + value = (long)(parsedValue); + return true; + } + + #endregion + + } + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Parsing/InvariantUnsigned.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Parsing/InvariantUnsigned.cs new file mode 100644 index 000000000..6509f0410 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Parsing/InvariantUnsigned.cs @@ -0,0 +1,2332 @@ +// 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. + + +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace System.Text +{ + public static partial class PrimitiveParser + { + public static partial class InvariantUtf8 + { + #region Byte + public unsafe static bool TryParseByte(byte* text, int length, out byte value) + { + if (length < 1) + { + value = default; + return false; + } + + // Parse the first digit separately. If invalid here, we need to return false. + uint firstDigit = text[0] - 48u; // '0' + if (firstDigit > 9) + { + value = default; + return false; + } + uint parsedValue = firstDigit; + + if (length < ByteOverflowLength) + { + // Length is less than ByteOverflowLength; overflow is not possible + for (int index = 1; index < length; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + value = (byte)(parsedValue); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than ByteOverflowLength; overflow is only possible after ByteOverflowLength + // digits. There may be no overflow after ByteOverflowLength if there are leading zeroes. + for (int index = 1; index < ByteOverflowLength - 1; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + value = (byte)(parsedValue); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = ByteOverflowLength - 1; index < length; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + value = (byte)(parsedValue); + return true; + } + // If parsedValue > (byte.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (byte.MaxValue / 10), any nextDigit greater than 5 implies overflow. + if (parsedValue > byte.MaxValue / 10 || (parsedValue == byte.MaxValue / 10 && nextDigit > 5)) + { + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + value = (byte)(parsedValue); + return true; + } + + public unsafe static bool TryParseByte(byte* text, int length, out byte value, out int bytesConsumed) + { + if (length < 1) + { + bytesConsumed = 0; + value = default; + return false; + } + + // Parse the first digit separately. If invalid here, we need to return false. + uint firstDigit = text[0] - 48u; // '0' + if (firstDigit > 9) + { + bytesConsumed = 0; + value = default; + return false; + } + uint parsedValue = firstDigit; + + if (length < ByteOverflowLength) + { + // Length is less than ByteOverflowLength; overflow is not possible + for (int index = 1; index < length; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + bytesConsumed = index; + value = (byte)(parsedValue); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than ByteOverflowLength; overflow is only possible after ByteOverflowLength + // digits. There may be no overflow after ByteOverflowLength if there are leading zeroes. + for (int index = 1; index < ByteOverflowLength - 1; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + bytesConsumed = index; + value = (byte)(parsedValue); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = ByteOverflowLength - 1; index < length; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + bytesConsumed = index; + value = (byte)(parsedValue); + return true; + } + // If parsedValue > (byte.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (byte.MaxValue / 10), any nextDigit greater than 5 implies overflow. + if (parsedValue > byte.MaxValue / 10 || (parsedValue == byte.MaxValue / 10 && nextDigit > 5)) + { + bytesConsumed = 0; + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + bytesConsumed = length; + value = (byte)(parsedValue); + return true; + } + + public static bool TryParseByte(ReadOnlySpan text, out byte value) + { + if (text.Length < 1) + { + value = default; + return false; + } + + // Parse the first digit separately. If invalid here, we need to return false. + uint firstDigit = text[0] - 48u; // '0' + if (firstDigit > 9) + { + value = default; + return false; + } + uint parsedValue = firstDigit; + + if (text.Length < ByteOverflowLength) + { + // Length is less than ByteOverflowLength; overflow is not possible + for (int index = 1; index < text.Length; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + value = (byte)(parsedValue); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than ByteOverflowLength; overflow is only possible after ByteOverflowLength + // digits. There may be no overflow after ByteOverflowLength if there are leading zeroes. + for (int index = 1; index < ByteOverflowLength - 1; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + value = (byte)(parsedValue); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = ByteOverflowLength - 1; index < text.Length; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + value = (byte)(parsedValue); + return true; + } + // If parsedValue > (byte.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (byte.MaxValue / 10), any nextDigit greater than 5 implies overflow. + if (parsedValue > byte.MaxValue / 10 || (parsedValue == byte.MaxValue / 10 && nextDigit > 5)) + { + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + value = (byte)(parsedValue); + return true; + } + + public static bool TryParseByte(ReadOnlySpan text, out byte value, out int bytesConsumed) + { + if (text.Length < 1) + { + bytesConsumed = 0; + value = default; + return false; + } + + // Parse the first digit separately. If invalid here, we need to return false. + uint firstDigit = text[0] - 48u; // '0' + if (firstDigit > 9) + { + bytesConsumed = 0; + value = default; + return false; + } + uint parsedValue = firstDigit; + + if (text.Length < ByteOverflowLength) + { + // Length is less than ByteOverflowLength; overflow is not possible + for (int index = 1; index < text.Length; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + bytesConsumed = index; + value = (byte)(parsedValue); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than ByteOverflowLength; overflow is only possible after ByteOverflowLength + // digits. There may be no overflow after ByteOverflowLength if there are leading zeroes. + for (int index = 1; index < ByteOverflowLength - 1; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + bytesConsumed = index; + value = (byte)(parsedValue); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = ByteOverflowLength - 1; index < text.Length; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + bytesConsumed = index; + value = (byte)(parsedValue); + return true; + } + // If parsedValue > (byte.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (byte.MaxValue / 10), any nextDigit greater than 5 implies overflow. + if (parsedValue > byte.MaxValue / 10 || (parsedValue == byte.MaxValue / 10 && nextDigit > 5)) + { + bytesConsumed = 0; + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + bytesConsumed = text.Length; + value = (byte)(parsedValue); + return true; + } + + #endregion + + #region UInt16 + public unsafe static bool TryParseUInt16(byte* text, int length, out ushort value) + { + if (length < 1) + { + value = default; + return false; + } + + // Parse the first digit separately. If invalid here, we need to return false. + uint firstDigit = text[0] - 48u; // '0' + if (firstDigit > 9) + { + value = default; + return false; + } + uint parsedValue = firstDigit; + + if (length < UInt16OverflowLength) + { + // Length is less than UInt16OverflowLength; overflow is not possible + for (int index = 1; index < length; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + value = (ushort)(parsedValue); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than UInt16OverflowLength; overflow is only possible after UInt16OverflowLength + // digits. There may be no overflow after UInt16OverflowLength if there are leading zeroes. + for (int index = 1; index < UInt16OverflowLength - 1; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + value = (ushort)(parsedValue); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = UInt16OverflowLength - 1; index < length; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + value = (ushort)(parsedValue); + return true; + } + // If parsedValue > (ushort.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (ushort.MaxValue / 10), any nextDigit greater than 5 implies overflow. + if (parsedValue > ushort.MaxValue / 10 || (parsedValue == ushort.MaxValue / 10 && nextDigit > 5)) + { + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + value = (ushort)(parsedValue); + return true; + } + + public unsafe static bool TryParseUInt16(byte* text, int length, out ushort value, out int bytesConsumed) + { + if (length < 1) + { + bytesConsumed = 0; + value = default; + return false; + } + + // Parse the first digit separately. If invalid here, we need to return false. + uint firstDigit = text[0] - 48u; // '0' + if (firstDigit > 9) + { + bytesConsumed = 0; + value = default; + return false; + } + uint parsedValue = firstDigit; + + if (length < UInt16OverflowLength) + { + // Length is less than UInt16OverflowLength; overflow is not possible + for (int index = 1; index < length; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + bytesConsumed = index; + value = (ushort)(parsedValue); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than UInt16OverflowLength; overflow is only possible after UInt16OverflowLength + // digits. There may be no overflow after UInt16OverflowLength if there are leading zeroes. + for (int index = 1; index < UInt16OverflowLength - 1; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + bytesConsumed = index; + value = (ushort)(parsedValue); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = UInt16OverflowLength - 1; index < length; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + bytesConsumed = index; + value = (ushort)(parsedValue); + return true; + } + // If parsedValue > (ushort.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (ushort.MaxValue / 10), any nextDigit greater than 5 implies overflow. + if (parsedValue > ushort.MaxValue / 10 || (parsedValue == ushort.MaxValue / 10 && nextDigit > 5)) + { + bytesConsumed = 0; + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + bytesConsumed = length; + value = (ushort)(parsedValue); + return true; + } + + public static bool TryParseUInt16(ReadOnlySpan text, out ushort value) + { + if (text.Length < 1) + { + value = default; + return false; + } + + // Parse the first digit separately. If invalid here, we need to return false. + uint firstDigit = text[0] - 48u; // '0' + if (firstDigit > 9) + { + value = default; + return false; + } + uint parsedValue = firstDigit; + + if (text.Length < UInt16OverflowLength) + { + // Length is less than UInt16OverflowLength; overflow is not possible + for (int index = 1; index < text.Length; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + value = (ushort)(parsedValue); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than UInt16OverflowLength; overflow is only possible after UInt16OverflowLength + // digits. There may be no overflow after UInt16OverflowLength if there are leading zeroes. + for (int index = 1; index < UInt16OverflowLength - 1; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + value = (ushort)(parsedValue); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = UInt16OverflowLength - 1; index < text.Length; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + value = (ushort)(parsedValue); + return true; + } + // If parsedValue > (ushort.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (ushort.MaxValue / 10), any nextDigit greater than 5 implies overflow. + if (parsedValue > ushort.MaxValue / 10 || (parsedValue == ushort.MaxValue / 10 && nextDigit > 5)) + { + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + value = (ushort)(parsedValue); + return true; + } + + public static bool TryParseUInt16(ReadOnlySpan text, out ushort value, out int bytesConsumed) + { + if (text.Length < 1) + { + bytesConsumed = 0; + value = default; + return false; + } + + // Parse the first digit separately. If invalid here, we need to return false. + uint firstDigit = text[0] - 48u; // '0' + if (firstDigit > 9) + { + bytesConsumed = 0; + value = default; + return false; + } + uint parsedValue = firstDigit; + + if (text.Length < UInt16OverflowLength) + { + // Length is less than UInt16OverflowLength; overflow is not possible + for (int index = 1; index < text.Length; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + bytesConsumed = index; + value = (ushort)(parsedValue); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than UInt16OverflowLength; overflow is only possible after UInt16OverflowLength + // digits. There may be no overflow after UInt16OverflowLength if there are leading zeroes. + for (int index = 1; index < UInt16OverflowLength - 1; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + bytesConsumed = index; + value = (ushort)(parsedValue); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = UInt16OverflowLength - 1; index < text.Length; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + bytesConsumed = index; + value = (ushort)(parsedValue); + return true; + } + // If parsedValue > (ushort.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (ushort.MaxValue / 10), any nextDigit greater than 5 implies overflow. + if (parsedValue > ushort.MaxValue / 10 || (parsedValue == ushort.MaxValue / 10 && nextDigit > 5)) + { + bytesConsumed = 0; + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + bytesConsumed = text.Length; + value = (ushort)(parsedValue); + return true; + } + + #endregion + + #region UInt32 + public unsafe static bool TryParseUInt32(byte* text, int length, out uint value) + { + if (length < 1) + { + value = default; + return false; + } + + // Parse the first digit separately. If invalid here, we need to return false. + uint firstDigit = text[0] - 48u; // '0' + if (firstDigit > 9) + { + value = default; + return false; + } + uint parsedValue = firstDigit; + + if (length < UInt32OverflowLength) + { + // Length is less than UInt32OverflowLength; overflow is not possible + for (int index = 1; index < length; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + value = parsedValue; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than UInt32OverflowLength; overflow is only possible after UInt32OverflowLength + // digits. There may be no overflow after UInt32OverflowLength if there are leading zeroes. + for (int index = 1; index < UInt32OverflowLength - 1; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + value = parsedValue; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = UInt32OverflowLength - 1; index < length; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + value = parsedValue; + return true; + } + // If parsedValue > (uint.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (uint.MaxValue / 10), any nextDigit greater than 5 implies overflow. + if (parsedValue > uint.MaxValue / 10 || (parsedValue == uint.MaxValue / 10 && nextDigit > 5)) + { + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + value = parsedValue; + return true; + } + + public unsafe static bool TryParseUInt32(byte* text, int length, out uint value, out int bytesConsumed) + { + if (length < 1) + { + bytesConsumed = 0; + value = default; + return false; + } + + // Parse the first digit separately. If invalid here, we need to return false. + uint firstDigit = text[0] - 48u; // '0' + if (firstDigit > 9) + { + bytesConsumed = 0; + value = default; + return false; + } + uint parsedValue = firstDigit; + + if (length < UInt32OverflowLength) + { + // Length is less than UInt32OverflowLength; overflow is not possible + for (int index = 1; index < length; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + bytesConsumed = index; + value = parsedValue; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than UInt32OverflowLength; overflow is only possible after UInt32OverflowLength + // digits. There may be no overflow after UInt32OverflowLength if there are leading zeroes. + for (int index = 1; index < UInt32OverflowLength - 1; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + bytesConsumed = index; + value = parsedValue; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = UInt32OverflowLength - 1; index < length; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + bytesConsumed = index; + value = parsedValue; + return true; + } + // If parsedValue > (uint.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (uint.MaxValue / 10), any nextDigit greater than 5 implies overflow. + if (parsedValue > uint.MaxValue / 10 || (parsedValue == uint.MaxValue / 10 && nextDigit > 5)) + { + bytesConsumed = 0; + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + bytesConsumed = length; + value = parsedValue; + return true; + } + + public static bool TryParseUInt32(ReadOnlySpan text, out uint value) + { + if (text.Length < 1) + { + value = default; + return false; + } + + // Parse the first digit separately. If invalid here, we need to return false. + uint firstDigit = text[0] - 48u; // '0' + if (firstDigit > 9) + { + value = default; + return false; + } + uint parsedValue = firstDigit; + + if (text.Length < UInt32OverflowLength) + { + // Length is less than UInt32OverflowLength; overflow is not possible + for (int index = 1; index < text.Length; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + value = parsedValue; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than UInt32OverflowLength; overflow is only possible after UInt32OverflowLength + // digits. There may be no overflow after UInt32OverflowLength if there are leading zeroes. + for (int index = 1; index < UInt32OverflowLength - 1; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + value = parsedValue; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = UInt32OverflowLength - 1; index < text.Length; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + value = parsedValue; + return true; + } + // If parsedValue > (uint.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (uint.MaxValue / 10), any nextDigit greater than 5 implies overflow. + if (parsedValue > uint.MaxValue / 10 || (parsedValue == uint.MaxValue / 10 && nextDigit > 5)) + { + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + value = parsedValue; + return true; + } + + public static bool TryParseUInt32(ReadOnlySpan text, out uint value, out int bytesConsumed) + { + if (text.Length < 1) + { + bytesConsumed = 0; + value = default; + return false; + } + + // Parse the first digit separately. If invalid here, we need to return false. + uint firstDigit = text[0] - 48u; // '0' + if (firstDigit > 9) + { + bytesConsumed = 0; + value = default; + return false; + } + uint parsedValue = firstDigit; + + if (text.Length < UInt32OverflowLength) + { + // Length is less than UInt32OverflowLength; overflow is not possible + for (int index = 1; index < text.Length; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + bytesConsumed = index; + value = parsedValue; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than UInt32OverflowLength; overflow is only possible after UInt32OverflowLength + // digits. There may be no overflow after UInt32OverflowLength if there are leading zeroes. + for (int index = 1; index < UInt32OverflowLength - 1; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + bytesConsumed = index; + value = parsedValue; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = UInt32OverflowLength - 1; index < text.Length; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + bytesConsumed = index; + value = parsedValue; + return true; + } + // If parsedValue > (uint.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (uint.MaxValue / 10), any nextDigit greater than 5 implies overflow. + if (parsedValue > uint.MaxValue / 10 || (parsedValue == uint.MaxValue / 10 && nextDigit > 5)) + { + bytesConsumed = 0; + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + bytesConsumed = text.Length; + value = parsedValue; + return true; + } + + #endregion + + #region UInt64 + public unsafe static bool TryParseUInt64(byte* text, int length, out ulong value) + { + if (length < 1) + { + value = default; + return false; + } + + // Parse the first digit separately. If invalid here, we need to return false. + ulong firstDigit = text[0] - 48u; // '0' + if (firstDigit > 9) + { + value = default; + return false; + } + ulong parsedValue = firstDigit; + + if (length < UInt64OverflowLength) + { + // Length is less than UInt64OverflowLength; overflow is not possible + for (int index = 1; index < length; index++) + { + ulong nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + value = parsedValue; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than UInt64OverflowLength; overflow is only possible after UInt64OverflowLength + // digits. There may be no overflow after UInt64OverflowLength if there are leading zeroes. + for (int index = 1; index < UInt64OverflowLength - 1; index++) + { + ulong nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + value = parsedValue; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = UInt64OverflowLength - 1; index < length; index++) + { + ulong nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + value = parsedValue; + return true; + } + // If parsedValue > (ulong.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (ulong.MaxValue / 10), any nextDigit greater than 5 implies overflow. + if (parsedValue > ulong.MaxValue / 10 || (parsedValue == ulong.MaxValue / 10 && nextDigit > 5)) + { + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + value = parsedValue; + return true; + } + + public unsafe static bool TryParseUInt64(byte* text, int length, out ulong value, out int bytesConsumed) + { + if (length < 1) + { + bytesConsumed = 0; + value = default; + return false; + } + + // Parse the first digit separately. If invalid here, we need to return false. + ulong firstDigit = text[0] - 48u; // '0' + if (firstDigit > 9) + { + bytesConsumed = 0; + value = default; + return false; + } + ulong parsedValue = firstDigit; + + if (length < UInt64OverflowLength) + { + // Length is less than UInt64OverflowLength; overflow is not possible + for (int index = 1; index < length; index++) + { + ulong nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + bytesConsumed = index; + value = parsedValue; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than UInt64OverflowLength; overflow is only possible after UInt64OverflowLength + // digits. There may be no overflow after UInt64OverflowLength if there are leading zeroes. + for (int index = 1; index < UInt64OverflowLength - 1; index++) + { + ulong nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + bytesConsumed = index; + value = parsedValue; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = UInt64OverflowLength - 1; index < length; index++) + { + ulong nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + bytesConsumed = index; + value = parsedValue; + return true; + } + // If parsedValue > (ulong.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (ulong.MaxValue / 10), any nextDigit greater than 5 implies overflow. + if (parsedValue > ulong.MaxValue / 10 || (parsedValue == ulong.MaxValue / 10 && nextDigit > 5)) + { + bytesConsumed = 0; + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + bytesConsumed = length; + value = parsedValue; + return true; + } + + public static bool TryParseUInt64(ReadOnlySpan text, out ulong value) + { + if (text.Length < 1) + { + value = default; + return false; + } + + // Parse the first digit separately. If invalid here, we need to return false. + ulong firstDigit = text[0] - 48u; // '0' + if (firstDigit > 9) + { + value = default; + return false; + } + ulong parsedValue = firstDigit; + + if (text.Length < UInt64OverflowLength) + { + // Length is less than UInt64OverflowLength; overflow is not possible + for (int index = 1; index < text.Length; index++) + { + ulong nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + value = parsedValue; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than UInt64OverflowLength; overflow is only possible after UInt64OverflowLength + // digits. There may be no overflow after UInt64OverflowLength if there are leading zeroes. + for (int index = 1; index < UInt64OverflowLength - 1; index++) + { + ulong nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + value = parsedValue; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = UInt64OverflowLength - 1; index < text.Length; index++) + { + ulong nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + value = parsedValue; + return true; + } + // If parsedValue > (ulong.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (ulong.MaxValue / 10), any nextDigit greater than 5 implies overflow. + if (parsedValue > ulong.MaxValue / 10 || (parsedValue == ulong.MaxValue / 10 && nextDigit > 5)) + { + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + value = parsedValue; + return true; + } + + public static bool TryParseUInt64(ReadOnlySpan text, out ulong value, out int bytesConsumed) + { + if (text.Length < 1) + { + bytesConsumed = 0; + value = default; + return false; + } + + // Parse the first digit separately. If invalid here, we need to return false. + ulong firstDigit = text[0] - 48u; // '0' + if (firstDigit > 9) + { + bytesConsumed = 0; + value = default; + return false; + } + ulong parsedValue = firstDigit; + + if (text.Length < UInt64OverflowLength) + { + // Length is less than UInt64OverflowLength; overflow is not possible + for (int index = 1; index < text.Length; index++) + { + ulong nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + bytesConsumed = index; + value = parsedValue; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than UInt64OverflowLength; overflow is only possible after UInt64OverflowLength + // digits. There may be no overflow after UInt64OverflowLength if there are leading zeroes. + for (int index = 1; index < UInt64OverflowLength - 1; index++) + { + ulong nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + bytesConsumed = index; + value = parsedValue; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = UInt64OverflowLength - 1; index < text.Length; index++) + { + ulong nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + bytesConsumed = index; + value = parsedValue; + return true; + } + // If parsedValue > (ulong.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (ulong.MaxValue / 10), any nextDigit greater than 5 implies overflow. + if (parsedValue > ulong.MaxValue / 10 || (parsedValue == ulong.MaxValue / 10 && nextDigit > 5)) + { + bytesConsumed = 0; + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + bytesConsumed = text.Length; + value = parsedValue; + return true; + } + + #endregion + + } + public static partial class InvariantUtf16 + { + #region Byte + public unsafe static bool TryParseByte(char* text, int length, out byte value) + { + if (length < 1) + { + value = default; + return false; + } + + // Parse the first digit separately. If invalid here, we need to return false. + uint firstDigit = text[0] - 48u; // '0' + if (firstDigit > 9) + { + value = default; + return false; + } + uint parsedValue = firstDigit; + + if (length < ByteOverflowLength) + { + // Length is less than ByteOverflowLength; overflow is not possible + for (int index = 1; index < length; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + value = (byte)(parsedValue); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than ByteOverflowLength; overflow is only possible after ByteOverflowLength + // digits. There may be no overflow after ByteOverflowLength if there are leading zeroes. + for (int index = 1; index < ByteOverflowLength - 1; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + value = (byte)(parsedValue); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = ByteOverflowLength - 1; index < length; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + value = (byte)(parsedValue); + return true; + } + // If parsedValue > (byte.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (byte.MaxValue / 10), any nextDigit greater than 5 implies overflow. + if (parsedValue > byte.MaxValue / 10 || (parsedValue == byte.MaxValue / 10 && nextDigit > 5)) + { + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + value = (byte)(parsedValue); + return true; + } + + public unsafe static bool TryParseByte(char* text, int length, out byte value, out int charsConsumed) + { + if (length < 1) + { + charsConsumed = 0; + value = default; + return false; + } + + // Parse the first digit separately. If invalid here, we need to return false. + uint firstDigit = text[0] - 48u; // '0' + if (firstDigit > 9) + { + charsConsumed = 0; + value = default; + return false; + } + uint parsedValue = firstDigit; + + if (length < ByteOverflowLength) + { + // Length is less than ByteOverflowLength; overflow is not possible + for (int index = 1; index < length; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + charsConsumed = index; + value = (byte)(parsedValue); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than ByteOverflowLength; overflow is only possible after ByteOverflowLength + // digits. There may be no overflow after ByteOverflowLength if there are leading zeroes. + for (int index = 1; index < ByteOverflowLength - 1; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + charsConsumed = index; + value = (byte)(parsedValue); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = ByteOverflowLength - 1; index < length; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + charsConsumed = index; + value = (byte)(parsedValue); + return true; + } + // If parsedValue > (byte.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (byte.MaxValue / 10), any nextDigit greater than 5 implies overflow. + if (parsedValue > byte.MaxValue / 10 || (parsedValue == byte.MaxValue / 10 && nextDigit > 5)) + { + charsConsumed = 0; + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + charsConsumed = length; + value = (byte)(parsedValue); + return true; + } + + public static bool TryParseByte(ReadOnlySpan text, out byte value) + { + if (text.Length < 1) + { + value = default; + return false; + } + + // Parse the first digit separately. If invalid here, we need to return false. + uint firstDigit = text[0] - 48u; // '0' + if (firstDigit > 9) + { + value = default; + return false; + } + uint parsedValue = firstDigit; + + if (text.Length < ByteOverflowLength) + { + // Length is less than ByteOverflowLength; overflow is not possible + for (int index = 1; index < text.Length; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + value = (byte)(parsedValue); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than ByteOverflowLength; overflow is only possible after ByteOverflowLength + // digits. There may be no overflow after ByteOverflowLength if there are leading zeroes. + for (int index = 1; index < ByteOverflowLength - 1; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + value = (byte)(parsedValue); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = ByteOverflowLength - 1; index < text.Length; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + value = (byte)(parsedValue); + return true; + } + // If parsedValue > (byte.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (byte.MaxValue / 10), any nextDigit greater than 5 implies overflow. + if (parsedValue > byte.MaxValue / 10 || (parsedValue == byte.MaxValue / 10 && nextDigit > 5)) + { + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + value = (byte)(parsedValue); + return true; + } + + public static bool TryParseByte(ReadOnlySpan text, out byte value, out int charsConsumed) + { + if (text.Length < 1) + { + charsConsumed = 0; + value = default; + return false; + } + + // Parse the first digit separately. If invalid here, we need to return false. + uint firstDigit = text[0] - 48u; // '0' + if (firstDigit > 9) + { + charsConsumed = 0; + value = default; + return false; + } + uint parsedValue = firstDigit; + + if (text.Length < ByteOverflowLength) + { + // Length is less than ByteOverflowLength; overflow is not possible + for (int index = 1; index < text.Length; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + charsConsumed = index; + value = (byte)(parsedValue); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than ByteOverflowLength; overflow is only possible after ByteOverflowLength + // digits. There may be no overflow after ByteOverflowLength if there are leading zeroes. + for (int index = 1; index < ByteOverflowLength - 1; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + charsConsumed = index; + value = (byte)(parsedValue); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = ByteOverflowLength - 1; index < text.Length; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + charsConsumed = index; + value = (byte)(parsedValue); + return true; + } + // If parsedValue > (byte.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (byte.MaxValue / 10), any nextDigit greater than 5 implies overflow. + if (parsedValue > byte.MaxValue / 10 || (parsedValue == byte.MaxValue / 10 && nextDigit > 5)) + { + charsConsumed = 0; + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + charsConsumed = text.Length; + value = (byte)(parsedValue); + return true; + } + + #endregion + + #region UInt16 + public unsafe static bool TryParseUInt16(char* text, int length, out ushort value) + { + if (length < 1) + { + value = default; + return false; + } + + // Parse the first digit separately. If invalid here, we need to return false. + uint firstDigit = text[0] - 48u; // '0' + if (firstDigit > 9) + { + value = default; + return false; + } + uint parsedValue = firstDigit; + + if (length < UInt16OverflowLength) + { + // Length is less than UInt16OverflowLength; overflow is not possible + for (int index = 1; index < length; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + value = (ushort)(parsedValue); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than UInt16OverflowLength; overflow is only possible after UInt16OverflowLength + // digits. There may be no overflow after UInt16OverflowLength if there are leading zeroes. + for (int index = 1; index < UInt16OverflowLength - 1; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + value = (ushort)(parsedValue); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = UInt16OverflowLength - 1; index < length; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + value = (ushort)(parsedValue); + return true; + } + // If parsedValue > (ushort.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (ushort.MaxValue / 10), any nextDigit greater than 5 implies overflow. + if (parsedValue > ushort.MaxValue / 10 || (parsedValue == ushort.MaxValue / 10 && nextDigit > 5)) + { + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + value = (ushort)(parsedValue); + return true; + } + + public unsafe static bool TryParseUInt16(char* text, int length, out ushort value, out int charsConsumed) + { + if (length < 1) + { + charsConsumed = 0; + value = default; + return false; + } + + // Parse the first digit separately. If invalid here, we need to return false. + uint firstDigit = text[0] - 48u; // '0' + if (firstDigit > 9) + { + charsConsumed = 0; + value = default; + return false; + } + uint parsedValue = firstDigit; + + if (length < UInt16OverflowLength) + { + // Length is less than UInt16OverflowLength; overflow is not possible + for (int index = 1; index < length; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + charsConsumed = index; + value = (ushort)(parsedValue); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than UInt16OverflowLength; overflow is only possible after UInt16OverflowLength + // digits. There may be no overflow after UInt16OverflowLength if there are leading zeroes. + for (int index = 1; index < UInt16OverflowLength - 1; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + charsConsumed = index; + value = (ushort)(parsedValue); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = UInt16OverflowLength - 1; index < length; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + charsConsumed = index; + value = (ushort)(parsedValue); + return true; + } + // If parsedValue > (ushort.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (ushort.MaxValue / 10), any nextDigit greater than 5 implies overflow. + if (parsedValue > ushort.MaxValue / 10 || (parsedValue == ushort.MaxValue / 10 && nextDigit > 5)) + { + charsConsumed = 0; + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + charsConsumed = length; + value = (ushort)(parsedValue); + return true; + } + + public static bool TryParseUInt16(ReadOnlySpan text, out ushort value) + { + if (text.Length < 1) + { + value = default; + return false; + } + + // Parse the first digit separately. If invalid here, we need to return false. + uint firstDigit = text[0] - 48u; // '0' + if (firstDigit > 9) + { + value = default; + return false; + } + uint parsedValue = firstDigit; + + if (text.Length < UInt16OverflowLength) + { + // Length is less than UInt16OverflowLength; overflow is not possible + for (int index = 1; index < text.Length; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + value = (ushort)(parsedValue); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than UInt16OverflowLength; overflow is only possible after UInt16OverflowLength + // digits. There may be no overflow after UInt16OverflowLength if there are leading zeroes. + for (int index = 1; index < UInt16OverflowLength - 1; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + value = (ushort)(parsedValue); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = UInt16OverflowLength - 1; index < text.Length; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + value = (ushort)(parsedValue); + return true; + } + // If parsedValue > (ushort.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (ushort.MaxValue / 10), any nextDigit greater than 5 implies overflow. + if (parsedValue > ushort.MaxValue / 10 || (parsedValue == ushort.MaxValue / 10 && nextDigit > 5)) + { + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + value = (ushort)(parsedValue); + return true; + } + + public static bool TryParseUInt16(ReadOnlySpan text, out ushort value, out int charsConsumed) + { + if (text.Length < 1) + { + charsConsumed = 0; + value = default; + return false; + } + + // Parse the first digit separately. If invalid here, we need to return false. + uint firstDigit = text[0] - 48u; // '0' + if (firstDigit > 9) + { + charsConsumed = 0; + value = default; + return false; + } + uint parsedValue = firstDigit; + + if (text.Length < UInt16OverflowLength) + { + // Length is less than UInt16OverflowLength; overflow is not possible + for (int index = 1; index < text.Length; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + charsConsumed = index; + value = (ushort)(parsedValue); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than UInt16OverflowLength; overflow is only possible after UInt16OverflowLength + // digits. There may be no overflow after UInt16OverflowLength if there are leading zeroes. + for (int index = 1; index < UInt16OverflowLength - 1; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + charsConsumed = index; + value = (ushort)(parsedValue); + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = UInt16OverflowLength - 1; index < text.Length; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + charsConsumed = index; + value = (ushort)(parsedValue); + return true; + } + // If parsedValue > (ushort.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (ushort.MaxValue / 10), any nextDigit greater than 5 implies overflow. + if (parsedValue > ushort.MaxValue / 10 || (parsedValue == ushort.MaxValue / 10 && nextDigit > 5)) + { + charsConsumed = 0; + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + charsConsumed = text.Length; + value = (ushort)(parsedValue); + return true; + } + + #endregion + + #region UInt32 + public unsafe static bool TryParseUInt32(char* text, int length, out uint value) + { + if (length < 1) + { + value = default; + return false; + } + + // Parse the first digit separately. If invalid here, we need to return false. + uint firstDigit = text[0] - 48u; // '0' + if (firstDigit > 9) + { + value = default; + return false; + } + uint parsedValue = firstDigit; + + if (length < UInt32OverflowLength) + { + // Length is less than UInt32OverflowLength; overflow is not possible + for (int index = 1; index < length; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + value = parsedValue; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than UInt32OverflowLength; overflow is only possible after UInt32OverflowLength + // digits. There may be no overflow after UInt32OverflowLength if there are leading zeroes. + for (int index = 1; index < UInt32OverflowLength - 1; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + value = parsedValue; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = UInt32OverflowLength - 1; index < length; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + value = parsedValue; + return true; + } + // If parsedValue > (uint.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (uint.MaxValue / 10), any nextDigit greater than 5 implies overflow. + if (parsedValue > uint.MaxValue / 10 || (parsedValue == uint.MaxValue / 10 && nextDigit > 5)) + { + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + value = parsedValue; + return true; + } + + public unsafe static bool TryParseUInt32(char* text, int length, out uint value, out int charsConsumed) + { + if (length < 1) + { + charsConsumed = 0; + value = default; + return false; + } + + // Parse the first digit separately. If invalid here, we need to return false. + uint firstDigit = text[0] - 48u; // '0' + if (firstDigit > 9) + { + charsConsumed = 0; + value = default; + return false; + } + uint parsedValue = firstDigit; + + if (length < UInt32OverflowLength) + { + // Length is less than UInt32OverflowLength; overflow is not possible + for (int index = 1; index < length; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + charsConsumed = index; + value = parsedValue; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than UInt32OverflowLength; overflow is only possible after UInt32OverflowLength + // digits. There may be no overflow after UInt32OverflowLength if there are leading zeroes. + for (int index = 1; index < UInt32OverflowLength - 1; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + charsConsumed = index; + value = parsedValue; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = UInt32OverflowLength - 1; index < length; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + charsConsumed = index; + value = parsedValue; + return true; + } + // If parsedValue > (uint.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (uint.MaxValue / 10), any nextDigit greater than 5 implies overflow. + if (parsedValue > uint.MaxValue / 10 || (parsedValue == uint.MaxValue / 10 && nextDigit > 5)) + { + charsConsumed = 0; + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + charsConsumed = length; + value = parsedValue; + return true; + } + + public static bool TryParseUInt32(ReadOnlySpan text, out uint value) + { + if (text.Length < 1) + { + value = default; + return false; + } + + // Parse the first digit separately. If invalid here, we need to return false. + uint firstDigit = text[0] - 48u; // '0' + if (firstDigit > 9) + { + value = default; + return false; + } + uint parsedValue = firstDigit; + + if (text.Length < UInt32OverflowLength) + { + // Length is less than UInt32OverflowLength; overflow is not possible + for (int index = 1; index < text.Length; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + value = parsedValue; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than UInt32OverflowLength; overflow is only possible after UInt32OverflowLength + // digits. There may be no overflow after UInt32OverflowLength if there are leading zeroes. + for (int index = 1; index < UInt32OverflowLength - 1; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + value = parsedValue; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = UInt32OverflowLength - 1; index < text.Length; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + value = parsedValue; + return true; + } + // If parsedValue > (uint.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (uint.MaxValue / 10), any nextDigit greater than 5 implies overflow. + if (parsedValue > uint.MaxValue / 10 || (parsedValue == uint.MaxValue / 10 && nextDigit > 5)) + { + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + value = parsedValue; + return true; + } + + public static bool TryParseUInt32(ReadOnlySpan text, out uint value, out int charsConsumed) + { + if (text.Length < 1) + { + charsConsumed = 0; + value = default; + return false; + } + + // Parse the first digit separately. If invalid here, we need to return false. + uint firstDigit = text[0] - 48u; // '0' + if (firstDigit > 9) + { + charsConsumed = 0; + value = default; + return false; + } + uint parsedValue = firstDigit; + + if (text.Length < UInt32OverflowLength) + { + // Length is less than UInt32OverflowLength; overflow is not possible + for (int index = 1; index < text.Length; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + charsConsumed = index; + value = parsedValue; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than UInt32OverflowLength; overflow is only possible after UInt32OverflowLength + // digits. There may be no overflow after UInt32OverflowLength if there are leading zeroes. + for (int index = 1; index < UInt32OverflowLength - 1; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + charsConsumed = index; + value = parsedValue; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = UInt32OverflowLength - 1; index < text.Length; index++) + { + uint nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + charsConsumed = index; + value = parsedValue; + return true; + } + // If parsedValue > (uint.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (uint.MaxValue / 10), any nextDigit greater than 5 implies overflow. + if (parsedValue > uint.MaxValue / 10 || (parsedValue == uint.MaxValue / 10 && nextDigit > 5)) + { + charsConsumed = 0; + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + charsConsumed = text.Length; + value = parsedValue; + return true; + } + + #endregion + + #region UInt64 + public unsafe static bool TryParseUInt64(char* text, int length, out ulong value) + { + if (length < 1) + { + value = default; + return false; + } + + // Parse the first digit separately. If invalid here, we need to return false. + ulong firstDigit = text[0] - 48u; // '0' + if (firstDigit > 9) + { + value = default; + return false; + } + ulong parsedValue = firstDigit; + + if (length < UInt64OverflowLength) + { + // Length is less than UInt64OverflowLength; overflow is not possible + for (int index = 1; index < length; index++) + { + ulong nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + value = parsedValue; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than UInt64OverflowLength; overflow is only possible after UInt64OverflowLength + // digits. There may be no overflow after UInt64OverflowLength if there are leading zeroes. + for (int index = 1; index < UInt64OverflowLength - 1; index++) + { + ulong nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + value = parsedValue; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = UInt64OverflowLength - 1; index < length; index++) + { + ulong nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + value = parsedValue; + return true; + } + // If parsedValue > (ulong.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (ulong.MaxValue / 10), any nextDigit greater than 5 implies overflow. + if (parsedValue > ulong.MaxValue / 10 || (parsedValue == ulong.MaxValue / 10 && nextDigit > 5)) + { + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + value = parsedValue; + return true; + } + + public unsafe static bool TryParseUInt64(char* text, int length, out ulong value, out int charsConsumed) + { + if (length < 1) + { + charsConsumed = 0; + value = default; + return false; + } + + // Parse the first digit separately. If invalid here, we need to return false. + ulong firstDigit = text[0] - 48u; // '0' + if (firstDigit > 9) + { + charsConsumed = 0; + value = default; + return false; + } + ulong parsedValue = firstDigit; + + if (length < UInt64OverflowLength) + { + // Length is less than UInt64OverflowLength; overflow is not possible + for (int index = 1; index < length; index++) + { + ulong nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + charsConsumed = index; + value = parsedValue; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than UInt64OverflowLength; overflow is only possible after UInt64OverflowLength + // digits. There may be no overflow after UInt64OverflowLength if there are leading zeroes. + for (int index = 1; index < UInt64OverflowLength - 1; index++) + { + ulong nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + charsConsumed = index; + value = parsedValue; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = UInt64OverflowLength - 1; index < length; index++) + { + ulong nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + charsConsumed = index; + value = parsedValue; + return true; + } + // If parsedValue > (ulong.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (ulong.MaxValue / 10), any nextDigit greater than 5 implies overflow. + if (parsedValue > ulong.MaxValue / 10 || (parsedValue == ulong.MaxValue / 10 && nextDigit > 5)) + { + charsConsumed = 0; + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + charsConsumed = length; + value = parsedValue; + return true; + } + + public static bool TryParseUInt64(ReadOnlySpan text, out ulong value) + { + if (text.Length < 1) + { + value = default; + return false; + } + + // Parse the first digit separately. If invalid here, we need to return false. + ulong firstDigit = text[0] - 48u; // '0' + if (firstDigit > 9) + { + value = default; + return false; + } + ulong parsedValue = firstDigit; + + if (text.Length < UInt64OverflowLength) + { + // Length is less than UInt64OverflowLength; overflow is not possible + for (int index = 1; index < text.Length; index++) + { + ulong nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + value = parsedValue; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than UInt64OverflowLength; overflow is only possible after UInt64OverflowLength + // digits. There may be no overflow after UInt64OverflowLength if there are leading zeroes. + for (int index = 1; index < UInt64OverflowLength - 1; index++) + { + ulong nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + value = parsedValue; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = UInt64OverflowLength - 1; index < text.Length; index++) + { + ulong nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + value = parsedValue; + return true; + } + // If parsedValue > (ulong.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (ulong.MaxValue / 10), any nextDigit greater than 5 implies overflow. + if (parsedValue > ulong.MaxValue / 10 || (parsedValue == ulong.MaxValue / 10 && nextDigit > 5)) + { + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + value = parsedValue; + return true; + } + + public static bool TryParseUInt64(ReadOnlySpan text, out ulong value, out int charsConsumed) + { + if (text.Length < 1) + { + charsConsumed = 0; + value = default; + return false; + } + + // Parse the first digit separately. If invalid here, we need to return false. + ulong firstDigit = text[0] - 48u; // '0' + if (firstDigit > 9) + { + charsConsumed = 0; + value = default; + return false; + } + ulong parsedValue = firstDigit; + + if (text.Length < UInt64OverflowLength) + { + // Length is less than UInt64OverflowLength; overflow is not possible + for (int index = 1; index < text.Length; index++) + { + ulong nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + charsConsumed = index; + value = parsedValue; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than UInt64OverflowLength; overflow is only possible after UInt64OverflowLength + // digits. There may be no overflow after UInt64OverflowLength if there are leading zeroes. + for (int index = 1; index < UInt64OverflowLength - 1; index++) + { + ulong nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + charsConsumed = index; + value = parsedValue; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = UInt64OverflowLength - 1; index < text.Length; index++) + { + ulong nextDigit = text[index] - 48u; // '0' + if (nextDigit > 9) + { + charsConsumed = index; + value = parsedValue; + return true; + } + // If parsedValue > (ulong.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (ulong.MaxValue / 10), any nextDigit greater than 5 implies overflow. + if (parsedValue > ulong.MaxValue / 10 || (parsedValue == ulong.MaxValue / 10 && nextDigit > 5)) + { + charsConsumed = 0; + value = default; + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + charsConsumed = text.Length; + value = parsedValue; + return true; + } + + #endregion + + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Parsing/InvariantUnsignedHex.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Parsing/InvariantUnsignedHex.cs new file mode 100644 index 000000000..8150e868a --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Parsing/InvariantUnsignedHex.cs @@ -0,0 +1,2594 @@ +// 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. + + +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace System.Text +{ + public static partial class PrimitiveParser + { + public static partial class InvariantUtf8 + { + public static partial class Hex + { + #region Byte + public unsafe static bool TryParseByte(byte* text, int length, out byte value) + { + if (length < 1) + { + value = default; + return false; + } + byte nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = default; + return false; + } + uint parsedValue = nextDigit; + + if (length <= ByteOverflowLengthHex) + { + // Length is less than or equal to ByteOverflowLengthHex; overflow is not possible + for (int index = 1; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = (byte)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than ByteOverflowLengthHex; overflow is only possible after ByteOverflowLengthHex + // digits. There may be no overflow after ByteOverflowLengthHex if there are leading zeroes. + for (int index = 1; index < ByteOverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = (byte)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = ByteOverflowLengthHex; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = (byte)(parsedValue); + return true; + } + // If we try to append a digit to anything larger than byte.MaxValue / 0x10, there will be overflow + if (parsedValue > byte.MaxValue / 0x10) + { + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + value = (byte)(parsedValue); + return true; + } + + public unsafe static bool TryParseByte(byte* text, int length, out byte value, out int bytesConsumed) + { + if (length < 1) + { + bytesConsumed = 0; + value = default; + return false; + } + byte nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = 0; + value = default; + return false; + } + uint parsedValue = nextDigit; + + if (length <= ByteOverflowLengthHex) + { + // Length is less than or equal to ByteOverflowLengthHex; overflow is not possible + for (int index = 1; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = index; + value = (byte)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than ByteOverflowLengthHex; overflow is only possible after ByteOverflowLengthHex + // digits. There may be no overflow after ByteOverflowLengthHex if there are leading zeroes. + for (int index = 1; index < ByteOverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = index; + value = (byte)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = ByteOverflowLengthHex; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = index; + value = (byte)(parsedValue); + return true; + } + // If we try to append a digit to anything larger than byte.MaxValue / 0x10, there will be overflow + if (parsedValue > byte.MaxValue / 0x10) + { + bytesConsumed = 0; + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + bytesConsumed = length; + value = (byte)(parsedValue); + return true; + } + + public static bool TryParseByte(ReadOnlySpan text, out byte value) + { + if (text.Length < 1) + { + value = default; + return false; + } + byte nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = default; + return false; + } + uint parsedValue = nextDigit; + + if (text.Length <= ByteOverflowLengthHex) + { + // Length is less than or equal to ByteOverflowLengthHex; overflow is not possible + for (int index = 1; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = (byte)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than ByteOverflowLengthHex; overflow is only possible after ByteOverflowLengthHex + // digits. There may be no overflow after ByteOverflowLengthHex if there are leading zeroes. + for (int index = 1; index < ByteOverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = (byte)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = ByteOverflowLengthHex; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = (byte)(parsedValue); + return true; + } + // If we try to append a digit to anything larger than byte.MaxValue / 0x10, there will be overflow + if (parsedValue > byte.MaxValue / 0x10) + { + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + value = (byte)(parsedValue); + return true; + } + + public static bool TryParseByte(ReadOnlySpan text, out byte value, out int bytesConsumed) + { + if (text.Length < 1) + { + bytesConsumed = 0; + value = default; + return false; + } + byte nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = 0; + value = default; + return false; + } + uint parsedValue = nextDigit; + + if (text.Length <= ByteOverflowLengthHex) + { + // Length is less than or equal to ByteOverflowLengthHex; overflow is not possible + for (int index = 1; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = index; + value = (byte)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than ByteOverflowLengthHex; overflow is only possible after ByteOverflowLengthHex + // digits. There may be no overflow after ByteOverflowLengthHex if there are leading zeroes. + for (int index = 1; index < ByteOverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = index; + value = (byte)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = ByteOverflowLengthHex; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = index; + value = (byte)(parsedValue); + return true; + } + // If we try to append a digit to anything larger than byte.MaxValue / 0x10, there will be overflow + if (parsedValue > byte.MaxValue / 0x10) + { + bytesConsumed = 0; + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + bytesConsumed = text.Length; + value = (byte)(parsedValue); + return true; + } + + #endregion + + #region UInt16 + public unsafe static bool TryParseUInt16(byte* text, int length, out ushort value) + { + if (length < 1) + { + value = default; + return false; + } + byte nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = default; + return false; + } + uint parsedValue = nextDigit; + + if (length <= UInt16OverflowLengthHex) + { + // Length is less than or equal to UInt16OverflowLengthHex; overflow is not possible + for (int index = 1; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = (ushort)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than UInt16OverflowLengthHex; overflow is only possible after UInt16OverflowLengthHex + // digits. There may be no overflow after UInt16OverflowLengthHex if there are leading zeroes. + for (int index = 1; index < UInt16OverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = (ushort)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = UInt16OverflowLengthHex; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = (ushort)(parsedValue); + return true; + } + // If we try to append a digit to anything larger than ushort.MaxValue / 0x10, there will be overflow + if (parsedValue > ushort.MaxValue / 0x10) + { + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + value = (ushort)(parsedValue); + return true; + } + + public unsafe static bool TryParseUInt16(byte* text, int length, out ushort value, out int bytesConsumed) + { + if (length < 1) + { + bytesConsumed = 0; + value = default; + return false; + } + byte nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = 0; + value = default; + return false; + } + uint parsedValue = nextDigit; + + if (length <= UInt16OverflowLengthHex) + { + // Length is less than or equal to UInt16OverflowLengthHex; overflow is not possible + for (int index = 1; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = index; + value = (ushort)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than UInt16OverflowLengthHex; overflow is only possible after UInt16OverflowLengthHex + // digits. There may be no overflow after UInt16OverflowLengthHex if there are leading zeroes. + for (int index = 1; index < UInt16OverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = index; + value = (ushort)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = UInt16OverflowLengthHex; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = index; + value = (ushort)(parsedValue); + return true; + } + // If we try to append a digit to anything larger than ushort.MaxValue / 0x10, there will be overflow + if (parsedValue > ushort.MaxValue / 0x10) + { + bytesConsumed = 0; + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + bytesConsumed = length; + value = (ushort)(parsedValue); + return true; + } + + public static bool TryParseUInt16(ReadOnlySpan text, out ushort value) + { + if (text.Length < 1) + { + value = default; + return false; + } + byte nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = default; + return false; + } + uint parsedValue = nextDigit; + + if (text.Length <= UInt16OverflowLengthHex) + { + // Length is less than or equal to UInt16OverflowLengthHex; overflow is not possible + for (int index = 1; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = (ushort)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than UInt16OverflowLengthHex; overflow is only possible after UInt16OverflowLengthHex + // digits. There may be no overflow after UInt16OverflowLengthHex if there are leading zeroes. + for (int index = 1; index < UInt16OverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = (ushort)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = UInt16OverflowLengthHex; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = (ushort)(parsedValue); + return true; + } + // If we try to append a digit to anything larger than ushort.MaxValue / 0x10, there will be overflow + if (parsedValue > ushort.MaxValue / 0x10) + { + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + value = (ushort)(parsedValue); + return true; + } + + public static bool TryParseUInt16(ReadOnlySpan text, out ushort value, out int bytesConsumed) + { + if (text.Length < 1) + { + bytesConsumed = 0; + value = default; + return false; + } + byte nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = 0; + value = default; + return false; + } + uint parsedValue = nextDigit; + + if (text.Length <= UInt16OverflowLengthHex) + { + // Length is less than or equal to UInt16OverflowLengthHex; overflow is not possible + for (int index = 1; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = index; + value = (ushort)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than UInt16OverflowLengthHex; overflow is only possible after UInt16OverflowLengthHex + // digits. There may be no overflow after UInt16OverflowLengthHex if there are leading zeroes. + for (int index = 1; index < UInt16OverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = index; + value = (ushort)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = UInt16OverflowLengthHex; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = index; + value = (ushort)(parsedValue); + return true; + } + // If we try to append a digit to anything larger than ushort.MaxValue / 0x10, there will be overflow + if (parsedValue > ushort.MaxValue / 0x10) + { + bytesConsumed = 0; + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + bytesConsumed = text.Length; + value = (ushort)(parsedValue); + return true; + } + + #endregion + + #region UInt32 + public unsafe static bool TryParseUInt32(byte* text, int length, out uint value) + { + if (length < 1) + { + value = default; + return false; + } + byte nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = default; + return false; + } + uint parsedValue = nextDigit; + + if (length <= UInt32OverflowLengthHex) + { + // Length is less than or equal to UInt32OverflowLengthHex; overflow is not possible + for (int index = 1; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = parsedValue; + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than UInt32OverflowLengthHex; overflow is only possible after UInt32OverflowLengthHex + // digits. There may be no overflow after UInt32OverflowLengthHex if there are leading zeroes. + for (int index = 1; index < UInt32OverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = parsedValue; + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = UInt32OverflowLengthHex; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = parsedValue; + return true; + } + // If we try to append a digit to anything larger than uint.MaxValue / 0x10, there will be overflow + if (parsedValue > uint.MaxValue / 0x10) + { + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + value = parsedValue; + return true; + } + + public unsafe static bool TryParseUInt32(byte* text, int length, out uint value, out int bytesConsumed) + { + if (length < 1) + { + bytesConsumed = 0; + value = default; + return false; + } + byte nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = 0; + value = default; + return false; + } + uint parsedValue = nextDigit; + + if (length <= UInt32OverflowLengthHex) + { + // Length is less than or equal to UInt32OverflowLengthHex; overflow is not possible + for (int index = 1; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = index; + value = parsedValue; + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than UInt32OverflowLengthHex; overflow is only possible after UInt32OverflowLengthHex + // digits. There may be no overflow after UInt32OverflowLengthHex if there are leading zeroes. + for (int index = 1; index < UInt32OverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = index; + value = parsedValue; + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = UInt32OverflowLengthHex; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = index; + value = parsedValue; + return true; + } + // If we try to append a digit to anything larger than uint.MaxValue / 0x10, there will be overflow + if (parsedValue > uint.MaxValue / 0x10) + { + bytesConsumed = 0; + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + bytesConsumed = length; + value = parsedValue; + return true; + } + + public static bool TryParseUInt32(ReadOnlySpan text, out uint value) + { + if (text.Length < 1) + { + value = default; + return false; + } + byte nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = default; + return false; + } + uint parsedValue = nextDigit; + + if (text.Length <= UInt32OverflowLengthHex) + { + // Length is less than or equal to UInt32OverflowLengthHex; overflow is not possible + for (int index = 1; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = parsedValue; + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than UInt32OverflowLengthHex; overflow is only possible after UInt32OverflowLengthHex + // digits. There may be no overflow after UInt32OverflowLengthHex if there are leading zeroes. + for (int index = 1; index < UInt32OverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = parsedValue; + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = UInt32OverflowLengthHex; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = parsedValue; + return true; + } + // If we try to append a digit to anything larger than uint.MaxValue / 0x10, there will be overflow + if (parsedValue > uint.MaxValue / 0x10) + { + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + value = parsedValue; + return true; + } + + public static bool TryParseUInt32(ReadOnlySpan text, out uint value, out int bytesConsumed) + { + if (text.Length < 1) + { + bytesConsumed = 0; + value = default; + return false; + } + byte nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = 0; + value = default; + return false; + } + uint parsedValue = nextDigit; + + if (text.Length <= UInt32OverflowLengthHex) + { + // Length is less than or equal to UInt32OverflowLengthHex; overflow is not possible + for (int index = 1; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = index; + value = parsedValue; + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than UInt32OverflowLengthHex; overflow is only possible after UInt32OverflowLengthHex + // digits. There may be no overflow after UInt32OverflowLengthHex if there are leading zeroes. + for (int index = 1; index < UInt32OverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = index; + value = parsedValue; + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = UInt32OverflowLengthHex; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = index; + value = parsedValue; + return true; + } + // If we try to append a digit to anything larger than uint.MaxValue / 0x10, there will be overflow + if (parsedValue > uint.MaxValue / 0x10) + { + bytesConsumed = 0; + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + bytesConsumed = text.Length; + value = parsedValue; + return true; + } + + #endregion + + #region UInt64 + public unsafe static bool TryParseUInt64(byte* text, int length, out ulong value) + { + if (length < 1) + { + value = default; + return false; + } + byte nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = default; + return false; + } + ulong parsedValue = nextDigit; + + if (length <= UInt64OverflowLengthHex) + { + // Length is less than or equal to UInt64OverflowLengthHex; overflow is not possible + for (int index = 1; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = parsedValue; + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than UInt64OverflowLengthHex; overflow is only possible after UInt64OverflowLengthHex + // digits. There may be no overflow after UInt64OverflowLengthHex if there are leading zeroes. + for (int index = 1; index < UInt64OverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = parsedValue; + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = UInt64OverflowLengthHex; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = parsedValue; + return true; + } + // If we try to append a digit to anything larger than ulong.MaxValue / 0x10, there will be overflow + if (parsedValue > ulong.MaxValue / 0x10) + { + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + value = parsedValue; + return true; + } + + public unsafe static bool TryParseUInt64(byte* text, int length, out ulong value, out int bytesConsumed) + { + if (length < 1) + { + bytesConsumed = 0; + value = default; + return false; + } + byte nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = 0; + value = default; + return false; + } + ulong parsedValue = nextDigit; + + if (length <= UInt64OverflowLengthHex) + { + // Length is less than or equal to UInt64OverflowLengthHex; overflow is not possible + for (int index = 1; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = index; + value = parsedValue; + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than UInt64OverflowLengthHex; overflow is only possible after UInt64OverflowLengthHex + // digits. There may be no overflow after UInt64OverflowLengthHex if there are leading zeroes. + for (int index = 1; index < UInt64OverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = index; + value = parsedValue; + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = UInt64OverflowLengthHex; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = index; + value = parsedValue; + return true; + } + // If we try to append a digit to anything larger than ulong.MaxValue / 0x10, there will be overflow + if (parsedValue > ulong.MaxValue / 0x10) + { + bytesConsumed = 0; + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + bytesConsumed = length; + value = parsedValue; + return true; + } + + public static bool TryParseUInt64(ReadOnlySpan text, out ulong value) + { + if (text.Length < 1) + { + value = default; + return false; + } + byte nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = default; + return false; + } + ulong parsedValue = nextDigit; + + if (text.Length <= UInt64OverflowLengthHex) + { + // Length is less than or equal to UInt64OverflowLengthHex; overflow is not possible + for (int index = 1; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = parsedValue; + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than UInt64OverflowLengthHex; overflow is only possible after UInt64OverflowLengthHex + // digits. There may be no overflow after UInt64OverflowLengthHex if there are leading zeroes. + for (int index = 1; index < UInt64OverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = parsedValue; + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = UInt64OverflowLengthHex; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + value = parsedValue; + return true; + } + // If we try to append a digit to anything larger than ulong.MaxValue / 0x10, there will be overflow + if (parsedValue > ulong.MaxValue / 0x10) + { + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + value = parsedValue; + return true; + } + + public static bool TryParseUInt64(ReadOnlySpan text, out ulong value, out int bytesConsumed) + { + if (text.Length < 1) + { + bytesConsumed = 0; + value = default; + return false; + } + byte nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = 0; + value = default; + return false; + } + ulong parsedValue = nextDigit; + + if (text.Length <= UInt64OverflowLengthHex) + { + // Length is less than or equal to UInt64OverflowLengthHex; overflow is not possible + for (int index = 1; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = index; + value = parsedValue; + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than UInt64OverflowLengthHex; overflow is only possible after UInt64OverflowLengthHex + // digits. There may be no overflow after UInt64OverflowLengthHex if there are leading zeroes. + for (int index = 1; index < UInt64OverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = index; + value = parsedValue; + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = UInt64OverflowLengthHex; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[nextCharacter]; + if (nextDigit == 0xFF) + { + bytesConsumed = index; + value = parsedValue; + return true; + } + // If we try to append a digit to anything larger than ulong.MaxValue / 0x10, there will be overflow + if (parsedValue > ulong.MaxValue / 0x10) + { + bytesConsumed = 0; + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + bytesConsumed = text.Length; + value = parsedValue; + return true; + } + + #endregion + + } + } + public static partial class InvariantUtf16 + { + public static partial class Hex + { + #region Byte + public unsafe static bool TryParseByte(char* text, int length, out byte value) + { + if (length < 1) + { + value = default; + return false; + } + char nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = default; + return false; + } + uint parsedValue = nextDigit; + + if (length <= ByteOverflowLengthHex) + { + // Length is less than or equal to ByteOverflowLengthHex; overflow is not possible + for (int index = 1; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = (byte)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than ByteOverflowLengthHex; overflow is only possible after ByteOverflowLengthHex + // digits. There may be no overflow after ByteOverflowLengthHex if there are leading zeroes. + for (int index = 1; index < ByteOverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = (byte)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = ByteOverflowLengthHex; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = (byte)(parsedValue); + return true; + } + // If we try to append a digit to anything larger than byte.MaxValue / 0x10, there will be overflow + if (parsedValue > byte.MaxValue / 0x10) + { + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + value = (byte)(parsedValue); + return true; + } + + public unsafe static bool TryParseByte(char* text, int length, out byte value, out int charsConsumed) + { + if (length < 1) + { + charsConsumed = 0; + value = default; + return false; + } + char nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = 0; + value = default; + return false; + } + uint parsedValue = nextDigit; + + if (length <= ByteOverflowLengthHex) + { + // Length is less than or equal to ByteOverflowLengthHex; overflow is not possible + for (int index = 1; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = index; + value = (byte)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than ByteOverflowLengthHex; overflow is only possible after ByteOverflowLengthHex + // digits. There may be no overflow after ByteOverflowLengthHex if there are leading zeroes. + for (int index = 1; index < ByteOverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = index; + value = (byte)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = ByteOverflowLengthHex; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = index; + value = (byte)(parsedValue); + return true; + } + // If we try to append a digit to anything larger than byte.MaxValue / 0x10, there will be overflow + if (parsedValue > byte.MaxValue / 0x10) + { + charsConsumed = 0; + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + charsConsumed = length; + value = (byte)(parsedValue); + return true; + } + + public static bool TryParseByte(ReadOnlySpan text, out byte value) + { + if (text.Length < 1) + { + value = default; + return false; + } + char nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = default; + return false; + } + uint parsedValue = nextDigit; + + if (text.Length <= ByteOverflowLengthHex) + { + // Length is less than or equal to ByteOverflowLengthHex; overflow is not possible + for (int index = 1; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = (byte)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than ByteOverflowLengthHex; overflow is only possible after ByteOverflowLengthHex + // digits. There may be no overflow after ByteOverflowLengthHex if there are leading zeroes. + for (int index = 1; index < ByteOverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = (byte)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = ByteOverflowLengthHex; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = (byte)(parsedValue); + return true; + } + // If we try to append a digit to anything larger than byte.MaxValue / 0x10, there will be overflow + if (parsedValue > byte.MaxValue / 0x10) + { + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + value = (byte)(parsedValue); + return true; + } + + public static bool TryParseByte(ReadOnlySpan text, out byte value, out int charsConsumed) + { + if (text.Length < 1) + { + charsConsumed = 0; + value = default; + return false; + } + char nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = 0; + value = default; + return false; + } + uint parsedValue = nextDigit; + + if (text.Length <= ByteOverflowLengthHex) + { + // Length is less than or equal to ByteOverflowLengthHex; overflow is not possible + for (int index = 1; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = index; + value = (byte)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than ByteOverflowLengthHex; overflow is only possible after ByteOverflowLengthHex + // digits. There may be no overflow after ByteOverflowLengthHex if there are leading zeroes. + for (int index = 1; index < ByteOverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = index; + value = (byte)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = ByteOverflowLengthHex; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = index; + value = (byte)(parsedValue); + return true; + } + // If we try to append a digit to anything larger than byte.MaxValue / 0x10, there will be overflow + if (parsedValue > byte.MaxValue / 0x10) + { + charsConsumed = 0; + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + charsConsumed = text.Length; + value = (byte)(parsedValue); + return true; + } + + #endregion + + #region UInt16 + public unsafe static bool TryParseUInt16(char* text, int length, out ushort value) + { + if (length < 1) + { + value = default; + return false; + } + char nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = default; + return false; + } + uint parsedValue = nextDigit; + + if (length <= UInt16OverflowLengthHex) + { + // Length is less than or equal to UInt16OverflowLengthHex; overflow is not possible + for (int index = 1; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = (ushort)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than UInt16OverflowLengthHex; overflow is only possible after UInt16OverflowLengthHex + // digits. There may be no overflow after UInt16OverflowLengthHex if there are leading zeroes. + for (int index = 1; index < UInt16OverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = (ushort)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = UInt16OverflowLengthHex; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = (ushort)(parsedValue); + return true; + } + // If we try to append a digit to anything larger than ushort.MaxValue / 0x10, there will be overflow + if (parsedValue > ushort.MaxValue / 0x10) + { + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + value = (ushort)(parsedValue); + return true; + } + + public unsafe static bool TryParseUInt16(char* text, int length, out ushort value, out int charsConsumed) + { + if (length < 1) + { + charsConsumed = 0; + value = default; + return false; + } + char nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = 0; + value = default; + return false; + } + uint parsedValue = nextDigit; + + if (length <= UInt16OverflowLengthHex) + { + // Length is less than or equal to UInt16OverflowLengthHex; overflow is not possible + for (int index = 1; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = index; + value = (ushort)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than UInt16OverflowLengthHex; overflow is only possible after UInt16OverflowLengthHex + // digits. There may be no overflow after UInt16OverflowLengthHex if there are leading zeroes. + for (int index = 1; index < UInt16OverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = index; + value = (ushort)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = UInt16OverflowLengthHex; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = index; + value = (ushort)(parsedValue); + return true; + } + // If we try to append a digit to anything larger than ushort.MaxValue / 0x10, there will be overflow + if (parsedValue > ushort.MaxValue / 0x10) + { + charsConsumed = 0; + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + charsConsumed = length; + value = (ushort)(parsedValue); + return true; + } + + public static bool TryParseUInt16(ReadOnlySpan text, out ushort value) + { + if (text.Length < 1) + { + value = default; + return false; + } + char nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = default; + return false; + } + uint parsedValue = nextDigit; + + if (text.Length <= UInt16OverflowLengthHex) + { + // Length is less than or equal to UInt16OverflowLengthHex; overflow is not possible + for (int index = 1; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = (ushort)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than UInt16OverflowLengthHex; overflow is only possible after UInt16OverflowLengthHex + // digits. There may be no overflow after UInt16OverflowLengthHex if there are leading zeroes. + for (int index = 1; index < UInt16OverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = (ushort)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = UInt16OverflowLengthHex; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = (ushort)(parsedValue); + return true; + } + // If we try to append a digit to anything larger than ushort.MaxValue / 0x10, there will be overflow + if (parsedValue > ushort.MaxValue / 0x10) + { + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + value = (ushort)(parsedValue); + return true; + } + + public static bool TryParseUInt16(ReadOnlySpan text, out ushort value, out int charsConsumed) + { + if (text.Length < 1) + { + charsConsumed = 0; + value = default; + return false; + } + char nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = 0; + value = default; + return false; + } + uint parsedValue = nextDigit; + + if (text.Length <= UInt16OverflowLengthHex) + { + // Length is less than or equal to UInt16OverflowLengthHex; overflow is not possible + for (int index = 1; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = index; + value = (ushort)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than UInt16OverflowLengthHex; overflow is only possible after UInt16OverflowLengthHex + // digits. There may be no overflow after UInt16OverflowLengthHex if there are leading zeroes. + for (int index = 1; index < UInt16OverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = index; + value = (ushort)(parsedValue); + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = UInt16OverflowLengthHex; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = index; + value = (ushort)(parsedValue); + return true; + } + // If we try to append a digit to anything larger than ushort.MaxValue / 0x10, there will be overflow + if (parsedValue > ushort.MaxValue / 0x10) + { + charsConsumed = 0; + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + charsConsumed = text.Length; + value = (ushort)(parsedValue); + return true; + } + + #endregion + + #region UInt32 + public unsafe static bool TryParseUInt32(char* text, int length, out uint value) + { + if (length < 1) + { + value = default; + return false; + } + char nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = default; + return false; + } + uint parsedValue = nextDigit; + + if (length <= UInt32OverflowLengthHex) + { + // Length is less than or equal to UInt32OverflowLengthHex; overflow is not possible + for (int index = 1; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = parsedValue; + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than UInt32OverflowLengthHex; overflow is only possible after UInt32OverflowLengthHex + // digits. There may be no overflow after UInt32OverflowLengthHex if there are leading zeroes. + for (int index = 1; index < UInt32OverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = parsedValue; + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = UInt32OverflowLengthHex; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = parsedValue; + return true; + } + // If we try to append a digit to anything larger than uint.MaxValue / 0x10, there will be overflow + if (parsedValue > uint.MaxValue / 0x10) + { + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + value = parsedValue; + return true; + } + + public unsafe static bool TryParseUInt32(char* text, int length, out uint value, out int charsConsumed) + { + if (length < 1) + { + charsConsumed = 0; + value = default; + return false; + } + char nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = 0; + value = default; + return false; + } + uint parsedValue = nextDigit; + + if (length <= UInt32OverflowLengthHex) + { + // Length is less than or equal to UInt32OverflowLengthHex; overflow is not possible + for (int index = 1; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = index; + value = parsedValue; + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than UInt32OverflowLengthHex; overflow is only possible after UInt32OverflowLengthHex + // digits. There may be no overflow after UInt32OverflowLengthHex if there are leading zeroes. + for (int index = 1; index < UInt32OverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = index; + value = parsedValue; + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = UInt32OverflowLengthHex; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = index; + value = parsedValue; + return true; + } + // If we try to append a digit to anything larger than uint.MaxValue / 0x10, there will be overflow + if (parsedValue > uint.MaxValue / 0x10) + { + charsConsumed = 0; + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + charsConsumed = length; + value = parsedValue; + return true; + } + + public static bool TryParseUInt32(ReadOnlySpan text, out uint value) + { + if (text.Length < 1) + { + value = default; + return false; + } + char nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = default; + return false; + } + uint parsedValue = nextDigit; + + if (text.Length <= UInt32OverflowLengthHex) + { + // Length is less than or equal to UInt32OverflowLengthHex; overflow is not possible + for (int index = 1; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = parsedValue; + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than UInt32OverflowLengthHex; overflow is only possible after UInt32OverflowLengthHex + // digits. There may be no overflow after UInt32OverflowLengthHex if there are leading zeroes. + for (int index = 1; index < UInt32OverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = parsedValue; + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = UInt32OverflowLengthHex; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = parsedValue; + return true; + } + // If we try to append a digit to anything larger than uint.MaxValue / 0x10, there will be overflow + if (parsedValue > uint.MaxValue / 0x10) + { + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + value = parsedValue; + return true; + } + + public static bool TryParseUInt32(ReadOnlySpan text, out uint value, out int charsConsumed) + { + if (text.Length < 1) + { + charsConsumed = 0; + value = default; + return false; + } + char nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = 0; + value = default; + return false; + } + uint parsedValue = nextDigit; + + if (text.Length <= UInt32OverflowLengthHex) + { + // Length is less than or equal to UInt32OverflowLengthHex; overflow is not possible + for (int index = 1; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = index; + value = parsedValue; + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than UInt32OverflowLengthHex; overflow is only possible after UInt32OverflowLengthHex + // digits. There may be no overflow after UInt32OverflowLengthHex if there are leading zeroes. + for (int index = 1; index < UInt32OverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = index; + value = parsedValue; + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = UInt32OverflowLengthHex; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = index; + value = parsedValue; + return true; + } + // If we try to append a digit to anything larger than uint.MaxValue / 0x10, there will be overflow + if (parsedValue > uint.MaxValue / 0x10) + { + charsConsumed = 0; + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + charsConsumed = text.Length; + value = parsedValue; + return true; + } + + #endregion + + #region UInt64 + public unsafe static bool TryParseUInt64(char* text, int length, out ulong value) + { + if (length < 1) + { + value = default; + return false; + } + char nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = default; + return false; + } + ulong parsedValue = nextDigit; + + if (length <= UInt64OverflowLengthHex) + { + // Length is less than or equal to UInt64OverflowLengthHex; overflow is not possible + for (int index = 1; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = parsedValue; + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than UInt64OverflowLengthHex; overflow is only possible after UInt64OverflowLengthHex + // digits. There may be no overflow after UInt64OverflowLengthHex if there are leading zeroes. + for (int index = 1; index < UInt64OverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = parsedValue; + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = UInt64OverflowLengthHex; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = parsedValue; + return true; + } + // If we try to append a digit to anything larger than ulong.MaxValue / 0x10, there will be overflow + if (parsedValue > ulong.MaxValue / 0x10) + { + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + value = parsedValue; + return true; + } + + public unsafe static bool TryParseUInt64(char* text, int length, out ulong value, out int charsConsumed) + { + if (length < 1) + { + charsConsumed = 0; + value = default; + return false; + } + char nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = 0; + value = default; + return false; + } + ulong parsedValue = nextDigit; + + if (length <= UInt64OverflowLengthHex) + { + // Length is less than or equal to UInt64OverflowLengthHex; overflow is not possible + for (int index = 1; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = index; + value = parsedValue; + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than UInt64OverflowLengthHex; overflow is only possible after UInt64OverflowLengthHex + // digits. There may be no overflow after UInt64OverflowLengthHex if there are leading zeroes. + for (int index = 1; index < UInt64OverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = index; + value = parsedValue; + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = UInt64OverflowLengthHex; index < length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = index; + value = parsedValue; + return true; + } + // If we try to append a digit to anything larger than ulong.MaxValue / 0x10, there will be overflow + if (parsedValue > ulong.MaxValue / 0x10) + { + charsConsumed = 0; + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + charsConsumed = length; + value = parsedValue; + return true; + } + + public static bool TryParseUInt64(ReadOnlySpan text, out ulong value) + { + if (text.Length < 1) + { + value = default; + return false; + } + char nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = default; + return false; + } + ulong parsedValue = nextDigit; + + if (text.Length <= UInt64OverflowLengthHex) + { + // Length is less than or equal to UInt64OverflowLengthHex; overflow is not possible + for (int index = 1; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = parsedValue; + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than UInt64OverflowLengthHex; overflow is only possible after UInt64OverflowLengthHex + // digits. There may be no overflow after UInt64OverflowLengthHex if there are leading zeroes. + for (int index = 1; index < UInt64OverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = parsedValue; + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = UInt64OverflowLengthHex; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + value = parsedValue; + return true; + } + // If we try to append a digit to anything larger than ulong.MaxValue / 0x10, there will be overflow + if (parsedValue > ulong.MaxValue / 0x10) + { + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + value = parsedValue; + return true; + } + + public static bool TryParseUInt64(ReadOnlySpan text, out ulong value, out int charsConsumed) + { + if (text.Length < 1) + { + charsConsumed = 0; + value = default; + return false; + } + char nextCharacter; + byte nextDigit; + + // Cache s_hexLookup in order to avoid static constructor checks + byte[] hexLookup = s_HexLookup; + + // Parse the first digit separately. If invalid here, we need to return false. + nextCharacter = text[0]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = 0; + value = default; + return false; + } + ulong parsedValue = nextDigit; + + if (text.Length <= UInt64OverflowLengthHex) + { + // Length is less than or equal to UInt64OverflowLengthHex; overflow is not possible + for (int index = 1; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = index; + value = parsedValue; + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + else + { + // Length is greater than UInt64OverflowLengthHex; overflow is only possible after UInt64OverflowLengthHex + // digits. There may be no overflow after UInt64OverflowLengthHex if there are leading zeroes. + for (int index = 1; index < UInt64OverflowLengthHex; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = index; + value = parsedValue; + return true; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + for (int index = UInt64OverflowLengthHex; index < text.Length; index++) + { + nextCharacter = text[index]; + nextDigit = hexLookup[(byte)nextCharacter]; + if (nextDigit == 0xFF || (nextCharacter >> 8) != 0) + { + charsConsumed = index; + value = parsedValue; + return true; + } + // If we try to append a digit to anything larger than ulong.MaxValue / 0x10, there will be overflow + if (parsedValue > ulong.MaxValue / 0x10) + { + charsConsumed = 0; + value = default; + return false; + } + parsedValue = (parsedValue << 4) + nextDigit; + } + } + + charsConsumed = text.Length; + value = parsedValue; + return true; + } + + #endregion + + } + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Parsing/InvariantUtf16_decimal.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Parsing/InvariantUtf16_decimal.cs new file mode 100644 index 000000000..5e54c7ae6 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Parsing/InvariantUtf16_decimal.cs @@ -0,0 +1,111 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + + +namespace System.Text +{ + public static partial class PrimitiveParser + { + public static partial class InvariantUtf16 + { + public unsafe static bool TryParseDecimal(char* text, int length, out decimal value) + { + int consumed; + var span = new ReadOnlySpan(text, length); + return TryParseDecimal(span, out value, out consumed); + } + public unsafe static bool TryParseDecimal(char* text, int length, out decimal value, out int charactersConsumed) + { + var span = new ReadOnlySpan(text, length); + return TryParseDecimal(span, out value, out charactersConsumed); + } + public static bool TryParseDecimal(ReadOnlySpan text, out decimal value) + { + int consumed; + return TryParseDecimal(text, out value, out consumed); + } + public static bool TryParseDecimal(ReadOnlySpan text, out decimal value, out int charactersConsumed) + { + // Precondition replacement + if (text.Length < 1) + { + value = 0; + charactersConsumed = 0; + return false; + } + + value = 0.0M; + charactersConsumed = 0; + string decimalString = ""; + bool decimalPlace = false, signed = false; + + int indexOfFirstDigit = 0; + if (text[0] == '-' || text[0] == '+') + { + signed = true; + decimalString += text[0]; + indexOfFirstDigit = 1; + charactersConsumed++; + } + + for (int charIndex = indexOfFirstDigit; charIndex < text.Length; charIndex++) + { + char nextChar = text[charIndex]; + char nextCharVal = (char)(nextChar - '0'); + + if (nextCharVal > 9) + { + if (!decimalPlace && nextChar == '.') + { + charactersConsumed++; + decimalPlace = true; + decimalString += nextChar; + } + else if ((decimalPlace && signed && charactersConsumed == 2) || ((signed || decimalPlace) && charactersConsumed == 1)) + { + value = 0; + charactersConsumed = 0; + return false; + } + else + { + if (decimal.TryParse(decimalString, out value)) + { + return true; + } + else + { + charactersConsumed = 0; + return false; + } + } + } + else + { + charactersConsumed++; + decimalString += nextChar; + } + } + + if ((decimalPlace && signed && charactersConsumed == 2) || ((signed || decimalPlace) && charactersConsumed == 1)) + { + value = 0; + charactersConsumed = 0; + return false; + } + else + { + if (decimal.TryParse(decimalString, out value)) + { + return true; + } + else + { + charactersConsumed = 0; + return false; + } + } + } + } + } +} \ No newline at end of file diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Parsing/InvariantUtf8_decimal.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Parsing/InvariantUtf8_decimal.cs new file mode 100644 index 000000000..374f6e0dc --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Parsing/InvariantUtf8_decimal.cs @@ -0,0 +1,111 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + + +namespace System.Text +{ + public static partial class PrimitiveParser + { + public static partial class InvariantUtf8 + { + public unsafe static bool TryParseDecimal(byte* text, int length, out decimal value) + { + int consumed; + var span = new ReadOnlySpan(text, length); + return TryParseDecimal(span, out value, out consumed); + } + public unsafe static bool TryParseDecimal(byte* text, int length, out decimal value, out int bytesConsumed) + { + var span = new ReadOnlySpan(text, length); + return TryParseDecimal(span, out value, out bytesConsumed); + } + public static bool TryParseDecimal(ReadOnlySpan text, out decimal value) + { + int consumed; + return TryParseDecimal(text, out value, out consumed); + } + public static bool TryParseDecimal(ReadOnlySpan text, out decimal value, out int bytesConsumed) + { + // Precondition replacement + if (text.Length < 1) + { + value = 0; + bytesConsumed = 0; + return false; + } + + value = 0.0M; + bytesConsumed = 0; + string decimalString = ""; + bool decimalPlace = false, signed = false; + + int indexOfFirstDigit = 0; + if (text[0] == '-' || text[0] == '+') + { + signed = true; + decimalString += (char)text[0]; + indexOfFirstDigit = 1; + bytesConsumed++; + } + + for (int byteIndex = indexOfFirstDigit; byteIndex < text.Length; byteIndex++) + { + byte nextByte = text[byteIndex]; + byte nextByteVal = (byte)(nextByte - '0'); + + if (nextByteVal > 9) + { + if (!decimalPlace && nextByte == '.') + { + bytesConsumed++; + decimalPlace = true; + decimalString += (char)nextByte; + } + else if ((decimalPlace && signed && bytesConsumed == 2) || ((signed || decimalPlace) && bytesConsumed == 1)) + { + value = 0; + bytesConsumed = 0; + return false; + } + else + { + if (decimal.TryParse(decimalString, out value)) + { + return true; + } + else + { + bytesConsumed = 0; + return false; + } + } + } + else + { + bytesConsumed++; + decimalString += (char)nextByte; + } + } + + if ((decimalPlace && signed && bytesConsumed == 2) || ((signed || decimalPlace) && bytesConsumed == 1)) + { + value = 0; + bytesConsumed = 0; + return false; + } + else + { + if (decimal.TryParse(decimalString, out value)) + { + return true; + } + else + { + bytesConsumed = 0; + return false; + } + } + } + } + } +} \ No newline at end of file diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Parsing/PrimitiveParser.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Parsing/PrimitiveParser.cs new file mode 100644 index 000000000..d49fd3328 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Parsing/PrimitiveParser.cs @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; + +namespace System.Text +{ + public static partial class PrimitiveParser + { + const int ByteOverflowLength = 3; + const int ByteOverflowLengthHex = 2; + const int UInt16OverflowLength = 5; + const int UInt16OverflowLengthHex = 4; + const int UInt32OverflowLength = 10; + const int UInt32OverflowLengthHex = 8; + const int UInt64OverflowLength = 20; + const int UInt64OverflowLengthHex = 16; + + const int SByteOverflowLength = 3; + const int SByteOverflowLengthHex = 2; + const int Int16OverflowLength = 5; + const int Int16OverflowLengthHex = 4; + const int Int32OverflowLength = 10; + const int Int32OverflowLengthHex = 8; + const int Int64OverflowLength = 19; + const int Int64OverflowLengthHex = 16; + + static readonly byte[] s_HexLookup = + { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 15 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 31 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 47 + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 63 + 0xFF, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 79 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 95 + 0xFF, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 111 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 127 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 143 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 159 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 175 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 191 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 207 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 223 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 239 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF // 255 + }; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsHexFormat(ParsedFormat format) + { + return format.Symbol == 'X' || format.Symbol == 'x'; + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Parsing/PrimitiveParser_Boolean.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Parsing/PrimitiveParser_Boolean.cs new file mode 100644 index 000000000..6ce8d4591 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Parsing/PrimitiveParser_Boolean.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + + +namespace System.Text +{ + public static partial class PrimitiveParser + { + public static bool TryParseBoolean(ReadOnlySpan text, out bool value, out int bytesConsumed, SymbolTable symbolTable = null) + { + symbolTable = symbolTable ?? SymbolTable.InvariantUtf8; + + bytesConsumed = 0; + value = default; + + if (symbolTable == SymbolTable.InvariantUtf8) + { + return InvariantUtf8.TryParseBoolean(text, out value, out bytesConsumed); + } + if (symbolTable == SymbolTable.InvariantUtf16) + { + ReadOnlySpan textChars = text.NonPortableCast(); + int charactersConsumed; + bool result = InvariantUtf16.TryParseBoolean(textChars, out value, out charactersConsumed); + bytesConsumed = charactersConsumed * sizeof(char); + return result; + } + + return false; + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Parsing/PrimitiveParser_Decimal.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Parsing/PrimitiveParser_Decimal.cs new file mode 100644 index 000000000..c9acb38f7 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Parsing/PrimitiveParser_Decimal.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + + +namespace System.Text +{ + public static partial class PrimitiveParser + { + public static bool TryParseDecimal(ReadOnlySpan text, out decimal value, out int bytesConsumed, SymbolTable symbolTable = null) + { + symbolTable = symbolTable ?? SymbolTable.InvariantUtf8; + + bytesConsumed = 0; + value = default; + + if (symbolTable == SymbolTable.InvariantUtf8) + { + return InvariantUtf8.TryParseDecimal(text, out value, out bytesConsumed); + } + else if (symbolTable == SymbolTable.InvariantUtf16) + { + ReadOnlySpan textChars = text.NonPortableCast(); + int charactersConsumed; + bool result = InvariantUtf16.TryParseDecimal(textChars, out value, out charactersConsumed); + bytesConsumed = charactersConsumed * sizeof(char); + return result; + } + + return false; + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Parsing/Signed.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Parsing/Signed.cs new file mode 100644 index 000000000..395a1dad2 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Parsing/Signed.cs @@ -0,0 +1,528 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; + +namespace System.Text +{ + public static partial class PrimitiveParser + { + #region Helpers + + private const sbyte maxValueSbyteDiv10 = sbyte.MaxValue / 10; + private const short maxValueShortDiv10 = short.MaxValue / 10; + private const int maxValueIntDiv10 = int.MaxValue / 10; + private const long maxValueLongDiv10 = long.MaxValue / 10; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsDigit(int i) + { + return (uint)(i - '0') <= ('9' - '0'); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsValid(SymbolTable.Symbol symbol) + { + return symbol <= SymbolTable.Symbol.D9; + } + + // If parsedValue > (sbyte.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (sbyte.MaxValue / 10), any nextDigit greater than 7 or 8 (depending on sign) implies overflow. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool WillOverFlow(sbyte value, int nextDigit, int sign) + { + bool nextDigitTooLarge = nextDigit > 8 || (sign > 0 && nextDigit > 7); + return (value > maxValueSbyteDiv10 || (value == maxValueSbyteDiv10 && nextDigitTooLarge)); + } + + // If parsedValue > (short.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (short.MaxValue / 10), any nextDigit greater than 7 or 8 (depending on sign) implies overflow. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool WillOverFlow(short value, int nextDigit, int sign) + { + bool nextDigitTooLarge = nextDigit > 8 || (sign > 0 && nextDigit > 7); + return (value > maxValueShortDiv10 || (value == maxValueShortDiv10 && nextDigitTooLarge)); + } + + // If parsedValue > (int.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (int.MaxValue / 10), any nextDigit greater than 7 or 8 (depending on sign) implies overflow. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool WillOverFlow(int value, int nextDigit, int sign) + { + bool nextDigitTooLarge = nextDigit > 8 || (sign > 0 && nextDigit > 7); + return (value > maxValueIntDiv10 || (value == maxValueIntDiv10 && nextDigitTooLarge)); + } + + // If parsedValue > (long.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (long.MaxValue / 10), any nextDigit greater than 7 or 8 (depending on sign) implies overflow. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool WillOverFlow(long value, int nextDigit, int sign) + { + bool nextDigitTooLarge = nextDigit > 8 || (sign > 0 && nextDigit > 7); + return (value > maxValueLongDiv10 || (value == maxValueLongDiv10 && nextDigitTooLarge)); + } + + #endregion + + public static bool TryParseSByte(ReadOnlySpan text, out sbyte value, out int bytesConsumed, ParsedFormat format = default, SymbolTable symbolTable = null) + { + symbolTable = symbolTable ?? SymbolTable.InvariantUtf8; + + if (!format.IsDefault && format.HasPrecision) + { + throw new NotImplementedException("Format with precision not supported."); + } + + if (symbolTable == SymbolTable.InvariantUtf8) + { + if (IsHexFormat(format)) + { + return InvariantUtf8.Hex.TryParseSByte(text, out value, out bytesConsumed); + } + else + { + return InvariantUtf8.TryParseSByte(text, out value, out bytesConsumed); + } + } + else if (symbolTable == SymbolTable.InvariantUtf16) + { + ReadOnlySpan utf16Text = text.NonPortableCast(); + int charsConsumed; + bool result; + if (IsHexFormat(format)) + { + result = InvariantUtf16.Hex.TryParseSByte(utf16Text, out value, out charsConsumed); + } + else + { + result = InvariantUtf16.TryParseSByte(utf16Text, out value, out charsConsumed); + } + bytesConsumed = charsConsumed * sizeof(char); + return result; + } + + if (IsHexFormat(format)) + { + throw new NotImplementedException("The only supported encodings for hexadecimal parsing are InvariantUtf8 and InvariantUtf16."); + } + + if (!(format.IsDefault || format.Symbol == 'G' || format.Symbol == 'g')) + { + throw new NotImplementedException(String.Format("Format '{0}' not supported.", format.Symbol)); + } + + SymbolTable.Symbol nextSymbol; + int thisSymbolConsumed; + if (!symbolTable.TryParse(text, out nextSymbol, out thisSymbolConsumed)) + { + value = default; + bytesConsumed = 0; + return false; + } + + int sign = 1; + if (nextSymbol == SymbolTable.Symbol.MinusSign) + { + sign = -1; + } + + int signConsumed = 0; + if (nextSymbol == SymbolTable.Symbol.PlusSign || nextSymbol == SymbolTable.Symbol.MinusSign) + { + signConsumed = thisSymbolConsumed; + if (!symbolTable.TryParse(text.Slice(signConsumed), out nextSymbol, out thisSymbolConsumed)) + { + value = default; + bytesConsumed = 0; + return false; + } + } + + if (nextSymbol > SymbolTable.Symbol.D9) + { + value = default; + bytesConsumed = 0; + return false; + } + + int parsedValue = (int)nextSymbol; + int index = signConsumed + thisSymbolConsumed; + + while (index < text.Length) + { + bool success = symbolTable.TryParse(text.Slice(index), out nextSymbol, out thisSymbolConsumed); + if (!success || nextSymbol > SymbolTable.Symbol.D9) + { + bytesConsumed = index; + value = (sbyte)(parsedValue * sign); + return true; + } + + // If parsedValue > (sbyte.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (sbyte.MaxValue / 10), any nextDigit greater than 7 or 8 (depending on sign) implies overflow. + bool positive = sign > 0; + bool nextDigitTooLarge = nextSymbol > SymbolTable.Symbol.D8 || (positive && nextSymbol > SymbolTable.Symbol.D7); + if (parsedValue > sbyte.MaxValue / 10 || (parsedValue == sbyte.MaxValue / 10 && nextDigitTooLarge)) + { + bytesConsumed = 0; + value = default; + return false; + } + + index += thisSymbolConsumed; + parsedValue = parsedValue * 10 + (int)nextSymbol; + } + + bytesConsumed = text.Length; + value = (sbyte)(parsedValue * sign); + return true; + } + + public static bool TryParseInt16(ReadOnlySpan text, out short value, out int bytesConsumed, ParsedFormat format = default, SymbolTable symbolTable = null) + { + symbolTable = symbolTable ?? SymbolTable.InvariantUtf8; + + if (!format.IsDefault && format.HasPrecision) + { + throw new NotImplementedException("Format with precision not supported."); + } + + if (symbolTable == SymbolTable.InvariantUtf8) + { + if (IsHexFormat(format)) + { + return InvariantUtf8.Hex.TryParseInt16(text, out value, out bytesConsumed); + } + else + { + return InvariantUtf8.TryParseInt16(text, out value, out bytesConsumed); + } + } + else if (symbolTable == SymbolTable.InvariantUtf16) + { + ReadOnlySpan utf16Text = text.NonPortableCast(); + int charsConsumed; + bool result; + if (IsHexFormat(format)) + { + result = InvariantUtf16.Hex.TryParseInt16(utf16Text, out value, out charsConsumed); + } + else + { + result = InvariantUtf16.TryParseInt16(utf16Text, out value, out charsConsumed); + } + bytesConsumed = charsConsumed * sizeof(char); + return result; + } + + if (IsHexFormat(format)) + { + throw new NotImplementedException("The only supported encodings for hexadecimal parsing are InvariantUtf8 and InvariantUtf16."); + } + + if (!(format.IsDefault || format.Symbol == 'G' || format.Symbol == 'g')) + { + throw new NotImplementedException(String.Format("Format '{0}' not supported.", format.Symbol)); + } + + SymbolTable.Symbol nextSymbol; + int thisSymbolConsumed; + if (!symbolTable.TryParse(text, out nextSymbol, out thisSymbolConsumed)) + { + value = default; + bytesConsumed = 0; + return false; + } + + int sign = 1; + if ((SymbolTable.Symbol)nextSymbol == SymbolTable.Symbol.MinusSign) + { + sign = -1; + } + + int signConsumed = 0; + if (nextSymbol == SymbolTable.Symbol.PlusSign || nextSymbol == SymbolTable.Symbol.MinusSign) + { + signConsumed = thisSymbolConsumed; + if (!symbolTable.TryParse(text.Slice(signConsumed), out nextSymbol, out thisSymbolConsumed)) + { + value = default; + bytesConsumed = 0; + return false; + } + } + + if (nextSymbol > SymbolTable.Symbol.D9) + { + value = default; + bytesConsumed = 0; + return false; + } + + int parsedValue = (int)nextSymbol; + int index = signConsumed + thisSymbolConsumed; + + while (index < text.Length) + { + bool success = symbolTable.TryParse(text.Slice(index), out nextSymbol, out thisSymbolConsumed); + if (!success || nextSymbol > SymbolTable.Symbol.D9) + { + bytesConsumed = index; + value = (short)(parsedValue * sign); + return true; + } + + // If parsedValue > (short.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (short.MaxValue / 10), any nextDigit greater than 7 or 8 (depending on sign) implies overflow. + bool positive = sign > 0; + bool nextDigitTooLarge = nextSymbol > SymbolTable.Symbol.D8 || (positive && nextSymbol > SymbolTable.Symbol.D7); + if (parsedValue > short.MaxValue / 10 || (parsedValue == short.MaxValue / 10 && nextDigitTooLarge)) + { + bytesConsumed = 0; + value = default; + return false; + } + + index += thisSymbolConsumed; + parsedValue = parsedValue * 10 + (int)nextSymbol; + } + + bytesConsumed = text.Length; + value = (short)(parsedValue * sign); + return true; + } + + public static bool TryParseInt32(ReadOnlySpan text, out int value, out int bytesConsumed, ParsedFormat format = default, SymbolTable symbolTable = null) + { + bool isDefault = format.IsDefault; + symbolTable = symbolTable ?? SymbolTable.InvariantUtf8; + + if (!isDefault && format.HasPrecision) + { + throw new NotImplementedException("Format with precision not supported."); + } + + bool isHex = IsHexFormat(format); + + if (symbolTable == SymbolTable.InvariantUtf8) + { + return isHex ? InvariantUtf8.Hex.TryParseInt32(text, out value, out bytesConsumed) : + InvariantUtf8.TryParseInt32(text, out value, out bytesConsumed); + } + else if (symbolTable == SymbolTable.InvariantUtf16) + { + /*return isHex ? InvariantUtf16.Hex.TryParseInt32(text, out value, out bytesConsumed) : + InvariantUtf16.TryParseInt32(text, out value, out bytesConsumed);*/ + ReadOnlySpan utf16Text = text.NonPortableCast(); + bool result = isHex ? InvariantUtf16.Hex.TryParseInt32(utf16Text, out value, out int charsConsumed) : + InvariantUtf16.TryParseInt32(utf16Text, out value, out charsConsumed); + bytesConsumed = charsConsumed * sizeof(char); + return result; + } + + if (isHex) + { + throw new NotImplementedException("The only supported encodings for hexadecimal parsing are InvariantUtf8 and InvariantUtf16."); + } + + if (!(isDefault || format.Symbol == 'G' || format.Symbol == 'g')) + { + throw new NotImplementedException(String.Format("Format '{0}' not supported.", format.Symbol)); + } + + int textLength = text.Length; + if (textLength < 1) goto FalseExit; + + if (!symbolTable.TryParse(text, out SymbolTable.Symbol symbol, out int consumed)) goto FalseExit; + + sbyte sign = 1; + int index = 0; + if (symbol == SymbolTable.Symbol.MinusSign) + { + sign = -1; + index += consumed; + if (index >= textLength) goto FalseExit; + if (!symbolTable.TryParse(text.Slice(index), out symbol, out consumed)) goto FalseExit; + } + else if (symbol == SymbolTable.Symbol.PlusSign) + { + index += consumed; + if (index >= textLength) goto FalseExit; + if (!symbolTable.TryParse(text.Slice(index), out symbol, out consumed)) goto FalseExit; + } + + int answer = 0; + if (IsValid(symbol)) + { + int numBytes = consumed; + if (symbol == SymbolTable.Symbol.D0) + { + do + { + index += consumed; + if (index >= textLength) goto Done; + if (!symbolTable.TryParse(text.Slice(index), out symbol, out consumed)) goto Done; + } while (symbol == SymbolTable.Symbol.D0); + if (!IsValid(symbol)) goto Done; + } + + int firstNonZeroDigitIndex = index; + if (textLength - firstNonZeroDigitIndex < Int32OverflowLength * numBytes) + { + do + { + answer = answer * 10 + (int)symbol; + index += consumed; + if (index >= textLength) goto Done; + if (!symbolTable.TryParse(text.Slice(index), out symbol, out consumed)) goto Done; + } while (IsValid(symbol)); + } + else + { + do + { + answer = answer * 10 + (int)symbol; + index += consumed; + if (index - firstNonZeroDigitIndex == (Int32OverflowLength - 1) * numBytes) + { + if (!symbolTable.TryParse(text.Slice(index), out symbol, out consumed)) goto Done; + if (IsValid(symbol)) + { + if (WillOverFlow(answer, (int)symbol, sign)) goto FalseExit; + answer = answer * 10 + (int)symbol; + index += consumed; + } + goto Done; + } + if (!symbolTable.TryParse(text.Slice(index), out symbol, out consumed)) goto Done; + } while (IsValid(symbol)); + } + goto Done; + } + + FalseExit: + bytesConsumed = 0; + value = 0; + return false; + + Done: + bytesConsumed = index; + value = answer * sign; + return true; + } + + public static bool TryParseInt64(ReadOnlySpan text, out long value, out int bytesConsumed, ParsedFormat format = default, SymbolTable symbolTable = null) + { + symbolTable = symbolTable ?? SymbolTable.InvariantUtf8; + + if (!format.IsDefault && format.HasPrecision) + { + throw new NotImplementedException("Format with precision not supported."); + } + + if (symbolTable == SymbolTable.InvariantUtf8) + { + if (IsHexFormat(format)) + { + return InvariantUtf8.Hex.TryParseInt64(text, out value, out bytesConsumed); + } + else + { + return InvariantUtf8.TryParseInt64(text, out value, out bytesConsumed); + } + } + else if (symbolTable == SymbolTable.InvariantUtf16) + { + ReadOnlySpan utf16Text = text.NonPortableCast(); + int charsConsumed; + bool result; + if (IsHexFormat(format)) + { + result = InvariantUtf16.Hex.TryParseInt64(utf16Text, out value, out charsConsumed); + } + else + { + result = InvariantUtf16.TryParseInt64(utf16Text, out value, out charsConsumed); + } + bytesConsumed = charsConsumed * sizeof(char); + return result; + } + + if (IsHexFormat(format)) + { + throw new NotImplementedException("The only supported encodings for hexadecimal parsing are InvariantUtf8 and InvariantUtf16."); + } + + if (!(format.IsDefault || format.Symbol == 'G' || format.Symbol == 'g')) + { + throw new NotImplementedException(String.Format("Format '{0}' not supported.", format.Symbol)); + } + + SymbolTable.Symbol nextSymbol; + int thisSymbolConsumed; + if (!symbolTable.TryParse(text, out nextSymbol, out thisSymbolConsumed)) + { + value = default; + bytesConsumed = 0; + return false; + } + + int sign = 1; + if (nextSymbol == SymbolTable.Symbol.MinusSign) + { + sign = -1; + } + + int signConsumed = 0; + if (nextSymbol == SymbolTable.Symbol.PlusSign || nextSymbol == SymbolTable.Symbol.MinusSign) + { + signConsumed = thisSymbolConsumed; + if (!symbolTable.TryParse(text.Slice(signConsumed), out nextSymbol, out thisSymbolConsumed)) + { + value = default; + bytesConsumed = 0; + return false; + } + } + + if (nextSymbol > SymbolTable.Symbol.D9) + { + value = default; + bytesConsumed = 0; + return false; + } + + long parsedValue = (long)nextSymbol; + int index = signConsumed + thisSymbolConsumed; + + while (index < text.Length) + { + bool success = symbolTable.TryParse(text.Slice(index), out nextSymbol, out thisSymbolConsumed); + if (!success || nextSymbol > SymbolTable.Symbol.D9) + { + bytesConsumed = index; + value = (long)(parsedValue * sign); + return true; + } + + // If parsedValue > (long.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (long.MaxValue / 10), any nextDigit greater than 7 or 8 (depending on sign) implies overflow. + bool positive = sign > 0; + bool nextDigitTooLarge = nextSymbol > SymbolTable.Symbol.D8 || (positive && nextSymbol > SymbolTable.Symbol.D7); + if (parsedValue > long.MaxValue / 10 || (parsedValue == long.MaxValue / 10 && nextDigitTooLarge)) + { + bytesConsumed = 0; + value = default; + return false; + } + + index += thisSymbolConsumed; + parsedValue = parsedValue * 10 + (long)nextSymbol; + } + + bytesConsumed = text.Length; + value = (long)(parsedValue * sign); + return true; + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Parsing/Unsigned.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Parsing/Unsigned.cs new file mode 100644 index 000000000..a55d85070 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Primitives/System/Text/Parsing/Unsigned.cs @@ -0,0 +1,385 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace System.Text +{ + public static partial class PrimitiveParser + { + + public static bool TryParseByte(ReadOnlySpan text, out byte value, out int bytesConsumed, ParsedFormat format = default, SymbolTable symbolTable = null) + { + symbolTable = symbolTable ?? SymbolTable.InvariantUtf8; + + if (!format.IsDefault && format.HasPrecision) + { + throw new NotImplementedException("Format with precision not supported."); + } + + if (symbolTable == SymbolTable.InvariantUtf8) + { + if (IsHexFormat(format)) + { + return InvariantUtf8.Hex.TryParseByte(text, out value, out bytesConsumed); + } + else + { + return InvariantUtf8.TryParseByte(text, out value, out bytesConsumed); + } + } + else if (symbolTable == SymbolTable.InvariantUtf16) + { + ReadOnlySpan utf16Text = text.NonPortableCast(); + int charsConsumed; + bool result; + if (IsHexFormat(format)) + { + result = InvariantUtf16.Hex.TryParseByte(utf16Text, out value, out charsConsumed); + } + else + { + result = InvariantUtf16.TryParseByte(utf16Text, out value, out charsConsumed); + } + bytesConsumed = charsConsumed * sizeof(char); + return result; + } + + if (IsHexFormat(format)) + { + throw new NotImplementedException("The only supported encodings for hexadecimal parsing are InvariantUtf8 and InvariantUtf16."); + } + + if (!(format.IsDefault || format.Symbol == 'G' || format.Symbol == 'g')) + { + throw new NotImplementedException(String.Format("Format '{0}' not supported.", format.Symbol)); + } + + SymbolTable.Symbol nextSymbol; + int thisSymbolConsumed; + if (!symbolTable.TryParse(text, out nextSymbol, out thisSymbolConsumed)) + { + value = default; + bytesConsumed = 0; + return false; + } + + if (nextSymbol > SymbolTable.Symbol.D9) + { + value = default; + bytesConsumed = 0; + return false; + } + + uint parsedValue = (uint)nextSymbol; + int index = thisSymbolConsumed; + + while (index < text.Length) + { + bool success = symbolTable.TryParse(text.Slice(index), out nextSymbol, out thisSymbolConsumed); + if (!success || nextSymbol > SymbolTable.Symbol.D9) + { + bytesConsumed = index; + value = (byte) parsedValue; + return true; + } + + // If parsedValue > (byte.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (byte.MaxValue / 10), any nextDigit greater than 5 implies overflow. + if (parsedValue > byte.MaxValue / 10 || (parsedValue == byte.MaxValue / 10 && nextSymbol > SymbolTable.Symbol.D5)) + { + bytesConsumed = 0; + value = default; + return false; + } + + index += thisSymbolConsumed; + parsedValue = parsedValue * 10 + (uint)nextSymbol; + } + + bytesConsumed = text.Length; + value = (byte) parsedValue; + return true; + } + + public static bool TryParseUInt16(ReadOnlySpan text, out ushort value, out int bytesConsumed, ParsedFormat format = default, SymbolTable symbolTable = null) + { + symbolTable = symbolTable ?? SymbolTable.InvariantUtf8; + + if (!format.IsDefault && format.HasPrecision) + { + throw new NotImplementedException("Format with precision not supported."); + } + + if (symbolTable == SymbolTable.InvariantUtf8) + { + if (IsHexFormat(format)) + { + return InvariantUtf8.Hex.TryParseUInt16(text, out value, out bytesConsumed); + } + else + { + return InvariantUtf8.TryParseUInt16(text, out value, out bytesConsumed); + } + } + else if (symbolTable == SymbolTable.InvariantUtf16) + { + ReadOnlySpan utf16Text = text.NonPortableCast(); + int charsConsumed; + bool result; + if (IsHexFormat(format)) + { + result = InvariantUtf16.Hex.TryParseUInt16(utf16Text, out value, out charsConsumed); + } + else + { + result = InvariantUtf16.TryParseUInt16(utf16Text, out value, out charsConsumed); + } + bytesConsumed = charsConsumed * sizeof(char); + return result; + } + + if (IsHexFormat(format)) + { + throw new NotImplementedException("The only supported encodings for hexadecimal parsing are InvariantUtf8 and InvariantUtf16."); + } + + if (!(format.IsDefault || format.Symbol == 'G' || format.Symbol == 'g')) + { + throw new NotImplementedException(String.Format("Format '{0}' not supported.", format.Symbol)); + } + + SymbolTable.Symbol nextSymbol; + int thisSymbolConsumed; + if (!symbolTable.TryParse(text, out nextSymbol, out thisSymbolConsumed)) + { + value = default; + bytesConsumed = 0; + return false; + } + + if (nextSymbol > SymbolTable.Symbol.D9) + { + value = default; + bytesConsumed = 0; + return false; + } + + uint parsedValue = (uint)nextSymbol; + int index = thisSymbolConsumed; + + while (index < text.Length) + { + bool success = symbolTable.TryParse(text.Slice(index), out nextSymbol, out thisSymbolConsumed); + if (!success || nextSymbol > SymbolTable.Symbol.D9) + { + bytesConsumed = index; + value = (ushort) parsedValue; + return true; + } + + // If parsedValue > (ushort.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (ushort.MaxValue / 10), any nextDigit greater than 5 implies overflow. + if (parsedValue > ushort.MaxValue / 10 || (parsedValue == ushort.MaxValue / 10 && nextSymbol > SymbolTable.Symbol.D5)) + { + bytesConsumed = 0; + value = default; + return false; + } + + index += thisSymbolConsumed; + parsedValue = parsedValue * 10 + (uint)nextSymbol; + } + + bytesConsumed = text.Length; + value = (ushort) parsedValue; + return true; + } + + public static bool TryParseUInt32(ReadOnlySpan text, out uint value, out int bytesConsumed, ParsedFormat format = default, SymbolTable symbolTable = null) + { + symbolTable = symbolTable ?? SymbolTable.InvariantUtf8; + + if (!format.IsDefault && format.HasPrecision) + { + throw new NotImplementedException("Format with precision not supported."); + } + + if (symbolTable == SymbolTable.InvariantUtf8) + { + if (IsHexFormat(format)) + { + return InvariantUtf8.Hex.TryParseUInt32(text, out value, out bytesConsumed); + } + else + { + return InvariantUtf8.TryParseUInt32(text, out value, out bytesConsumed); + } + } + else if (symbolTable == SymbolTable.InvariantUtf16) + { + ReadOnlySpan utf16Text = text.NonPortableCast(); + int charsConsumed; + bool result; + if (IsHexFormat(format)) + { + result = InvariantUtf16.Hex.TryParseUInt32(utf16Text, out value, out charsConsumed); + } + else + { + result = InvariantUtf16.TryParseUInt32(utf16Text, out value, out charsConsumed); + } + bytesConsumed = charsConsumed * sizeof(char); + return result; + } + + if (IsHexFormat(format)) + { + throw new NotImplementedException("The only supported encodings for hexadecimal parsing are InvariantUtf8 and InvariantUtf16."); + } + + if (!(format.IsDefault || format.Symbol == 'G' || format.Symbol == 'g')) + { + throw new NotImplementedException(String.Format("Format '{0}' not supported.", format.Symbol)); + } + + SymbolTable.Symbol nextSymbol; + int thisSymbolConsumed; + if (!symbolTable.TryParse(text, out nextSymbol, out thisSymbolConsumed)) + { + value = default; + bytesConsumed = 0; + return false; + } + + if (nextSymbol > SymbolTable.Symbol.D9) + { + value = default; + bytesConsumed = 0; + return false; + } + + uint parsedValue = (uint)nextSymbol; + int index = thisSymbolConsumed; + + while (index < text.Length) + { + bool success = symbolTable.TryParse(text.Slice(index), out nextSymbol, out thisSymbolConsumed); + if (!success || nextSymbol > SymbolTable.Symbol.D9) + { + bytesConsumed = index; + value = (uint) parsedValue; + return true; + } + + // If parsedValue > (uint.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (uint.MaxValue / 10), any nextDigit greater than 5 implies overflow. + if (parsedValue > uint.MaxValue / 10 || (parsedValue == uint.MaxValue / 10 && nextSymbol > SymbolTable.Symbol.D5)) + { + bytesConsumed = 0; + value = default; + return false; + } + + index += thisSymbolConsumed; + parsedValue = parsedValue * 10 + (uint)nextSymbol; + } + + bytesConsumed = text.Length; + value = (uint) parsedValue; + return true; + } + + public static bool TryParseUInt64(ReadOnlySpan text, out ulong value, out int bytesConsumed, ParsedFormat format = default, SymbolTable symbolTable = null) + { + symbolTable = symbolTable ?? SymbolTable.InvariantUtf8; + + if (!format.IsDefault && format.HasPrecision) + { + throw new NotImplementedException("Format with precision not supported."); + } + + if (symbolTable == SymbolTable.InvariantUtf8) + { + if (IsHexFormat(format)) + { + return InvariantUtf8.Hex.TryParseUInt64(text, out value, out bytesConsumed); + } + else + { + return InvariantUtf8.TryParseUInt64(text, out value, out bytesConsumed); + } + } + else if (symbolTable == SymbolTable.InvariantUtf16) + { + ReadOnlySpan utf16Text = text.NonPortableCast(); + int charsConsumed; + bool result; + if (IsHexFormat(format)) + { + result = InvariantUtf16.Hex.TryParseUInt64(utf16Text, out value, out charsConsumed); + } + else + { + result = InvariantUtf16.TryParseUInt64(utf16Text, out value, out charsConsumed); + } + bytesConsumed = charsConsumed * sizeof(char); + return result; + } + + if (IsHexFormat(format)) + { + throw new NotImplementedException("The only supported encodings for hexadecimal parsing are InvariantUtf8 and InvariantUtf16."); + } + + if (!(format.IsDefault || format.Symbol == 'G' || format.Symbol == 'g')) + { + throw new NotImplementedException(String.Format("Format '{0}' not supported.", format.Symbol)); + } + + SymbolTable.Symbol nextSymbol; + int thisSymbolConsumed; + if (!symbolTable.TryParse(text, out nextSymbol, out thisSymbolConsumed)) + { + value = default; + bytesConsumed = 0; + return false; + } + + if (nextSymbol > SymbolTable.Symbol.D9) + { + value = default; + bytesConsumed = 0; + return false; + } + + ulong parsedValue = (uint)nextSymbol; + int index = thisSymbolConsumed; + + while (index < text.Length) + { + bool success = symbolTable.TryParse(text.Slice(index), out nextSymbol, out thisSymbolConsumed); + if (!success || nextSymbol > SymbolTable.Symbol.D9) + { + bytesConsumed = index; + value = (ulong) parsedValue; + return true; + } + + // If parsedValue > (ulong.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (ulong.MaxValue / 10), any nextDigit greater than 5 implies overflow. + if (parsedValue > ulong.MaxValue / 10 || (parsedValue == ulong.MaxValue / 10 && nextSymbol > SymbolTable.Symbol.D5)) + { + bytesConsumed = 0; + value = default; + return false; + } + + index += thisSymbolConsumed; + parsedValue = parsedValue * 10 + (uint)nextSymbol; + } + + bytesConsumed = text.Length; + value = (ulong) parsedValue; + return true; + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Utf8String/System/Text/Utf16LittleEndianCodePointEnumerable.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Utf8String/System/Text/Utf16LittleEndianCodePointEnumerable.cs new file mode 100644 index 000000000..685155468 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Utf8String/System/Text/Utf16LittleEndianCodePointEnumerable.cs @@ -0,0 +1,35 @@ +// 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.Collections; +using System.Collections.Generic; + +namespace System.Text.Utf16 +{ + // TODO: Should this and Utf8 code point enumerators/enumerable be subclasses of Utf8/16Encoder? + internal struct Utf16LittleEndianCodePointEnumerable : IEnumerable, IEnumerable + { + private string _s; + + public Utf16LittleEndianCodePointEnumerable(string s) + { + _s = s; + } + + public Utf16LittleEndianCodePointEnumerator GetEnumerator() + { + return new Utf16LittleEndianCodePointEnumerator(_s); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Utf8String/System/Text/Utf16LittleEndianCodePointEnumerator.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Utf8String/System/Text/Utf16LittleEndianCodePointEnumerator.cs new file mode 100644 index 000000000..8a091c5e3 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Utf8String/System/Text/Utf16LittleEndianCodePointEnumerator.cs @@ -0,0 +1,86 @@ +// 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.Collections; +using System.Collections.Generic; + +namespace System.Text.Utf16 +{ + internal struct Utf16LittleEndianCodePointEnumerator : IEnumerator, IEnumerator + { + string _s; + int _index; + + int _encodedChars; + uint _codePoint; + + public Utf16LittleEndianCodePointEnumerator(string s) + { + _s = s; + _index = -1; + _encodedChars = 0; + _codePoint = default; + } + + public uint Current + { + get + { + if (_encodedChars != 0) + { + return _codePoint; + } + + if (_index < 0 || _index >= _s.Length) + { + throw new InvalidOperationException("Enumerator is on invalid position"); + } + + if (!Utf8Helper.TryDecodeCodePointFromString(_s, _index, out _codePoint, out _encodedChars)) + { + _codePoint = default; + _encodedChars = 0; + // or index outside of string + throw new InvalidOperationException("Invalid characters in the string"); + } + + if (_encodedChars <= 0) + { + // TODO: Change exception type + throw new Exception("Internal error: CodePoint is decoded but number of characters read is 0 or negative"); + } + + return _codePoint; + } + } + + public void Reset() + { + _index = -1; + _encodedChars = 0; + _codePoint = default; + } + + public bool MoveNext() + { + if (_index == -1) + { + _index = 0; + _encodedChars = 0; + } + else + { + uint dummy = Current; + _index += _encodedChars; + _encodedChars = 0; + } + + return _index < _s.Length; + } + + object IEnumerator.Current { get { return Current; } } + + void IDisposable.Dispose() { } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Utf8String/System/Text/Utf8Helper.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Utf8String/System/Text/Utf8Helper.cs new file mode 100644 index 000000000..3b50f5edb --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Utf8String/System/Text/Utf8Helper.cs @@ -0,0 +1,232 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; + +namespace System.Text +{ + static class Utf8Helper + { + #region Constants + + // TODO: Make this immutable and let them be strong typed + // http://unicode.org/cldr/utility/list-unicodeset.jsp?a=\p{whitespace}&g=&i= + private static readonly uint[] SortedWhitespaceCodePoints = new uint[] + { + 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, + 0x0020, + 0x0085, + 0x00A0, + 0x1680, + 0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006, + 0x2007, + 0x2008, 0x2009, 0x200A, + 0x2028, 0x2029, + 0x202F, + 0x205F, + 0x3000 + }; + + // To get this to compile with dotnet cli, we need to temporarily un-binary the magic values + private const byte b0000_0111U = 0x07; //7 + private const byte b0000_1111U = 0x0F; //15 + private const byte b0001_1111U = 0x1F; //31 + private const byte b0011_1111U = 0x3F; //63 + private const byte b0111_1111U = 0x7F; //127 + private const byte b1000_0000U = 0x80; //128 + private const byte b1100_0000U = 0xC0; //192 + private const byte b1110_0000U = 0xE0; //224 + private const byte b1111_0000U = 0xF0; //240 + private const byte b1111_1000U = 0xF8; //248 + + private const byte NonFirstByteInCodePointValue = 0x80; + private const byte NonFirstByteInCodePointMask = 0xC0; + + public const int MaxCodeUnitsPerCodePoint = 4; + + #endregion Constants + + public static bool TryDecodeCodePoint(ReadOnlySpan utf8, int index, out uint codePoint, out int bytesConsumed) + { + if (index >= utf8.Length) + { + codePoint = default; + bytesConsumed = 0; + return false; + } + + var first = utf8[index]; + + bytesConsumed = GetEncodedBytes(first); + if (bytesConsumed == 0 || utf8.Length - index < bytesConsumed) + { + bytesConsumed = 0; + codePoint = default; + return false; + } + + switch (bytesConsumed) + { + case 1: + codePoint = first; + break; + + case 2: + codePoint = (uint)(first & b0001_1111U); + break; + + case 3: + codePoint = (uint)(first & b0000_1111U); + break; + + case 4: + codePoint = (uint)(first & b0000_0111U); + break; + + default: + codePoint = default; + bytesConsumed = 0; + return false; + } + + for (var i = 1; i < bytesConsumed; i++) + { + uint current = utf8[index + i]; + if ((current & b1100_0000U) != b1000_0000U) + { + bytesConsumed = 0; + codePoint = default; + return false; + } + + codePoint = (codePoint << 6) | (b0011_1111U & current); + } + + return true; + } + + private static int GetEncodedBytes(byte b) + { + if ((b & b1000_0000U) == 0) + return 1; + + if ((b & b1110_0000U) == b1100_0000U) + return 2; + + if ((b & b1111_0000U) == b1110_0000U) + return 3; + + if ((b & b1111_1000U) == b1111_0000U) + return 4; + + return 0; + } + + public static int GetNumberOfEncodedBytes(uint codePoint) + { + if (codePoint <= 0x7F) + return 1; + + if (codePoint <= 0x7FF) + return 2; + + if (codePoint <= 0xFFFF) + return 3; + + if (codePoint <= 0x10FFFF) + return 4; + + return 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsFirstCodeUnitInEncodedCodePoint(byte codeUnit) + { + return (codeUnit & NonFirstByteInCodePointMask) != NonFirstByteInCodePointValue; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool TryFindEncodedCodePointBytesCountGoingBackwards(ReadOnlySpan buffer, out int encodedBytes) + { + encodedBytes = 1; + ReadOnlySpan it = buffer; + // TODO: Should we have something like: Span.(Slice from the back) + for (; encodedBytes <= MaxCodeUnitsPerCodePoint; encodedBytes++, it = it.Slice(0, it.Length - 1)) + { + if (it.Length == 0) + { + encodedBytes = default; + return false; + } + + // TODO: Should we have Span.Last? + if (IsFirstCodeUnitInEncodedCodePoint(it[it.Length - 1])) + { + // output: encodedBytes + return true; + } + } + + // Invalid unicode character or stream prematurely ended (which is still invalid character in that stream) + encodedBytes = default; + return false; + } + + // TODO: Name TBD + // TODO: optimize? + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryDecodeCodePointBackwards(ReadOnlySpan buffer, out uint codePoint, out int encodedBytes) + { + if (TryFindEncodedCodePointBytesCountGoingBackwards(buffer, out encodedBytes)) + { + int realEncodedBytes; + // TODO: Inline decoding, as the invalid surrogate check can be done faster + bool ret = TryDecodeCodePoint(buffer, buffer.Length - encodedBytes, out codePoint, out realEncodedBytes); + if (ret && encodedBytes != realEncodedBytes) + { + // invalid surrogate character + // we know the character length by iterating on surrogate characters from the end + // but the first byte of the character has also encoded length + // seems like the lengths don't match + codePoint = default; + return false; + } + + return true; + } + + codePoint = default; + encodedBytes = default; + return false; + } + + // TODO: Should we rewrite this to not use char.ConvertToUtf32 or is it fast enough? + public static bool TryDecodeCodePointFromString(string s, int index, out uint codePoint, out int encodedChars) + { + if (index < 0 || index >= s.Length) + { + codePoint = default; + encodedChars = 0; + return false; + } + + if (index == s.Length - 1 && char.IsSurrogate(s[index])) + { + codePoint = default; + encodedChars = 0; + return false; + } + + encodedChars = char.IsHighSurrogate(s[index]) ? 2 : 1; + codePoint = unchecked((uint)char.ConvertToUtf32(s, index)); + + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsWhitespace(uint codePoint) + { + return Array.BinarySearch(SortedWhitespaceCodePoints, codePoint) >= 0; + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Utf8String/System/Text/Utf8String.CodePointEnumerable.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Utf8String/System/Text/Utf8String.CodePointEnumerable.cs new file mode 100644 index 000000000..22fdcd8eb --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Utf8String/System/Text/Utf8String.CodePointEnumerable.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + + +namespace System.Text.Utf8 +{ + partial struct Utf8String + { + public struct CodePointEnumerable + { + private ReadOnlySpan _buffer; + + public CodePointEnumerable(byte[] bytes, int index, int length) + { + _buffer = new ReadOnlySpan(bytes, index, length); + } + + public unsafe CodePointEnumerable(ReadOnlySpan buffer) + { + _buffer = buffer; + } + + public CodePointEnumerator GetEnumerator() + { + return new CodePointEnumerator(_buffer); + } + + public CodePointReverseEnumerator GetReverseEnumerator() + { + return new CodePointReverseEnumerator(_buffer); + } + + public int Count() + { + int result = 0; + foreach (var cp in this) + { + result++; + } + + return result; + } + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Utf8String/System/Text/Utf8String.CodePointEnumerator.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Utf8String/System/Text/Utf8String.CodePointEnumerator.cs new file mode 100644 index 000000000..152763590 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Utf8String/System/Text/Utf8String.CodePointEnumerator.cs @@ -0,0 +1,120 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + + +namespace System.Text.Utf8 +{ + partial struct Utf8String + { + public struct CodePointEnumerator + { + private ReadOnlySpan _buffer; + private int _index; + private int _currentLenCache; + private const int ResetIndex = -Utf8Helper.MaxCodeUnitsPerCodePoint - 1; + + public unsafe CodePointEnumerator(ReadOnlySpan buffer) : this() + { + _buffer = buffer; + + Reset(); + } + + // TODO: Name TBD + public int PositionInCodeUnits + { + get + { + if (IsOnResetPosition()) + { + return -1; + } + + return _index; + } + } + + public unsafe uint Current + { + get + { + if (IsOnResetPosition()) + { + throw new InvalidOperationException("MoveNext() needs to be called at least once"); + } + + if (!HasValue()) + { + throw new InvalidOperationException("Current does not exist"); + } + + uint codePoint; + bool succeeded = Utf8Helper.TryDecodeCodePoint(_buffer, _index, out codePoint, out _currentLenCache); + + if (!succeeded || _currentLenCache == 0) + { + // TODO: Change exception type + throw new Exception("Invalid code point!"); + } + + return codePoint; + } + } + + public bool MoveNext() + { + if (!HasValue()) + { + return false; + } + if (IsOnResetPosition()) + { + MoveToFirstPosition(); + return HasValue(); + } + + if (_currentLenCache == 0) + { + uint codePointDummy = Current; + if (_currentLenCache == 0) + { + throw new Exception("Invalid UTF-8 character (badly encoded)"); + } + } + + _index += _currentLenCache; + _currentLenCache = 0; + + return HasValue(); + } + + // This is different than Reset, it goes to the first element not before first + private void MoveToFirstPosition() + { + _index = 0; + } + + private bool IsOnResetPosition() + { + return _index == ResetIndex; + } + + private bool HasValue() + { + if (IsOnResetPosition()) + { + return true; + } + + return _index < _buffer.Length; + } + + // This is different than MoveToFirstPosition, this actually goes before anything + public void Reset() + { + _index = ResetIndex; + _currentLenCache = 0; + } + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Utf8String/System/Text/Utf8String.CodePointReverseEnumerator.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Utf8String/System/Text/Utf8String.CodePointReverseEnumerator.cs new file mode 100644 index 000000000..1577f5ab2 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Utf8String/System/Text/Utf8String.CodePointReverseEnumerator.cs @@ -0,0 +1,122 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + + +namespace System.Text.Utf8 +{ + partial struct Utf8String + { + // TODO: Name TBD + public struct CodePointReverseEnumerator + { + private ReadOnlySpan _buffer; + private int _index; + private int _currentLenCache; + private const int ResetIndex = -Utf8Helper.MaxCodeUnitsPerCodePoint - 1; + + public unsafe CodePointReverseEnumerator(ReadOnlySpan buffer) : this() + { + _buffer = buffer; + + Reset(); + } + + // TODO: Name TBD + public int PositionInCodeUnits + { + get + { + if (IsOnResetPosition()) + { + return -1; + } + + return _index; + } + } + + public unsafe uint Current + { + get + { + if (IsOnResetPosition()) + { + throw new InvalidOperationException("MoveNext() needs to be called at least once"); + } + + if (!HasValue()) + { + throw new InvalidOperationException("Current does not exist"); + } + + ReadOnlySpan buffer = _buffer.Slice(0, _index); + uint ret; + bool succeeded = Utf8Helper.TryDecodeCodePointBackwards(buffer, out ret, out _currentLenCache); + + if (!succeeded || _currentLenCache == 0) + { + // TODO: Change exception type + throw new Exception("Invalid code point!"); + } + + return ret; + } + } + + public bool MoveNext() + { + if (!HasValue()) + { + return false; + } + if (IsOnResetPosition()) + { + MoveToFirstPosition(); + return HasValue(); + } + + if (_currentLenCache == 0) + { + uint codePointDummy = Current; + if (_currentLenCache == 0) + { + throw new Exception("Invalid UTF-8 character (badly encoded)"); + } + } + + _index -= _currentLenCache; + _currentLenCache = 0; + + return HasValue(); + } + + // This is different than Reset, it goes to the first element not before first + private void MoveToFirstPosition() + { + _index = _buffer.Length; + } + + private bool IsOnResetPosition() + { + return _index == ResetIndex; + } + + private bool HasValue() + { + if (IsOnResetPosition()) + { + return true; + } + + return _index > 0; + } + + // This is different than MoveToFirstPosition, this actually goes before anything + public void Reset() + { + _index = ResetIndex; + _currentLenCache = 0; + } + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Utf8String/System/Text/Utf8String.Enumerator.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Utf8String/System/Text/Utf8String.Enumerator.cs new file mode 100644 index 000000000..b85ef5a84 --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Utf8String/System/Text/Utf8String.Enumerator.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + + +namespace System.Text.Utf8 +{ + partial struct Utf8String + { + public struct Enumerator + { + private readonly ReadOnlySpan _buffer; + private readonly int _length; + private int _index; + + internal Enumerator(ReadOnlySpan buffer) + { + _buffer = buffer; + _length = buffer.Length; + _index = -1; + } + + public byte Current + { + get + { + return _buffer[_index]; + } + } + + public bool MoveNext() + { + return ++_index < _length; + } + + public void Reset() + { + _index = -1; + } + } + } +} diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Utf8String/System/Text/Utf8String.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Utf8String/System/Text/Utf8String.cs new file mode 100644 index 000000000..2afd316ce --- /dev/null +++ b/src/Discord.Net.Core/_corefxlab/System.Text.Utf8String/System/Text/Utf8String.cs @@ -0,0 +1,641 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Text.Utf16; + +namespace System.Text.Utf8 +{ + [DebuggerDisplay("{ToString()}u8")] + public partial struct Utf8String + { + private readonly ReadOnlySpan _buffer; + + private const int StringNotFound = -1; + + static Utf8String s_empty => default; + + // TODO: Validate constructors, When should we copy? When should we just use the underlying array? + // TODO: Should we be immutable/readonly? + public Utf8String(ReadOnlySpan buffer) + { + _buffer = buffer; + } + + public Utf8String(byte[] utf8bytes) + { + _buffer = new ReadOnlySpan(utf8bytes); + } + + public Utf8String(byte[] utf8bytes, int index, int length) + { + _buffer = new ReadOnlySpan(utf8bytes, index, length); + } + + public Utf8String(string s) + { + if (s == null) + { + throw new ArgumentNullException("s", "String cannot be null"); + } + + if (s == string.Empty) + { + _buffer = ReadOnlySpan.Empty; + } + else + { + _buffer = new ReadOnlySpan(GetUtf8BytesFromString(s)); + } + } + + /// + /// This constructor is for use by the compiler. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public Utf8String(RuntimeFieldHandle utf8Data, int length) : this(CreateArrayFromFieldHandle(utf8Data, length)) + { + } + + public static explicit operator Utf8String(ArraySegment utf8Bytes) + { + return new Utf8String(utf8Bytes); + } + + static byte[] CreateArrayFromFieldHandle(RuntimeFieldHandle utf8Data, int length) + { + var array = new byte[length]; + RuntimeHelpers.InitializeArray(array, utf8Data); + return array; + } + + public static Utf8String Empty { get { return s_empty; } } + + /// + /// Returns length of the string in UTF-8 code units (bytes) + /// + public int Length + { + get + { + return _buffer.Length; + } + } + + public Enumerator GetEnumerator() + { + return new Enumerator(_buffer); + } + + public CodePointEnumerable CodePoints + { + get + { + return new CodePointEnumerable(_buffer); + } + } + + public byte this[int i] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + // there is no need to check the boundaries -> Span is going to do this on it's own + return (byte)_buffer[i]; + } + } + + public static implicit operator ReadOnlySpan(Utf8String utf8) + { + return utf8.Bytes; + } + + public static explicit operator Utf8String(string s) + { + return new Utf8String(s); + } + + public static explicit operator string(Utf8String s) + { + return s.ToString(); + } + + public ReadOnlySpan Bytes => _buffer; + + public override string ToString() + { + var status = Encoders.Utf8.ToUtf16Length(this.Bytes, out int needed); + if (status != Buffers.TransformationStatus.Done) + return string.Empty; + + // UTF-16 is 2 bytes per char + var chars = new char[needed >> 1]; + var utf16 = new Span(chars).AsBytes(); + status = Encoders.Utf8.ToUtf16(this.Bytes, utf16, out int consumed, out int written); + if (status != Buffers.TransformationStatus.Done) + return string.Empty; + + return new string(chars); + } + + public bool ReferenceEquals(Utf8String other) + { + return _buffer == other._buffer; + } + + public bool Equals(Utf8String other) + { + return _buffer.SequenceEqual(other._buffer); + } + + public bool Equals(string other) + { + CodePointEnumerator thisEnumerator = GetCodePointEnumerator(); + Utf16LittleEndianCodePointEnumerator otherEnumerator = new Utf16LittleEndianCodePointEnumerator(other); + + while (true) + { + bool hasNext = thisEnumerator.MoveNext(); + if (hasNext != otherEnumerator.MoveNext()) + { + return false; + } + + if (!hasNext) + { + return true; + } + + if (thisEnumerator.Current != otherEnumerator.Current) + { + return false; + } + } + } + + public static bool operator ==(Utf8String left, Utf8String right) + { + return left.Equals(right); + } + + public static bool operator !=(Utf8String left, Utf8String right) + { + return !left.Equals(right); + } + + public static bool operator ==(Utf8String left, string right) + { + return left.Equals(right); + } + + public static bool operator !=(Utf8String left, string right) + { + return !left.Equals(right); + } + + public static bool operator ==(string left, Utf8String right) + { + return right.Equals(left); + } + + public static bool operator !=(string left, Utf8String right) + { + return !right.Equals(left); + } + + public int CompareTo(Utf8String other) + { + throw new NotImplementedException(); + } + + public int CompareTo(string other) + { + throw new NotImplementedException(); + } + + /// + /// + /// + /// Index in UTF-8 code units (bytes) + /// Length in UTF-8 code units (bytes) + public Utf8String Substring(int index) + { + return Substring(index, Length - index); + } + + /// + /// + /// + /// Index in UTF-8 code units (bytes) + /// Length in UTF-8 code units (bytes) + public Utf8String Substring(int index, int length) + { + if (index < 0) + { + throw new ArgumentOutOfRangeException("index"); + } + + if (length < 0) + { + // TODO: Should we support that? + throw new ArgumentOutOfRangeException("length"); + } + + if (length == 0) + { + return Empty; + } + + if (length == Length) + { + return this; + } + + if (index + length > Length) + { + // TODO: Should this be index or length? + throw new ArgumentOutOfRangeException("index"); + } + + return new Utf8String(_buffer.Slice(index, length)); + } + + // TODO: Naive algorithm, reimplement faster + // TODO: Should this be public? + public int IndexOf(Utf8String value) + { + if (value.Length == 0) + { + // TODO: Is this the right answer? + // TODO: Does this even make sense? + return 0; + } + + if (Length == 0) + { + return StringNotFound; + } + + Utf8String restOfTheString = this; + for (int i = 0; restOfTheString.Length <= Length; restOfTheString = Substring(++i)) + { + int pos = restOfTheString.IndexOf(value[0]); + if (pos == StringNotFound) + { + return StringNotFound; + } + i += pos; + if (IsSubstringAt(i, value)) + { + return i; + } + } + + return StringNotFound; + } + + // TODO: Should this be public? + public int IndexOf(byte codeUnit) + { + // TODO: _buffer.IndexOf(codeUnit.Value); when Span has it + + for (int i = 0; i < Length; i++) + { + if (codeUnit == this[i]) + { + return i; + } + } + + return StringNotFound; + } + + // TODO: Should this be public? + public int IndexOf(uint codePoint) + { + CodePointEnumerator it = GetCodePointEnumerator(); + while (it.MoveNext()) + { + if (it.Current == codePoint) + { + return it.PositionInCodeUnits; + } + } + + return StringNotFound; + } + + // TODO: Re-evaluate all Substring family methods and check their parameters name + public bool TrySubstringFrom(Utf8String value, out Utf8String result) + { + int idx = IndexOf(value); + + if (idx == StringNotFound) + { + result = default; + return false; + } + + result = Substring(idx); + return true; + } + + public bool TrySubstringFrom(byte codeUnit, out Utf8String result) + { + int idx = IndexOf(codeUnit); + + if (idx == StringNotFound) + { + result = default; + return false; + } + + result = Substring(idx); + return true; + } + + public bool TrySubstringFrom(uint codePoint, out Utf8String result) + { + int idx = IndexOf(codePoint); + + if (idx == StringNotFound) + { + result = default; + return false; + } + + result = Substring(idx); + return true; + } + + public bool TrySubstringTo(Utf8String value, out Utf8String result) + { + int idx = IndexOf(value); + + if (idx == StringNotFound) + { + result = default; + return false; + } + + result = Substring(0, idx); + return true; + } + + public bool TrySubstringTo(byte codeUnit, out Utf8String result) + { + int idx = IndexOf(codeUnit); + + if (idx == StringNotFound) + { + result = default; + return false; + } + + result = Substring(0, idx); + return true; + } + + public bool TrySubstringTo(uint codePoint, out Utf8String result) + { + int idx = IndexOf(codePoint); + + if (idx == StringNotFound) + { + result = default; + return false; + } + + result = Substring(0, idx); + return true; + } + + public bool IsSubstringAt(int index, Utf8String s) + { + if (index < 0 || index + s.Length > Length) + { + return false; + } + + return Substring(index, s.Length).Equals(s); + } + + public void CopyTo(Span buffer) + { + _buffer.CopyTo(buffer); + } + + public void CopyTo(byte[] buffer) + { + _buffer.CopyTo(buffer); + } + + // TODO: write better hashing function + // TODO: span.GetHashCode() + some constant? + public override int GetHashCode() + { + unchecked + { + if (Length <= 4) + { + int hash = Length; + for (int i = 0; i < Length; i++) + { + hash <<= 8; + hash ^= (byte)this[i]; + } + return hash; + } + else + { + int hash = Length; + hash ^= (byte)this[0]; + hash <<= 8; + hash ^= (byte)this[1]; + hash <<= 8; + hash ^= (byte)this[Length - 2]; + hash <<= 8; + hash ^= (byte)this[Length - 1]; + return hash; + } + } + } + + public override bool Equals(object obj) + { + if (obj is Utf8String) + { + return Equals((Utf8String)obj); + } + if (obj is string) + { + return Equals((string)obj); + } + + return false; + } + + private CodePointEnumerator GetCodePointEnumerator() + { + return new CodePointEnumerator(_buffer); + } + + public bool StartsWith(uint codePoint) + { + CodePointEnumerator e = GetCodePointEnumerator(); + if (!e.MoveNext()) + { + return false; + } + + return e.Current == codePoint; + } + + public bool StartsWith(byte codeUnit) + { + if (Length == 0) + { + return false; + } + + return this[0] == codeUnit; + } + + public bool StartsWith(Utf8String value) + { + if(value.Length > this.Length) + { + return false; + } + + return this.Substring(0, value.Length).Equals(value); + } + + public bool EndsWith(byte codeUnit) + { + if (Length == 0) + { + return false; + } + + return this[Length - 1] == codeUnit; + } + + public bool EndsWith(Utf8String value) + { + if (Length < value.Length) + { + return false; + } + + return this.Substring(Length - value.Length, value.Length).Equals(value); + } + + public bool EndsWith(uint codePoint) + { + throw new NotImplementedException(); + } + + private static int GetUtf8LengthInBytes(IEnumerable codePoints) + { + int len = 0; + foreach (var codePoint in codePoints) + { + len += Utf8Helper.GetNumberOfEncodedBytes(codePoint); + } + + return len; + } + + // TODO: This should return Utf16CodeUnits which should wrap byte[]/Span, same for other encoders + private static byte[] GetUtf8BytesFromString(string str) + { + var utf16 = str.AsSpan().AsBytes(); + var status = Encoders.Utf16.ToUtf8Length(utf16, out int needed); + if (status != Buffers.TransformationStatus.Done) + return null; + + var utf8 = new byte[needed]; + status = Encoders.Utf16.ToUtf8(utf16, utf8, out int consumed, out int written); + if (status != Buffers.TransformationStatus.Done) + // This shouldn't happen... + return null; + + return utf8; + } + + public Utf8String TrimStart() + { + CodePointEnumerator it = GetCodePointEnumerator(); + while (it.MoveNext() && Utf8Helper.IsWhitespace(it.Current)) + { + } + + return Substring(it.PositionInCodeUnits); + } + + public Utf8String TrimStart(uint[] trimCodePoints) + { + throw new NotImplementedException(); + } + + public Utf8String TrimStart(byte[] trimCodeUnits) + { + throw new NotImplementedException(); + } + + public Utf8String TrimEnd() + { + CodePointReverseEnumerator it = CodePoints.GetReverseEnumerator(); + while (it.MoveNext() && Utf8Helper.IsWhitespace(it.Current)) + { + } + + return Substring(0, it.PositionInCodeUnits); + } + + public Utf8String TrimEnd(uint[] trimCodePoints) + { + throw new NotImplementedException(); + } + + public Utf8String TrimEnd(byte[] trimCodeUnits) + { + throw new NotImplementedException(); + } + + public Utf8String Trim() + { + return TrimStart().TrimEnd(); + } + + public Utf8String Trim(uint[] trimCodePoints) + { + throw new NotImplementedException(); + } + + public Utf8String Trim(byte[] trimCodeUnits) + { + throw new NotImplementedException(); + } + + // TODO: Name TBD, CopyArray? GetBytes? + public byte[] CopyBytes() + { + return _buffer.ToArray(); + } + + public byte[] CopyCodeUnits() + { + throw new NotImplementedException(); + } + + public static bool IsWhiteSpace(byte codePoint) + { + return codePoint == ' ' || codePoint == '\n' || codePoint == '\r' || codePoint == '\t'; + } + } +} diff --git a/src/Discord.Net.Providers.WS4Net/WS4NetClient.cs b/src/Discord.Net.Providers.WS4Net/WS4NetClient.cs index 93d6a83d6..9d44492a7 100644 --- a/src/Discord.Net.Providers.WS4Net/WS4NetClient.cs +++ b/src/Discord.Net.Providers.WS4Net/WS4NetClient.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Text; +using System.Text.Utf8; using System.Threading; using System.Threading.Tasks; using WebSocket4Net; @@ -12,8 +13,7 @@ namespace Discord.Net.Providers.WS4Net { internal class WS4NetClient : IWebSocketClient, IDisposable { - public event Func BinaryMessage; - public event Func TextMessage; + public event Func, bool, Task> Message; public event Func Closed; private readonly SemaphoreSlim _lock; @@ -129,15 +129,20 @@ namespace Discord.Net.Providers.WS4Net _cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_parentToken, _cancelTokenSource.Token).Token; } - public async Task SendAsync(byte[] data, int index, int count, bool isText) + public async Task SendAsync(ReadOnlyBuffer data, bool isText) { await _lock.WaitAsync(_cancelToken).ConfigureAwait(false); try { if (isText) - _client.Send(Encoding.UTF8.GetString(data, index, count)); + _client.Send(new Utf8String(data.Span).ToString()); else - _client.Send(data, index, count); + { + if (data.DangerousTryGetArray(out var array)) + _client.Send(array.Array, 0, data.Length); + else + _client.Send(data.ToArray(), 0, data.Length); + } } finally { @@ -147,11 +152,12 @@ namespace Discord.Net.Providers.WS4Net private void OnTextMessage(object sender, MessageReceivedEventArgs e) { - TextMessage(e.Message).GetAwaiter().GetResult(); + //TODO: Inefficient, but were dropping this plugin ASAP + Message(new ReadOnlyBuffer(Encoding.UTF8.GetBytes(e.Message)), true).GetAwaiter().GetResult(); } private void OnBinaryMessage(object sender, DataReceivedEventArgs e) { - BinaryMessage(e.Data, 0, e.Data.Count()).GetAwaiter().GetResult(); + Message(new ReadOnlyBuffer(e.Data, 0, e.Data.Count()), false).GetAwaiter().GetResult(); } private void OnConnected(object sender, object e) { diff --git a/src/Discord.Net.Rest/API/Common/Application.cs b/src/Discord.Net.Rest/API/Common/Application.cs index ca4c443f1..1215d095c 100644 --- a/src/Discord.Net.Rest/API/Common/Application.cs +++ b/src/Discord.Net.Rest/API/Common/Application.cs @@ -1,24 +1,24 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API { internal class Application { - [JsonProperty("description")] + [ModelProperty("description")] public string Description { get; set; } - [JsonProperty("rpc_origins")] + [ModelProperty("rpc_origins")] public string[] RPCOrigins { get; set; } - [JsonProperty("name")] + [ModelProperty("name")] public string Name { get; set; } - [JsonProperty("id")] + [ModelProperty("id")] public ulong Id { get; set; } - [JsonProperty("icon")] + [ModelProperty("icon")] public string Icon { get; set; } - [JsonProperty("flags"), Int53] + [ModelProperty("flags"), Int53] public Optional Flags { get; set; } - [JsonProperty("owner")] + [ModelProperty("owner")] public Optional Owner { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/Attachment.cs b/src/Discord.Net.Rest/API/Common/Attachment.cs index 4a651d9fa..9b794fc72 100644 --- a/src/Discord.Net.Rest/API/Common/Attachment.cs +++ b/src/Discord.Net.Rest/API/Common/Attachment.cs @@ -1,23 +1,23 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API { internal class Attachment { - [JsonProperty("id")] + [ModelProperty("id")] public ulong Id { get; set; } - [JsonProperty("filename")] + [ModelProperty("filename")] public string Filename { get; set; } - [JsonProperty("size")] + [ModelProperty("size")] public int Size { get; set; } - [JsonProperty("url")] + [ModelProperty("url")] public string Url { get; set; } - [JsonProperty("proxy_url")] + [ModelProperty("proxy_url")] public string ProxyUrl { get; set; } - [JsonProperty("height")] + [ModelProperty("height")] public Optional Height { get; set; } - [JsonProperty("width")] + [ModelProperty("width")] public Optional Width { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/Ban.cs b/src/Discord.Net.Rest/API/Common/Ban.cs index 202004f53..36c42cf23 100644 --- a/src/Discord.Net.Rest/API/Common/Ban.cs +++ b/src/Discord.Net.Rest/API/Common/Ban.cs @@ -1,13 +1,13 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API { internal class Ban { - [JsonProperty("user")] + [ModelProperty("user")] public User User { get; set; } - [JsonProperty("reason")] + [ModelProperty("reason")] public string Reason { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/Channel.cs b/src/Discord.Net.Rest/API/Common/Channel.cs index 56a24a1f4..a1a45b04c 100644 --- a/src/Discord.Net.Rest/API/Common/Channel.cs +++ b/src/Discord.Net.Rest/API/Common/Channel.cs @@ -1,5 +1,5 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; using System; namespace Discord.API @@ -7,41 +7,41 @@ namespace Discord.API internal class Channel { //Shared - [JsonProperty("id")] + [ModelProperty("id")] public ulong Id { get; set; } - [JsonProperty("type")] + [ModelProperty("type")] public ChannelType Type { get; set; } - [JsonProperty("last_message_id")] + [ModelProperty("last_message_id")] public ulong? LastMessageId { get; set; } //GuildChannel - [JsonProperty("guild_id")] + [ModelProperty("guild_id")] public Optional GuildId { get; set; } - [JsonProperty("name")] + [ModelProperty("name")] public Optional Name { get; set; } - [JsonProperty("position")] + [ModelProperty("position")] public Optional Position { get; set; } - [JsonProperty("permission_overwrites")] + [ModelProperty("permission_overwrites")] public Optional PermissionOverwrites { get; set; } //TextChannel - [JsonProperty("topic")] + [ModelProperty("topic")] public Optional Topic { get; set; } - [JsonProperty("last_pin_timestamp")] + [ModelProperty("last_pin_timestamp")] public Optional LastPinTimestamp { get; set; } //VoiceChannel - [JsonProperty("bitrate")] + [ModelProperty("bitrate")] public Optional Bitrate { get; set; } - [JsonProperty("user_limit")] + [ModelProperty("user_limit")] public Optional UserLimit { get; set; } //PrivateChannel - [JsonProperty("recipients")] + [ModelProperty("recipients")] public Optional Recipients { get; set; } //GroupChannel - [JsonProperty("icon")] + [ModelProperty("icon")] public Optional Icon { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/Connection.cs b/src/Discord.Net.Rest/API/Common/Connection.cs index ad0a76ac1..29086ab33 100644 --- a/src/Discord.Net.Rest/API/Common/Connection.cs +++ b/src/Discord.Net.Rest/API/Common/Connection.cs @@ -1,21 +1,21 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; using System.Collections.Generic; namespace Discord.API { internal class Connection { - [JsonProperty("id")] + [ModelProperty("id")] public string Id { get; set; } - [JsonProperty("type")] + [ModelProperty("type")] public string Type { get; set; } - [JsonProperty("name")] + [ModelProperty("name")] public string Name { get; set; } - [JsonProperty("revoked")] + [ModelProperty("revoked")] public bool Revoked { get; set; } - [JsonProperty("integrations")] + [ModelProperty("integrations")] public IReadOnlyCollection Integrations { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/Embed.cs b/src/Discord.Net.Rest/API/Common/Embed.cs index 1c9fa34e2..ff5aef413 100644 --- a/src/Discord.Net.Rest/API/Common/Embed.cs +++ b/src/Discord.Net.Rest/API/Common/Embed.cs @@ -1,37 +1,36 @@ #pragma warning disable CS1591 using System; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; +using Discord.Serialization; namespace Discord.API { internal class Embed { - [JsonProperty("title")] + [ModelProperty("title")] public string Title { get; set; } - [JsonProperty("description")] + [ModelProperty("description")] public string Description { get; set; } - [JsonProperty("url")] + [ModelProperty("url")] public string Url { get; set; } - [JsonProperty("color")] + [ModelProperty("color")] public uint? Color { get; set; } - [JsonProperty("type"), JsonConverter(typeof(StringEnumConverter))] + [ModelProperty("type")] public EmbedType Type { get; set; } - [JsonProperty("timestamp")] + [ModelProperty("timestamp")] public DateTimeOffset? Timestamp { get; set; } - [JsonProperty("author")] + [ModelProperty("author")] public Optional Author { get; set; } - [JsonProperty("footer")] + [ModelProperty("footer")] public Optional Footer { get; set; } - [JsonProperty("video")] + [ModelProperty("video")] public Optional Video { get; set; } - [JsonProperty("thumbnail")] + [ModelProperty("thumbnail")] public Optional Thumbnail { get; set; } - [JsonProperty("image")] + [ModelProperty("image")] public Optional Image { get; set; } - [JsonProperty("provider")] + [ModelProperty("provider")] public Optional Provider { get; set; } - [JsonProperty("fields")] + [ModelProperty("fields")] public Optional Fields { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/EmbedAuthor.cs b/src/Discord.Net.Rest/API/Common/EmbedAuthor.cs index 4381a9da3..aadb5b257 100644 --- a/src/Discord.Net.Rest/API/Common/EmbedAuthor.cs +++ b/src/Discord.Net.Rest/API/Common/EmbedAuthor.cs @@ -1,17 +1,17 @@ using System; -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API { internal class EmbedAuthor { - [JsonProperty("name")] + [ModelProperty("name")] public string Name { get; set; } - [JsonProperty("url")] + [ModelProperty("url")] public string Url { get; set; } - [JsonProperty("icon_url")] + [ModelProperty("icon_url")] public string IconUrl { get; set; } - [JsonProperty("proxy_icon_url")] + [ModelProperty("proxy_icon_url")] public string ProxyIconUrl { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/EmbedField.cs b/src/Discord.Net.Rest/API/Common/EmbedField.cs index 6ce810f1a..86171a769 100644 --- a/src/Discord.Net.Rest/API/Common/EmbedField.cs +++ b/src/Discord.Net.Rest/API/Common/EmbedField.cs @@ -1,14 +1,14 @@ -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API { internal class EmbedField { - [JsonProperty("name")] + [ModelProperty("name")] public string Name { get; set; } - [JsonProperty("value")] + [ModelProperty("value")] public string Value { get; set; } - [JsonProperty("inline")] + [ModelProperty("inline")] public bool Inline { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/EmbedFooter.cs b/src/Discord.Net.Rest/API/Common/EmbedFooter.cs index 3dd7020d9..748175b05 100644 --- a/src/Discord.Net.Rest/API/Common/EmbedFooter.cs +++ b/src/Discord.Net.Rest/API/Common/EmbedFooter.cs @@ -1,15 +1,15 @@ using System; -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API { internal class EmbedFooter { - [JsonProperty("text")] + [ModelProperty("text")] public string Text { get; set; } - [JsonProperty("icon_url")] + [ModelProperty("icon_url")] public string IconUrl { get; set; } - [JsonProperty("proxy_icon_url")] + [ModelProperty("proxy_icon_url")] public string ProxyIconUrl { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/EmbedImage.cs b/src/Discord.Net.Rest/API/Common/EmbedImage.cs index c6b3562a3..4c42bc5ce 100644 --- a/src/Discord.Net.Rest/API/Common/EmbedImage.cs +++ b/src/Discord.Net.Rest/API/Common/EmbedImage.cs @@ -1,18 +1,18 @@ #pragma warning disable CS1591 using System; -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API { internal class EmbedImage { - [JsonProperty("url")] + [ModelProperty("url")] public string Url { get; set; } - [JsonProperty("proxy_url")] + [ModelProperty("proxy_url")] public string ProxyUrl { get; set; } - [JsonProperty("height")] + [ModelProperty("height")] public Optional Height { get; set; } - [JsonProperty("width")] + [ModelProperty("width")] public Optional Width { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/EmbedProvider.cs b/src/Discord.Net.Rest/API/Common/EmbedProvider.cs index 1658eda1a..3d223ea78 100644 --- a/src/Discord.Net.Rest/API/Common/EmbedProvider.cs +++ b/src/Discord.Net.Rest/API/Common/EmbedProvider.cs @@ -1,14 +1,14 @@ #pragma warning disable CS1591 using System; -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API { internal class EmbedProvider { - [JsonProperty("name")] + [ModelProperty("name")] public string Name { get; set; } - [JsonProperty("url")] + [ModelProperty("url")] public string Url { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/EmbedThumbnail.cs b/src/Discord.Net.Rest/API/Common/EmbedThumbnail.cs index 993beb72b..54984c240 100644 --- a/src/Discord.Net.Rest/API/Common/EmbedThumbnail.cs +++ b/src/Discord.Net.Rest/API/Common/EmbedThumbnail.cs @@ -1,18 +1,18 @@ #pragma warning disable CS1591 using System; -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API { internal class EmbedThumbnail { - [JsonProperty("url")] + [ModelProperty("url")] public string Url { get; set; } - [JsonProperty("proxy_url")] + [ModelProperty("proxy_url")] public string ProxyUrl { get; set; } - [JsonProperty("height")] + [ModelProperty("height")] public Optional Height { get; set; } - [JsonProperty("width")] + [ModelProperty("width")] public Optional Width { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/EmbedVideo.cs b/src/Discord.Net.Rest/API/Common/EmbedVideo.cs index 610cf58a8..5ebc7c2f5 100644 --- a/src/Discord.Net.Rest/API/Common/EmbedVideo.cs +++ b/src/Discord.Net.Rest/API/Common/EmbedVideo.cs @@ -1,16 +1,16 @@ #pragma warning disable CS1591 using System; -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API { internal class EmbedVideo { - [JsonProperty("url")] + [ModelProperty("url")] public string Url { get; set; } - [JsonProperty("height")] + [ModelProperty("height")] public Optional Height { get; set; } - [JsonProperty("width")] + [ModelProperty("width")] public Optional Width { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/Emoji.cs b/src/Discord.Net.Rest/API/Common/Emoji.cs index bd9c4d466..c7e58eee7 100644 --- a/src/Discord.Net.Rest/API/Common/Emoji.cs +++ b/src/Discord.Net.Rest/API/Common/Emoji.cs @@ -1,19 +1,19 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API { internal class Emoji { - [JsonProperty("id")] + [ModelProperty("id")] public ulong? Id { get; set; } - [JsonProperty("name")] + [ModelProperty("name")] public string Name { get; set; } - [JsonProperty("roles")] + [ModelProperty("roles")] public ulong[] Roles { get; set; } - [JsonProperty("require_colons")] + [ModelProperty("require_colons")] public bool RequireColons { get; set; } - [JsonProperty("managed")] + [ModelProperty("managed")] public bool Managed { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/Game.cs b/src/Discord.Net.Rest/API/Common/Game.cs index a499d83b0..4f5833107 100644 --- a/src/Discord.Net.Rest/API/Common/Game.cs +++ b/src/Discord.Net.Rest/API/Common/Game.cs @@ -1,23 +1,15 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; -using System.Runtime.Serialization; +using Discord.Serialization; namespace Discord.API { internal class Game { - [JsonProperty("name")] + [ModelProperty("name")] public string Name { get; set; } - [JsonProperty("url")] + [ModelProperty("url")] public Optional StreamUrl { get; set; } - [JsonProperty("type")] + [ModelProperty("type")] public Optional StreamType { get; set; } - - [OnError] - internal void OnError(StreamingContext context, ErrorContext errorContext) - { - errorContext.Handled = true; - } } } diff --git a/src/Discord.Net.Rest/API/Common/Guild.cs b/src/Discord.Net.Rest/API/Common/Guild.cs index b69ba1293..d90065b5d 100644 --- a/src/Discord.Net.Rest/API/Common/Guild.cs +++ b/src/Discord.Net.Rest/API/Common/Guild.cs @@ -1,43 +1,43 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API { internal class Guild { - [JsonProperty("id")] + [ModelProperty("id")] public ulong Id { get; set; } - [JsonProperty("name")] + [ModelProperty("name")] public string Name { get; set; } - [JsonProperty("icon")] + [ModelProperty("icon")] public string Icon { get; set; } - [JsonProperty("splash")] + [ModelProperty("splash")] public string Splash { get; set; } - [JsonProperty("owner_id")] + [ModelProperty("owner_id")] public ulong OwnerId { get; set; } - [JsonProperty("region")] + [ModelProperty("region")] public string Region { get; set; } - [JsonProperty("afk_channel_id")] + [ModelProperty("afk_channel_id")] public ulong? AFKChannelId { get; set; } - [JsonProperty("afk_timeout")] + [ModelProperty("afk_timeout")] public int AFKTimeout { get; set; } - [JsonProperty("embed_enabled")] + [ModelProperty("embed_enabled")] public bool EmbedEnabled { get; set; } - [JsonProperty("embed_channel_id")] + [ModelProperty("embed_channel_id")] public ulong? EmbedChannelId { get; set; } - [JsonProperty("verification_level")] + [ModelProperty("verification_level")] public VerificationLevel VerificationLevel { get; set; } - [JsonProperty("voice_states")] + [ModelProperty("voice_states")] public VoiceState[] VoiceStates { get; set; } - [JsonProperty("roles")] + [ModelProperty("roles")] public Role[] Roles { get; set; } - [JsonProperty("emojis")] + [ModelProperty("emojis")] public Emoji[] Emojis { get; set; } - [JsonProperty("features")] + [ModelProperty("features")] public string[] Features { get; set; } - [JsonProperty("mfa_level")] + [ModelProperty("mfa_level")] public MfaLevel MfaLevel { get; set; } - [JsonProperty("default_message_notifications")] + [ModelProperty("default_message_notifications")] public DefaultMessageNotifications DefaultMessageNotifications { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/GuildEmbed.cs b/src/Discord.Net.Rest/API/Common/GuildEmbed.cs index ff8b8e180..2f03bde4f 100644 --- a/src/Discord.Net.Rest/API/Common/GuildEmbed.cs +++ b/src/Discord.Net.Rest/API/Common/GuildEmbed.cs @@ -1,13 +1,13 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API { internal class GuildEmbed { - [JsonProperty("enabled")] + [ModelProperty("enabled")] public bool Enabled { get; set; } - [JsonProperty("channel_id")] + [ModelProperty("channel_id")] public ulong ChannelId { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/GuildMember.cs b/src/Discord.Net.Rest/API/Common/GuildMember.cs index 24ad17c14..45c5060d2 100644 --- a/src/Discord.Net.Rest/API/Common/GuildMember.cs +++ b/src/Discord.Net.Rest/API/Common/GuildMember.cs @@ -1,22 +1,22 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; using System; namespace Discord.API { internal class GuildMember { - [JsonProperty("user")] + [ModelProperty("user")] public User User { get; set; } - [JsonProperty("nick")] + [ModelProperty("nick")] public Optional Nick { get; set; } - [JsonProperty("roles")] + [ModelProperty("roles")] public Optional Roles { get; set; } - [JsonProperty("joined_at")] + [ModelProperty("joined_at")] public Optional JoinedAt { get; set; } - [JsonProperty("deaf")] + [ModelProperty("deaf")] public Optional Deaf { get; set; } - [JsonProperty("mute")] + [ModelProperty("mute")] public Optional Mute { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/Integration.cs b/src/Discord.Net.Rest/API/Common/Integration.cs index 821359975..8a4b4c1cb 100644 --- a/src/Discord.Net.Rest/API/Common/Integration.cs +++ b/src/Discord.Net.Rest/API/Common/Integration.cs @@ -1,32 +1,32 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; using System; namespace Discord.API { internal class Integration { - [JsonProperty("id")] + [ModelProperty("id")] public ulong Id { get; set; } - [JsonProperty("name")] + [ModelProperty("name")] public string Name { get; set; } - [JsonProperty("type")] + [ModelProperty("type")] public string Type { get; set; } - [JsonProperty("enabled")] + [ModelProperty("enabled")] public bool Enabled { get; set; } - [JsonProperty("syncing")] + [ModelProperty("syncing")] public bool Syncing { get; set; } - [JsonProperty("role_id")] + [ModelProperty("role_id")] public ulong RoleId { get; set; } - [JsonProperty("expire_behavior")] + [ModelProperty("expire_behavior")] public ulong ExpireBehavior { get; set; } - [JsonProperty("expire_grace_period")] + [ModelProperty("expire_grace_period")] public ulong ExpireGracePeriod { get; set; } - [JsonProperty("user")] + [ModelProperty("user")] public User User { get; set; } - [JsonProperty("account")] + [ModelProperty("account")] public IntegrationAccount Account { get; set; } - [JsonProperty("synced_at")] + [ModelProperty("synced_at")] public DateTimeOffset SyncedAt { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/IntegrationAccount.cs b/src/Discord.Net.Rest/API/Common/IntegrationAccount.cs index 22831e795..b8b952da5 100644 --- a/src/Discord.Net.Rest/API/Common/IntegrationAccount.cs +++ b/src/Discord.Net.Rest/API/Common/IntegrationAccount.cs @@ -1,13 +1,13 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API { internal class IntegrationAccount { - [JsonProperty("id")] + [ModelProperty("id")] public ulong Id { get; set; } - [JsonProperty("name")] + [ModelProperty("name")] public string Name { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/Invite.cs b/src/Discord.Net.Rest/API/Common/Invite.cs index 67a318c5a..ec60a797a 100644 --- a/src/Discord.Net.Rest/API/Common/Invite.cs +++ b/src/Discord.Net.Rest/API/Common/Invite.cs @@ -1,15 +1,15 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API { internal class Invite { - [JsonProperty("code")] + [ModelProperty("code")] public string Code { get; set; } - [JsonProperty("guild")] + [ModelProperty("guild")] public InviteGuild Guild { get; set; } - [JsonProperty("channel")] + [ModelProperty("channel")] public InviteChannel Channel { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/InviteChannel.cs b/src/Discord.Net.Rest/API/Common/InviteChannel.cs index ca9699067..7b4c643d1 100644 --- a/src/Discord.Net.Rest/API/Common/InviteChannel.cs +++ b/src/Discord.Net.Rest/API/Common/InviteChannel.cs @@ -1,15 +1,15 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API { internal class InviteChannel { - [JsonProperty("id")] + [ModelProperty("id")] public ulong Id { get; set; } - [JsonProperty("name")] + [ModelProperty("name")] public string Name { get; set; } - [JsonProperty("type")] + [ModelProperty("type")] public string Type { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/InviteGuild.cs b/src/Discord.Net.Rest/API/Common/InviteGuild.cs index 3d6d7cd74..b9cc671ef 100644 --- a/src/Discord.Net.Rest/API/Common/InviteGuild.cs +++ b/src/Discord.Net.Rest/API/Common/InviteGuild.cs @@ -1,15 +1,15 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API { internal class InviteGuild { - [JsonProperty("id")] + [ModelProperty("id")] public ulong Id { get; set; } - [JsonProperty("name")] + [ModelProperty("name")] public string Name { get; set; } - [JsonProperty("splash_hash")] + [ModelProperty("splash_hash")] public string SplashHash { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/InviteMetadata.cs b/src/Discord.Net.Rest/API/Common/InviteMetadata.cs index 586307523..f303006a5 100644 --- a/src/Discord.Net.Rest/API/Common/InviteMetadata.cs +++ b/src/Discord.Net.Rest/API/Common/InviteMetadata.cs @@ -1,24 +1,24 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; using System; namespace Discord.API { internal class InviteMetadata : Invite { - [JsonProperty("inviter")] + [ModelProperty("inviter")] public User Inviter { get; set; } - [JsonProperty("uses")] + [ModelProperty("uses")] public int Uses { get; set; } - [JsonProperty("max_uses")] + [ModelProperty("max_uses")] public int MaxUses { get; set; } - [JsonProperty("max_age")] + [ModelProperty("max_age")] public int MaxAge { get; set; } - [JsonProperty("temporary")] + [ModelProperty("temporary")] public bool Temporary { get; set; } - [JsonProperty("created_at")] + [ModelProperty("created_at")] public DateTimeOffset CreatedAt { get; set; } - [JsonProperty("revoked")] + [ModelProperty("revoked")] public bool Revoked { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/Message.cs b/src/Discord.Net.Rest/API/Common/Message.cs index 9a7629b96..328fefdbb 100644 --- a/src/Discord.Net.Rest/API/Common/Message.cs +++ b/src/Discord.Net.Rest/API/Common/Message.cs @@ -1,42 +1,42 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; using System; namespace Discord.API { internal class Message { - [JsonProperty("id")] + [ModelProperty("id")] public ulong Id { get; set; } - [JsonProperty("type")] + [ModelProperty("type")] public MessageType Type { get; set; } - [JsonProperty("channel_id")] + [ModelProperty("channel_id")] public ulong ChannelId { get; set; } - [JsonProperty("webhook_id")] + [ModelProperty("webhook_id")] public Optional WebhookId { get; set; } - [JsonProperty("author")] + [ModelProperty("author")] public Optional Author { get; set; } - [JsonProperty("content")] + [ModelProperty("content")] public Optional Content { get; set; } - [JsonProperty("timestamp")] + [ModelProperty("timestamp")] public Optional Timestamp { get; set; } - [JsonProperty("edited_timestamp")] + [ModelProperty("edited_timestamp")] public Optional EditedTimestamp { get; set; } - [JsonProperty("tts")] + [ModelProperty("tts")] public Optional IsTextToSpeech { get; set; } - [JsonProperty("mention_everyone")] + [ModelProperty("mention_everyone")] public Optional MentionEveryone { get; set; } - [JsonProperty("mentions")] + [ModelProperty("mentions")] public Optional[]> UserMentions { get; set; } - [JsonProperty("mention_roles")] + [ModelProperty("mention_roles")] public Optional RoleMentions { get; set; } - [JsonProperty("attachments")] + [ModelProperty("attachments")] public Optional Attachments { get; set; } - [JsonProperty("embeds")] + [ModelProperty("embeds")] public Optional Embeds { get; set; } - [JsonProperty("pinned")] + [ModelProperty("pinned")] public Optional Pinned { get; set; } - [JsonProperty("reactions")] + [ModelProperty("reactions")] public Optional Reactions { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/Overwrite.cs b/src/Discord.Net.Rest/API/Common/Overwrite.cs index 1ba836127..c7e613285 100644 --- a/src/Discord.Net.Rest/API/Common/Overwrite.cs +++ b/src/Discord.Net.Rest/API/Common/Overwrite.cs @@ -1,17 +1,17 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API { internal class Overwrite { - [JsonProperty("id")] + [ModelProperty("id")] public ulong TargetId { get; set; } - [JsonProperty("type")] + [ModelProperty("type")] public PermissionTarget TargetType { get; set; } - [JsonProperty("deny"), Int53] + [ModelProperty("deny"), Int53] public ulong Deny { get; set; } - [JsonProperty("allow"), Int53] + [ModelProperty("allow"), Int53] public ulong Allow { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/Presence.cs b/src/Discord.Net.Rest/API/Common/Presence.cs index 2902b7ce3..68dae302a 100644 --- a/src/Discord.Net.Rest/API/Common/Presence.cs +++ b/src/Discord.Net.Rest/API/Common/Presence.cs @@ -1,22 +1,22 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API { internal class Presence { - [JsonProperty("user")] + [ModelProperty("user")] public User User { get; set; } - [JsonProperty("guild_id")] + [ModelProperty("guild_id")] public Optional GuildId { get; set; } - [JsonProperty("status")] + [ModelProperty("status")] public UserStatus Status { get; set; } - [JsonProperty("game")] + [ModelProperty("game")] public Game Game { get; set; } - [JsonProperty("roles")] + [ModelProperty("roles")] public Optional Roles { get; set; } - [JsonProperty("nick")] + [ModelProperty("nick")] public Optional Nick { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/Reaction.cs b/src/Discord.Net.Rest/API/Common/Reaction.cs index 4d368ab2d..c79d1b3ba 100644 --- a/src/Discord.Net.Rest/API/Common/Reaction.cs +++ b/src/Discord.Net.Rest/API/Common/Reaction.cs @@ -1,14 +1,14 @@ -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API { internal class Reaction { - [JsonProperty("count")] + [ModelProperty("count")] public int Count { get; set; } - [JsonProperty("me")] + [ModelProperty("me")] public bool Me { get; set; } - [JsonProperty("emoji")] + [ModelProperty("emoji")] public Emoji Emoji { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/ReadState.cs b/src/Discord.Net.Rest/API/Common/ReadState.cs index 6ea6e4bd0..9fc6c11dd 100644 --- a/src/Discord.Net.Rest/API/Common/ReadState.cs +++ b/src/Discord.Net.Rest/API/Common/ReadState.cs @@ -1,15 +1,15 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API { internal class ReadState { - [JsonProperty("id")] + [ModelProperty("id")] public ulong Id { get; set; } - [JsonProperty("mention_count")] + [ModelProperty("mention_count")] public int MentionCount { get; set; } - [JsonProperty("last_message_id")] + [ModelProperty("last_message_id")] public Optional LastMessageId { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/Relationship.cs b/src/Discord.Net.Rest/API/Common/Relationship.cs index ecbb96f80..92c217aed 100644 --- a/src/Discord.Net.Rest/API/Common/Relationship.cs +++ b/src/Discord.Net.Rest/API/Common/Relationship.cs @@ -1,15 +1,15 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API { internal class Relationship { - [JsonProperty("id")] + [ModelProperty("id")] public ulong Id { get; set; } - [JsonProperty("user")] + [ModelProperty("user")] public User User { get; set; } - [JsonProperty("type")] + [ModelProperty("type")] public RelationshipType Type { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/Role.cs b/src/Discord.Net.Rest/API/Common/Role.cs index 856a8695f..9daa2d8ab 100644 --- a/src/Discord.Net.Rest/API/Common/Role.cs +++ b/src/Discord.Net.Rest/API/Common/Role.cs @@ -1,25 +1,25 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API { internal class Role { - [JsonProperty("id")] + [ModelProperty("id")] public ulong Id { get; set; } - [JsonProperty("name")] + [ModelProperty("name")] public string Name { get; set; } - [JsonProperty("color")] + [ModelProperty("color")] public uint Color { get; set; } - [JsonProperty("hoist")] + [ModelProperty("hoist")] public bool Hoist { get; set; } - [JsonProperty("mentionable")] + [ModelProperty("mentionable")] public bool Mentionable { get; set; } - [JsonProperty("position")] + [ModelProperty("position")] public int Position { get; set; } - [JsonProperty("permissions"), Int53] + [ModelProperty("permissions"), Int53] public ulong Permissions { get; set; } - [JsonProperty("managed")] + [ModelProperty("managed")] public bool Managed { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/User.cs b/src/Discord.Net.Rest/API/Common/User.cs index d49d24623..06046081c 100644 --- a/src/Discord.Net.Rest/API/Common/User.cs +++ b/src/Discord.Net.Rest/API/Common/User.cs @@ -1,27 +1,27 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API { internal class User { - [JsonProperty("id")] + [ModelProperty("id")] public ulong Id { get; set; } - [JsonProperty("username")] + [ModelProperty("username")] public Optional Username { get; set; } - [JsonProperty("discriminator")] + [ModelProperty("discriminator")] public Optional Discriminator { get; set; } - [JsonProperty("bot")] + [ModelProperty("bot")] public Optional Bot { get; set; } - [JsonProperty("avatar")] + [ModelProperty("avatar")] public Optional Avatar { get; set; } //CurrentUser - [JsonProperty("verified")] + [ModelProperty("verified")] public Optional Verified { get; set; } - [JsonProperty("email")] + [ModelProperty("email")] public Optional Email { get; set; } - [JsonProperty("mfa_enabled")] + [ModelProperty("mfa_enabled")] public Optional MfaEnabled { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/UserGuild.cs b/src/Discord.Net.Rest/API/Common/UserGuild.cs index f4f763885..e87b5fd98 100644 --- a/src/Discord.Net.Rest/API/Common/UserGuild.cs +++ b/src/Discord.Net.Rest/API/Common/UserGuild.cs @@ -1,19 +1,19 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API { internal class UserGuild { - [JsonProperty("id")] + [ModelProperty("id")] public ulong Id { get; set; } - [JsonProperty("name")] + [ModelProperty("name")] public string Name { get; set; } - [JsonProperty("icon")] + [ModelProperty("icon")] public string Icon { get; set; } - [JsonProperty("owner")] + [ModelProperty("owner")] public bool Owner { get; set; } - [JsonProperty("permissions"), Int53] + [ModelProperty("permissions"), Int53] public ulong Permissions { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/VoiceRegion.cs b/src/Discord.Net.Rest/API/Common/VoiceRegion.cs index 5f31e8f64..0b6647c62 100644 --- a/src/Discord.Net.Rest/API/Common/VoiceRegion.cs +++ b/src/Discord.Net.Rest/API/Common/VoiceRegion.cs @@ -1,21 +1,21 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API { internal class VoiceRegion { - [JsonProperty("id")] + [ModelProperty("id")] public string Id { get; set; } - [JsonProperty("name")] + [ModelProperty("name")] public string Name { get; set; } - [JsonProperty("vip")] + [ModelProperty("vip")] public bool IsVip { get; set; } - [JsonProperty("optimal")] + [ModelProperty("optimal")] public bool IsOptimal { get; set; } - [JsonProperty("sample_hostname")] + [ModelProperty("sample_hostname")] public string SampleHostname { get; set; } - [JsonProperty("sample_port")] + [ModelProperty("sample_port")] public int SamplePort { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/VoiceState.cs b/src/Discord.Net.Rest/API/Common/VoiceState.cs index 563a5f95b..f624144f6 100644 --- a/src/Discord.Net.Rest/API/Common/VoiceState.cs +++ b/src/Discord.Net.Rest/API/Common/VoiceState.cs @@ -1,27 +1,27 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API { internal class VoiceState { - [JsonProperty("guild_id")] + [ModelProperty("guild_id")] public ulong? GuildId { get; set; } - [JsonProperty("channel_id")] + [ModelProperty("channel_id")] public ulong? ChannelId { get; set; } - [JsonProperty("user_id")] + [ModelProperty("user_id")] public ulong UserId { get; set; } - [JsonProperty("session_id")] + [ModelProperty("session_id")] public string SessionId { get; set; } - [JsonProperty("deaf")] + [ModelProperty("deaf")] public bool Deaf { get; set; } - [JsonProperty("mute")] + [ModelProperty("mute")] public bool Mute { get; set; } - [JsonProperty("self_deaf")] + [ModelProperty("self_deaf")] public bool SelfDeaf { get; set; } - [JsonProperty("self_mute")] + [ModelProperty("self_mute")] public bool SelfMute { get; set; } - [JsonProperty("suppress")] + [ModelProperty("suppress")] public bool Suppress { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/CreateChannelInviteParams.cs b/src/Discord.Net.Rest/API/Rest/CreateChannelInviteParams.cs index db79bc314..569da29ff 100644 --- a/src/Discord.Net.Rest/API/Rest/CreateChannelInviteParams.cs +++ b/src/Discord.Net.Rest/API/Rest/CreateChannelInviteParams.cs @@ -1,18 +1,17 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rest { - [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class CreateChannelInviteParams { - [JsonProperty("max_age")] + [ModelProperty("max_age")] public Optional MaxAge { get; set; } - [JsonProperty("max_uses")] + [ModelProperty("max_uses")] public Optional MaxUses { get; set; } - [JsonProperty("temporary")] + [ModelProperty("temporary")] public Optional IsTemporary { get; set; } - [JsonProperty("unique")] + [ModelProperty("unique")] public Optional IsUnique { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/CreateDMChannelParams.cs b/src/Discord.Net.Rest/API/Rest/CreateDMChannelParams.cs index f32796e02..fc22fee03 100644 --- a/src/Discord.Net.Rest/API/Rest/CreateDMChannelParams.cs +++ b/src/Discord.Net.Rest/API/Rest/CreateDMChannelParams.cs @@ -1,12 +1,11 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rest { - [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class CreateDMChannelParams { - [JsonProperty("recipient_id")] + [ModelProperty("recipient_id")] public ulong RecipientId { get; } public CreateDMChannelParams(ulong recipientId) diff --git a/src/Discord.Net.Rest/API/Rest/CreateGuildChannelParams.cs b/src/Discord.Net.Rest/API/Rest/CreateGuildChannelParams.cs index bae677148..8dfa72785 100644 --- a/src/Discord.Net.Rest/API/Rest/CreateGuildChannelParams.cs +++ b/src/Discord.Net.Rest/API/Rest/CreateGuildChannelParams.cs @@ -1,17 +1,16 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rest { - [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class CreateGuildChannelParams { - [JsonProperty("name")] + [ModelProperty("name")] public string Name { get; } - [JsonProperty("type")] + [ModelProperty("type")] public ChannelType Type { get; } - [JsonProperty("bitrate")] + [ModelProperty("bitrate")] public Optional Bitrate { get; set; } public CreateGuildChannelParams(string name, ChannelType type) diff --git a/src/Discord.Net.Rest/API/Rest/CreateGuildIntegrationParams.cs b/src/Discord.Net.Rest/API/Rest/CreateGuildIntegrationParams.cs index 1053a0ed3..aa1e236e6 100644 --- a/src/Discord.Net.Rest/API/Rest/CreateGuildIntegrationParams.cs +++ b/src/Discord.Net.Rest/API/Rest/CreateGuildIntegrationParams.cs @@ -1,14 +1,13 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rest { - [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class CreateGuildIntegrationParams { - [JsonProperty("id")] + [ModelProperty("id")] public ulong Id { get; } - [JsonProperty("type")] + [ModelProperty("type")] public string Type { get; } public CreateGuildIntegrationParams(ulong id, string type) diff --git a/src/Discord.Net.Rest/API/Rest/CreateGuildParams.cs b/src/Discord.Net.Rest/API/Rest/CreateGuildParams.cs index cda6caedf..d416e0cb3 100644 --- a/src/Discord.Net.Rest/API/Rest/CreateGuildParams.cs +++ b/src/Discord.Net.Rest/API/Rest/CreateGuildParams.cs @@ -1,17 +1,16 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rest { - [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class CreateGuildParams { - [JsonProperty("name")] + [ModelProperty("name")] public string Name { get; } - [JsonProperty("region")] + [ModelProperty("region")] public string RegionId { get; } - [JsonProperty("icon")] + [ModelProperty("icon")] public Optional Icon { get; set; } public CreateGuildParams(string name, string regionId) diff --git a/src/Discord.Net.Rest/API/Rest/CreateMessageParams.cs b/src/Discord.Net.Rest/API/Rest/CreateMessageParams.cs index d77bff8ca..3067803d0 100644 --- a/src/Discord.Net.Rest/API/Rest/CreateMessageParams.cs +++ b/src/Discord.Net.Rest/API/Rest/CreateMessageParams.cs @@ -1,19 +1,18 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rest { - [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class CreateMessageParams { - [JsonProperty("content")] + [ModelProperty("content")] public string Content { get; } - [JsonProperty("nonce")] + [ModelProperty("nonce")] public Optional Nonce { get; set; } - [JsonProperty("tts")] + [ModelProperty("tts")] public Optional IsTTS { get; set; } - [JsonProperty("embed")] + [ModelProperty("embed")] public Optional Embed { get; set; } public CreateMessageParams(string content) diff --git a/src/Discord.Net.Rest/API/Rest/CreateWebhookMessageParams.cs b/src/Discord.Net.Rest/API/Rest/CreateWebhookMessageParams.cs index 970a30201..1b8661194 100644 --- a/src/Discord.Net.Rest/API/Rest/CreateWebhookMessageParams.cs +++ b/src/Discord.Net.Rest/API/Rest/CreateWebhookMessageParams.cs @@ -1,23 +1,22 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rest { - [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class CreateWebhookMessageParams { - [JsonProperty("content")] + [ModelProperty("content")] public string Content { get; } - [JsonProperty("nonce")] + [ModelProperty("nonce")] public Optional Nonce { get; set; } - [JsonProperty("tts")] + [ModelProperty("tts")] public Optional IsTTS { get; set; } - [JsonProperty("embeds")] + [ModelProperty("embeds")] public Optional Embeds { get; set; } - [JsonProperty("username")] + [ModelProperty("username")] public Optional Username { get; set; } - [JsonProperty("avatar_url")] + [ModelProperty("avatar_url")] public Optional AvatarUrl { get; set; } public CreateWebhookMessageParams(string content) diff --git a/src/Discord.Net.Rest/API/Rest/DeleteMessagesParams.cs b/src/Discord.Net.Rest/API/Rest/DeleteMessagesParams.cs index ca9d8c26e..ab5ee30cf 100644 --- a/src/Discord.Net.Rest/API/Rest/DeleteMessagesParams.cs +++ b/src/Discord.Net.Rest/API/Rest/DeleteMessagesParams.cs @@ -1,12 +1,11 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rest { - [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class DeleteMessagesParams { - [JsonProperty("messages")] + [ModelProperty("messages")] public ulong[] MessageIds { get; } public DeleteMessagesParams(ulong[] messageIds) diff --git a/src/Discord.Net.Rest/API/Rest/GetBotGatewayResponse.cs b/src/Discord.Net.Rest/API/Rest/GetBotGatewayResponse.cs index 111fcf3db..94a7b805d 100644 --- a/src/Discord.Net.Rest/API/Rest/GetBotGatewayResponse.cs +++ b/src/Discord.Net.Rest/API/Rest/GetBotGatewayResponse.cs @@ -1,13 +1,13 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rest { internal class GetBotGatewayResponse { - [JsonProperty("url")] + [ModelProperty("url")] public string Url { get; set; } - [JsonProperty("shards")] + [ModelProperty("shards")] public int Shards { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/GetGatewayResponse.cs b/src/Discord.Net.Rest/API/Rest/GetGatewayResponse.cs index ce3630170..bfe52e367 100644 --- a/src/Discord.Net.Rest/API/Rest/GetGatewayResponse.cs +++ b/src/Discord.Net.Rest/API/Rest/GetGatewayResponse.cs @@ -1,11 +1,11 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rest { internal class GetGatewayResponse { - [JsonProperty("url")] + [ModelProperty("url")] public string Url { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/GetGuildPruneCountResponse.cs b/src/Discord.Net.Rest/API/Rest/GetGuildPruneCountResponse.cs index 4af85acfa..a5a0c59e7 100644 --- a/src/Discord.Net.Rest/API/Rest/GetGuildPruneCountResponse.cs +++ b/src/Discord.Net.Rest/API/Rest/GetGuildPruneCountResponse.cs @@ -1,11 +1,11 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rest { internal class GetGuildPruneCountResponse { - [JsonProperty("pruned")] + [ModelProperty("pruned")] public int Pruned { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/GuildPruneParams.cs b/src/Discord.Net.Rest/API/Rest/GuildPruneParams.cs index 6a98d3758..efa95c0d8 100644 --- a/src/Discord.Net.Rest/API/Rest/GuildPruneParams.cs +++ b/src/Discord.Net.Rest/API/Rest/GuildPruneParams.cs @@ -1,12 +1,11 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rest { - [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class GuildPruneParams { - [JsonProperty("days")] + [ModelProperty("days")] public int Days { get; } public GuildPruneParams(int days) diff --git a/src/Discord.Net.Rest/API/Rest/ModifyChannelPermissionsParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyChannelPermissionsParams.cs index 0fe5f7e5a..4b30f6065 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyChannelPermissionsParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyChannelPermissionsParams.cs @@ -1,16 +1,15 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rest { - [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class ModifyChannelPermissionsParams { - [JsonProperty("type")] + [ModelProperty("type")] public string Type { get; } - [JsonProperty("allow")] + [ModelProperty("allow")] public ulong Allow { get; } - [JsonProperty("deny")] + [ModelProperty("deny")] public ulong Deny { get; } public ModifyChannelPermissionsParams(string type, ulong allow, ulong deny) diff --git a/src/Discord.Net.Rest/API/Rest/ModifyCurrentUserNickParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyCurrentUserNickParams.cs index ba44e34cf..872c7dff1 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyCurrentUserNickParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyCurrentUserNickParams.cs @@ -1,12 +1,11 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rest { - [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class ModifyCurrentUserNickParams { - [JsonProperty("nick")] + [ModelProperty("nick")] public string Nickname { get; } public ModifyCurrentUserNickParams(string nickname) diff --git a/src/Discord.Net.Rest/API/Rest/ModifyCurrentUserParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyCurrentUserParams.cs index 7ba27c3a5..dec17e5cb 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyCurrentUserParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyCurrentUserParams.cs @@ -1,14 +1,13 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rest { - [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class ModifyCurrentUserParams { - [JsonProperty("username")] + [ModelProperty("username")] public Optional Username { get; set; } - [JsonProperty("avatar")] + [ModelProperty("avatar")] public Optional Avatar { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/ModifyGuildChannelParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyGuildChannelParams.cs index b4add2ac9..bb076753c 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyGuildChannelParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyGuildChannelParams.cs @@ -1,14 +1,13 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rest { - [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class ModifyGuildChannelParams { - [JsonProperty("name")] + [ModelProperty("name")] public Optional Name { get; set; } - [JsonProperty("position")] + [ModelProperty("position")] public Optional Position { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/ModifyGuildChannelsParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyGuildChannelsParams.cs index f97fbda0b..0ed649c34 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyGuildChannelsParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyGuildChannelsParams.cs @@ -1,14 +1,13 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rest { - [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class ModifyGuildChannelsParams { - [JsonProperty("id")] + [ModelProperty("id")] public ulong Id { get; } - [JsonProperty("position")] + [ModelProperty("position")] public int Position { get; } public ModifyGuildChannelsParams(ulong id, int position) diff --git a/src/Discord.Net.Rest/API/Rest/ModifyGuildEmbedParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyGuildEmbedParams.cs index 487744c65..9162f4be5 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyGuildEmbedParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyGuildEmbedParams.cs @@ -1,14 +1,13 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rest { - [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class ModifyGuildEmbedParams { - [JsonProperty("enabled")] + [ModelProperty("enabled")] public Optional Enabled { get; set; } - [JsonProperty("channel")] + [ModelProperty("channel")] public Optional ChannelId { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/ModifyGuildIntegrationParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyGuildIntegrationParams.cs index 0a1b4f9fa..fdc9eecee 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyGuildIntegrationParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyGuildIntegrationParams.cs @@ -1,16 +1,15 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rest { - [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class ModifyGuildIntegrationParams { - [JsonProperty("expire_behavior")] + [ModelProperty("expire_behavior")] public Optional ExpireBehavior { get; set; } - [JsonProperty("expire_grace_period")] + [ModelProperty("expire_grace_period")] public Optional ExpireGracePeriod { get; set; } - [JsonProperty("enable_emoticons")] + [ModelProperty("enable_emoticons")] public Optional EnableEmoticons { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/ModifyGuildMemberParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyGuildMemberParams.cs index 159670afb..d66bd20d4 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyGuildMemberParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyGuildMemberParams.cs @@ -1,20 +1,19 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rest { - [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class ModifyGuildMemberParams { - [JsonProperty("mute")] + [ModelProperty("mute")] public Optional Mute { get; set; } - [JsonProperty("deaf")] + [ModelProperty("deaf")] public Optional Deaf { get; set; } - [JsonProperty("nick")] + [ModelProperty("nick")] public Optional Nickname { get; set; } - [JsonProperty("roles")] + [ModelProperty("roles")] public Optional RoleIds { get; set; } - [JsonProperty("channel_id")] + [ModelProperty("channel_id")] public Optional ChannelId { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/ModifyGuildParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyGuildParams.cs index 2c7d84087..592aab3a1 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyGuildParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyGuildParams.cs @@ -1,30 +1,29 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rest { - [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class ModifyGuildParams { - [JsonProperty("username")] + [ModelProperty("username")] public Optional Username { get; set; } - [JsonProperty("name")] + [ModelProperty("name")] public Optional Name { get; set; } - [JsonProperty("region")] + [ModelProperty("region")] public Optional RegionId { get; set; } - [JsonProperty("verification_level")] + [ModelProperty("verification_level")] public Optional VerificationLevel { get; set; } - [JsonProperty("default_message_notifications")] + [ModelProperty("default_message_notifications")] public Optional DefaultMessageNotifications { get; set; } - [JsonProperty("afk_timeout")] + [ModelProperty("afk_timeout")] public Optional AfkTimeout { get; set; } - [JsonProperty("icon")] + [ModelProperty("icon")] public Optional Icon { get; set; } - [JsonProperty("splash")] + [ModelProperty("splash")] public Optional Splash { get; set; } - [JsonProperty("afk_channel_id")] + [ModelProperty("afk_channel_id")] public Optional AfkChannelId { get; set; } - [JsonProperty("owner_id")] + [ModelProperty("owner_id")] public Optional OwnerId { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/ModifyGuildRoleParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyGuildRoleParams.cs index 287e1cafe..538433677 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyGuildRoleParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyGuildRoleParams.cs @@ -1,20 +1,19 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rest { - [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class ModifyGuildRoleParams { - [JsonProperty("name")] + [ModelProperty("name")] public Optional Name { get; set; } - [JsonProperty("permissions")] + [ModelProperty("permissions")] public Optional Permissions { get; set; } - [JsonProperty("color")] + [ModelProperty("color")] public Optional Color { get; set; } - [JsonProperty("hoist")] + [ModelProperty("hoist")] public Optional Hoist { get; set; } - [JsonProperty("mentionable")] + [ModelProperty("mentionable")] public Optional Mentionable { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/ModifyGuildRolesParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyGuildRolesParams.cs index 0e816a260..aa9e7c06d 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyGuildRolesParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyGuildRolesParams.cs @@ -1,14 +1,13 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rest { - [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class ModifyGuildRolesParams : ModifyGuildRoleParams { - [JsonProperty("id")] + [ModelProperty("id")] public ulong Id { get; } - [JsonProperty("position")] + [ModelProperty("position")] public int Position { get; } public ModifyGuildRolesParams(ulong id, int position) diff --git a/src/Discord.Net.Rest/API/Rest/ModifyMessageParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyMessageParams.cs index fdff4de15..f6338c9ef 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyMessageParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyMessageParams.cs @@ -1,14 +1,13 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rest { - [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class ModifyMessageParams { - [JsonProperty("content")] + [ModelProperty("content")] public Optional Content { get; set; } - [JsonProperty("embed")] + [ModelProperty("embed")] public Optional Embed { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/ModifyTextChannelParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyTextChannelParams.cs index 311336ec3..b46b215c6 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyTextChannelParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyTextChannelParams.cs @@ -1,12 +1,11 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rest { - [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class ModifyTextChannelParams : ModifyGuildChannelParams { - [JsonProperty("topic")] + [ModelProperty("topic")] public Optional Topic { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/ModifyVoiceChannelParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyVoiceChannelParams.cs index ce36eb11f..43898d268 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyVoiceChannelParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyVoiceChannelParams.cs @@ -1,14 +1,13 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rest { - [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class ModifyVoiceChannelParams : ModifyGuildChannelParams { - [JsonProperty("bitrate")] + [ModelProperty("bitrate")] public Optional Bitrate { get; set; } - [JsonProperty("user_limit")] + [ModelProperty("user_limit")] public Optional UserLimit { get; set; } } } diff --git a/src/Discord.Net.Rest/Discord.Net.Rest.csproj b/src/Discord.Net.Rest/Discord.Net.Rest.csproj index 439b7bbb1..f5b26e181 100644 --- a/src/Discord.Net.Rest/Discord.Net.Rest.csproj +++ b/src/Discord.Net.Rest/Discord.Net.Rest.csproj @@ -5,8 +5,14 @@ Discord.Rest A core Discord.Net library containing the REST client and models. net45;netstandard1.1;netstandard1.3 + true + + + + + @@ -15,4 +21,7 @@ + + + \ No newline at end of file diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index 5fb5e00ac..52bfb6ddf 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -8,13 +8,12 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; -using System.Globalization; -using System.IO; using System.Linq; using System.Linq.Expressions; using System.Net; using System.Runtime.CompilerServices; using System.Text; +using System.Text.Formatting; using System.Threading; using System.Threading.Tasks; @@ -29,6 +28,7 @@ namespace Discord.API protected readonly SemaphoreSlim _stateLock; protected readonly ScopedSerializer _serializer; + protected readonly ConcurrentQueue _formatters; private readonly RestClientProvider _restClientProvider; protected bool _isDisposed; @@ -53,6 +53,7 @@ namespace Discord.API RequestQueue = new RequestQueue(); _stateLock = new SemaphoreSlim(1, 1); + _formatters = new ConcurrentQueue(); SetBaseUrl(DiscordConfig.APIUrl); } @@ -187,9 +188,14 @@ namespace Discord.API options.BucketId = AuthTokenType == TokenType.User ? ClientBucket.Get(clientBucket).Id : bucketId; options.IsClientBucket = AuthTokenType == TokenType.User; - string json = payload != null ? SerializeJson(payload) : null; - var request = new JsonRestRequest(RestClient, method, endpoint, json, options); - await SendInternalAsync(method, endpoint, request).ConfigureAwait(false); + if (_formatters.TryDequeue(out var data)) + data = new ArrayFormatter(128, SymbolTable.InvariantUtf8); + try + { + var request = new JsonRestRequest(RestClient, method, endpoint, SerializeJson(data, payload), options); + await SendInternalAsync(method, endpoint, request).ConfigureAwait(false); + } + finally { _formatters.Enqueue(data); } } internal Task SendMultipartAsync(string method, Expression> endpointExpr, IReadOnlyDictionary multipartArgs, BucketIds ids, @@ -208,10 +214,10 @@ namespace Discord.API } internal Task SendAsync(string method, Expression> endpointExpr, BucketIds ids, - ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null) where TResponse : class + ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null) where TResponse : class, new() => SendAsync(method, GetEndpoint(endpointExpr), GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucket, options); public async Task SendAsync(string method, string endpoint, - string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) where TResponse : class + string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) where TResponse : class, new() { options = options ?? new RequestOptions(); options.BucketId = AuthTokenType == TokenType.User ? ClientBucket.Get(clientBucket).Id : bucketId; @@ -222,25 +228,30 @@ namespace Discord.API } internal Task SendJsonAsync(string method, Expression> endpointExpr, object payload, BucketIds ids, - ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null) where TResponse : class + ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null) where TResponse : class, new() => SendJsonAsync(method, GetEndpoint(endpointExpr), payload, GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucket, options); public async Task SendJsonAsync(string method, string endpoint, object payload, - string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) where TResponse : class + string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) where TResponse : class, new() { options = options ?? new RequestOptions(); options.BucketId = AuthTokenType == TokenType.User ? ClientBucket.Get(clientBucket).Id : bucketId; options.IsClientBucket = AuthTokenType == TokenType.User; - string json = payload != null ? SerializeJson(payload) : null; - var request = new JsonRestRequest(RestClient, method, endpoint, json, options); - return DeserializeJson(await SendInternalAsync(method, endpoint, request).ConfigureAwait(false)); + if (_formatters.TryDequeue(out var data)) + data = new ArrayFormatter(128, SymbolTable.InvariantUtf8); + try + { + var request = new JsonRestRequest(RestClient, method, endpoint, SerializeJson(data, payload), options); + return DeserializeJson(await SendInternalAsync(method, endpoint, request).ConfigureAwait(false)); + } + finally { _formatters.Enqueue(data); } } internal Task SendMultipartAsync(string method, Expression> endpointExpr, IReadOnlyDictionary multipartArgs, BucketIds ids, - ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null) + ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null) where TResponse : class, new() => SendMultipartAsync(method, GetEndpoint(endpointExpr), multipartArgs, GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucket, options); public async Task SendMultipartAsync(string method, string endpoint, IReadOnlyDictionary multipartArgs, - string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) + string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) where TResponse : class, new() { options = options ?? new RequestOptions(); options.BucketId = AuthTokenType == TokenType.User ? ClientBucket.Get(clientBucket).Id : bucketId; @@ -250,7 +261,7 @@ namespace Discord.API return DeserializeJson(await SendInternalAsync(method, endpoint, request).ConfigureAwait(false)); } - private async Task SendInternalAsync(string method, string endpoint, RestRequest request) + private async Task> SendInternalAsync(string method, string endpoint, RestRequest request) { if (!request.Options.IgnoreState) CheckState(); @@ -258,13 +269,13 @@ namespace Discord.API request.Options.RetryMode = DefaultRetryMode; var stopwatch = Stopwatch.StartNew(); - var responseStream = await RequestQueue.SendAsync(request).ConfigureAwait(false); + var response = await RequestQueue.SendAsync(request).ConfigureAwait(false); stopwatch.Stop(); double milliseconds = ToMilliseconds(stopwatch); await _sentRequestEvent.InvokeAsync(method, endpoint, milliseconds).ConfigureAwait(false); - return responseStream; + return response; } //Auth @@ -309,7 +320,7 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(guildId: guildId); - return await SendAsync>("GET", () => $"guilds/{guildId}/channels", ids, options: options).ConfigureAwait(false); + return await SendAsync>("GET", () => $"guilds/{guildId}/channels", ids, options: options).ConfigureAwait(false); } public async Task CreateGuildChannelAsync(ulong guildId, CreateGuildChannelParams args, RequestOptions options = null) { @@ -452,7 +463,7 @@ namespace Discord.API endpoint = () => $"channels/{channelId}/messages?limit={limit}&{relativeDir}={relativeId}"; else endpoint = () => $"channels/{channelId}/messages?limit={limit}"; - return await SendAsync>("GET", endpoint, ids, options: options).ConfigureAwait(false); + return await SendAsync>("GET", endpoint, ids, options: options).ConfigureAwait(false); } public async Task CreateMessageAsync(ulong channelId, CreateMessageParams args, RequestOptions options = null) { @@ -618,7 +629,7 @@ namespace Discord.API var ids = new BucketIds(channelId: channelId); Expression> endpoint = () => $"channels/{channelId}/messages/{messageId}/reactions/{emoji}"; - return await SendAsync>("GET", endpoint, ids, options: options).ConfigureAwait(false); + return await SendAsync>("GET", endpoint, ids, options: options).ConfigureAwait(false); } public async Task AckMessageAsync(ulong channelId, ulong messageId, RequestOptions options = null) { @@ -685,7 +696,7 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(channelId: channelId); - return await SendAsync>("GET", () => $"channels/{channelId}/pins", ids, options: options).ConfigureAwait(false); + return await SendAsync>("GET", () => $"channels/{channelId}/pins", ids, options: options).ConfigureAwait(false); } //Channel Recipients @@ -789,7 +800,7 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(guildId: guildId); - return await SendAsync>("GET", () => $"guilds/{guildId}/bans", ids, options: options).ConfigureAwait(false); + return await SendAsync>("GET", () => $"guilds/{guildId}/bans", ids, options: options).ConfigureAwait(false); } public async Task CreateGuildBanAsync(ulong guildId, ulong userId, CreateGuildBanParams args, RequestOptions options = null) { @@ -844,7 +855,7 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(guildId: guildId); - return await SendAsync>("GET", () => $"guilds/{guildId}/integrations", ids, options: options).ConfigureAwait(false); + return await SendAsync>("GET", () => $"guilds/{guildId}/integrations", ids, options: options).ConfigureAwait(false); } public async Task CreateGuildIntegrationAsync(ulong guildId, CreateGuildIntegrationParams args, RequestOptions options = null) { @@ -913,7 +924,7 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(guildId: guildId); - return await SendAsync>("GET", () => $"guilds/{guildId}/invites", ids, options: options).ConfigureAwait(false); + return await SendAsync< List>("GET", () => $"guilds/{guildId}/invites", ids, options: options).ConfigureAwait(false); } public async Task> GetChannelInvitesAsync(ulong channelId, RequestOptions options = null) { @@ -921,7 +932,7 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(channelId: channelId); - return await SendAsync>("GET", () => $"channels/{channelId}/invites", ids, options: options).ConfigureAwait(false); + return await SendAsync>("GET", () => $"channels/{channelId}/invites", ids, options: options).ConfigureAwait(false); } public async Task CreateChannelInviteAsync(ulong channelId, CreateChannelInviteParams args, RequestOptions options = null) { @@ -977,7 +988,7 @@ namespace Discord.API var ids = new BucketIds(guildId: guildId); Expression> endpoint = () => $"guilds/{guildId}/members?limit={limit}&after={afterUserId}"; - return await SendAsync>("GET", endpoint, ids, options: options).ConfigureAwait(false); + return await SendAsync< List>("GET", endpoint, ids, options: options).ConfigureAwait(false); } public async Task RemoveGuildMemberAsync(ulong guildId, ulong userId, string reason, RequestOptions options = null) { @@ -1018,7 +1029,7 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(guildId: guildId); - return await SendAsync>("GET", () => $"guilds/{guildId}/roles", ids, options: options).ConfigureAwait(false); + return await SendAsync>("GET", () => $"guilds/{guildId}/roles", ids, options: options).ConfigureAwait(false); } public async Task CreateGuildRoleAsync(ulong guildId, RequestOptions options = null) { @@ -1056,7 +1067,7 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(guildId: guildId); - return await SendJsonAsync>("PATCH", () => $"guilds/{guildId}/roles", args, ids, options: options).ConfigureAwait(false); + return await SendJsonAsync>("PATCH", () => $"guilds/{guildId}/roles", args, ids, options: options).ConfigureAwait(false); } //Users @@ -1081,12 +1092,12 @@ namespace Discord.API public async Task> GetMyConnectionsAsync(RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); - return await SendAsync>("GET", () => "users/@me/connections", new BucketIds(), options: options).ConfigureAwait(false); + return await SendAsync>("GET", () => "users/@me/connections", new BucketIds(), options: options).ConfigureAwait(false); } public async Task> GetMyPrivateChannelsAsync(RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); - return await SendAsync>("GET", () => "users/@me/channels", new BucketIds(), options: options).ConfigureAwait(false); + return await SendAsync>("GET", () => "users/@me/channels", new BucketIds(), options: options).ConfigureAwait(false); } public async Task> GetMyGuildsAsync(GetGuildSummariesParams args, RequestOptions options = null) { @@ -1099,7 +1110,7 @@ namespace Discord.API int limit = args.Limit.GetValueOrDefault(int.MaxValue); ulong afterGuildId = args.AfterGuildId.GetValueOrDefault(0); - return await SendAsync>("GET", () => $"users/@me/guilds?limit={limit}&after={afterGuildId}", new BucketIds(), options: options).ConfigureAwait(false); + return await SendAsync>("GET", () => $"users/@me/guilds?limit={limit}&after={afterGuildId}", new BucketIds(), options: options).ConfigureAwait(false); } public async Task GetMyApplicationAsync(RequestOptions options = null) { @@ -1136,7 +1147,7 @@ namespace Discord.API public async Task> GetVoiceRegionsAsync(RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); - return await SendAsync>("GET", () => "voice/regions", new BucketIds(), options: options).ConfigureAwait(false); + return await SendAsync>("GET", () => "voice/regions", new BucketIds(), options: options).ConfigureAwait(false); } public async Task> GetGuildVoiceRegionsAsync(ulong guildId, RequestOptions options = null) { @@ -1144,7 +1155,7 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(guildId: guildId); - return await SendAsync>("GET", () => $"guilds/{guildId}/regions", ids, options: options).ConfigureAwait(false); + return await SendAsync>("GET", () => $"guilds/{guildId}/regions", ids, options: options).ConfigureAwait(false); } //Helpers @@ -1154,17 +1165,15 @@ namespace Discord.API throw new InvalidOperationException("Client is not logged in."); } protected static double ToMilliseconds(Stopwatch stopwatch) => Math.Round((double)stopwatch.ElapsedTicks / (double)Stopwatch.Frequency * 1000.0, 2); - protected string SerializeJson(object value) + protected ReadOnlyBuffer SerializeJson(ArrayFormatter data, object value) { - var sb = new StringBuilder(256); - using (var writer = new StringWriter(sb, CultureInfo.InvariantCulture)) - _serializer.ToJson(writer, value); - return sb.ToString(); + _serializer.WriteJson(data, value); + return new ReadOnlyBuffer(data.Formatted.Array, 0, data.Formatted.Count); } - protected T DeserializeJson(Stream jsonStream) + protected T DeserializeJson(ReadOnlyBuffer data) + where T : class, new() { - using (var reader = new StreamReader(jsonStream)) - return _serializer.FromJson(reader); + return _serializer.ReadJson(data); } internal class BucketIds diff --git a/src/Discord.Net.Rest/Extensions/JsonReaderExtensions.cs b/src/Discord.Net.Rest/Extensions/JsonReaderExtensions.cs new file mode 100644 index 000000000..1aa7e34a1 --- /dev/null +++ b/src/Discord.Net.Rest/Extensions/JsonReaderExtensions.cs @@ -0,0 +1,132 @@ +using System; +using System.Text; +using System.Text.Json; +using System.Text.Utf8; + +namespace Discord.Serialization +{ + internal static class JsonReaderExtensions + { + public static bool GetBool(this JsonReader reader) => GetBool(reader.Value); + public static bool GetBool(this ReadOnlySpan text) + { + if (PrimitiveParser.TryParseBoolean(text, out bool result, out int ignored, SymbolTable.InvariantUtf8)) + return result; + throw new SerializationException("Failed to parse Boolean"); + } + + public static sbyte GetInt8(this JsonReader reader) => GetInt8(reader.Value); + public static sbyte GetInt8(this ReadOnlySpan text) + { + if (PrimitiveParser.TryParseSByte(text, out sbyte result, out int ignored, JsonConstants.NumberFormat, SymbolTable.InvariantUtf8)) + return result; + throw new SerializationException("Failed to parse Int8"); + } + public static short GetInt16(this JsonReader reader) => GetInt16(reader.Value); + public static short GetInt16(this ReadOnlySpan text) + { + if (PrimitiveParser.TryParseInt16(text, out short result, out int ignored, JsonConstants.NumberFormat, SymbolTable.InvariantUtf8)) + return result; + throw new SerializationException("Failed to parse Int16"); + } + public static int GetInt32(this JsonReader reader) => GetInt32(reader.Value); + public static int GetInt32(this ReadOnlySpan text) + { + if (PrimitiveParser.TryParseInt32(text, out int result, out int ignored, JsonConstants.NumberFormat, SymbolTable.InvariantUtf8)) + return result; + throw new SerializationException("Failed to parse Int32"); + } + public static long GetInt64(this JsonReader reader) => GetInt64(reader.Value); + public static long GetInt64(this ReadOnlySpan text) + { + if (PrimitiveParser.TryParseInt64(text, out long result, out int ignored, JsonConstants.NumberFormat, SymbolTable.InvariantUtf8)) + return result; + throw new SerializationException("Failed to parse Int64"); + } + + public static byte GetUInt8(this JsonReader reader) => GetUInt8(reader.Value); + public static byte GetUInt8(this ReadOnlySpan text) + { + if (PrimitiveParser.TryParseByte(text, out byte result, out int ignored, JsonConstants.NumberFormat, SymbolTable.InvariantUtf8)) + return result; + throw new SerializationException("Failed to parse UInt8"); + } + public static ushort GetUInt16(this JsonReader reader) => GetUInt16(reader.Value); + public static ushort GetUInt16(this ReadOnlySpan text) + { + if (PrimitiveParser.TryParseUInt16(text, out ushort result, out int ignored, JsonConstants.NumberFormat, SymbolTable.InvariantUtf8)) + return result; + throw new SerializationException("Failed to parse UInt16"); + } + public static uint GetUInt32(this JsonReader reader) => GetUInt32(reader.Value); + public static uint GetUInt32(this ReadOnlySpan text) + { + if (PrimitiveParser.TryParseUInt32(text, out uint result, out int ignored, JsonConstants.NumberFormat, SymbolTable.InvariantUtf8)) + return result; + throw new SerializationException("Failed to parse UInt32"); + } + public static ulong GetUInt64(this JsonReader reader) => GetUInt64(reader.Value); + public static ulong GetUInt64(this ReadOnlySpan text) + { + if (PrimitiveParser.TryParseUInt64(text, out ulong result, out int ignored, JsonConstants.NumberFormat, SymbolTable.InvariantUtf8)) + return result; + throw new SerializationException("Failed to parse UInt64"); + } + + public static char GetChar(this JsonReader reader) => GetChar(reader.Value); + public static char GetChar(this ReadOnlySpan text) + { + string str = GetString(text); + if (str.Length == 1) + return str[0]; + throw new SerializationException("Failed to parse Char"); + } + public static string GetString(this JsonReader reader) => GetString(reader.Value); + public static string GetString(this ReadOnlySpan text) => new Utf8String(text).ToString(); + + public static float GetSingle(this JsonReader reader) => GetSingle(reader.Value); + public static float GetSingle(this ReadOnlySpan text) + { + if (PrimitiveParser.TryParseDecimal(text, out decimal result, out int ignored, SymbolTable.InvariantUtf8)) + return (float)result; + throw new SerializationException("Failed to parse Single"); + } + public static double GetDouble(this JsonReader reader) => GetDouble(reader.Value); + public static double GetDouble(this ReadOnlySpan text) + { + if (PrimitiveParser.TryParseDecimal(text, out decimal result, out int ignored, SymbolTable.InvariantUtf8)) + return (double)result; + throw new SerializationException("Failed to parse Double"); + } + public static decimal GetDecimal(this JsonReader reader) => GetDecimal(reader.Value); + public static decimal GetDecimal(this ReadOnlySpan text) + { + if (PrimitiveParser.TryParseDecimal(text, out decimal result, out int ignored, SymbolTable.InvariantUtf8)) + return result; + throw new SerializationException("Failed to parse Decimal"); + } + + public static DateTime GetDateTime(this JsonReader reader) => GetDateTime(reader.Value); + public static DateTime GetDateTime(this ReadOnlySpan text) + { + string str = GetString(text); + if (DateTime.TryParse(str, out var result)) //TODO: Improve perf + return result; + throw new SerializationException("Failed to parse DateTime"); + } + public static DateTimeOffset GetDateTimeOffset(this JsonReader reader) => GetDateTimeOffset(reader.Value); + public static DateTimeOffset GetDateTimeOffset(this ReadOnlySpan text) + { + string str = GetString(text); + if (DateTimeOffset.TryParse(str, out var result)) //TODO: Improve perf + return result; + throw new SerializationException("Failed to parse DateTimeOffset"); + } + + public static void Skip(this JsonReader reader) + { + int initialDepth = reader._depth; + while (reader.Read() && reader._depth > initialDepth) { } + } + } +} diff --git a/src/Discord.Net.Rest/Extensions/StreamExtensions.cs b/src/Discord.Net.Rest/Extensions/StreamExtensions.cs new file mode 100644 index 000000000..fa78d0fef --- /dev/null +++ b/src/Discord.Net.Rest/Extensions/StreamExtensions.cs @@ -0,0 +1,25 @@ +using System; +using System.IO; + +namespace Discord +{ + internal static class StreamExtensions + { +#if MSTRYBUFFER + public static byte[] GetBuffer(this MemoryStream stream) + { + if (stream.TryGetBuffer(out var streamBuffer)) + return streamBuffer.Array; + else + return stream.ToArray(); + } +#elif !MSBUFFER + public static byte[] GetBuffer(this MemoryStream stream) => stream.ToArray(); +#endif + + public static ReadOnlyBuffer ToReadOnlyBuffer(this MemoryStream stream) + => new ReadOnlyBuffer(stream.GetBuffer(), 0, (int)stream.Length); + public static ReadOnlySpan ToSpan(this MemoryStream stream) + => new ReadOnlySpan(stream.GetBuffer(), 0, (int)stream.Length); + } +} diff --git a/src/Discord.Net.Rest/Net/DefaultRestClient.cs b/src/Discord.Net.Rest/Net/DefaultRestClient.cs index d9211722d..7a02f0bc9 100644 --- a/src/Discord.Net.Rest/Net/DefaultRestClient.cs +++ b/src/Discord.Net.Rest/Net/DefaultRestClient.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using System.Net; using System.Net.Http; +using System.Net.Http.Headers; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -68,13 +69,15 @@ namespace Discord.Net.Rest return await SendInternalAsync(restRequest, cancelToken, headerOnly).ConfigureAwait(false); } } - public async Task SendAsync(string method, string endpoint, string json, CancellationToken cancelToken, bool headerOnly, string reason = null) + public async Task SendAsync(string method, string endpoint, ReadOnlyBuffer json, CancellationToken cancelToken, bool headerOnly, string reason = null) { string uri = Path.Combine(_baseUrl, endpoint); using (var restRequest = new HttpRequestMessage(GetMethod(method), uri)) { if (reason != null) restRequest.Headers.Add("X-Audit-Log-Reason", Uri.EscapeDataString(reason)); - restRequest.Content = new StringContent(json, Encoding.UTF8, "application/json"); + var content = new ByteArrayContent(json.ToArray()); + content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + restRequest.Content = content; return await SendInternalAsync(restRequest, cancelToken, headerOnly).ConfigureAwait(false); } } @@ -122,9 +125,9 @@ namespace Discord.Net.Rest HttpResponseMessage response = await _client.SendAsync(request, cancelToken).ConfigureAwait(false); var headers = response.Headers.ToDictionary(x => x.Key, x => x.Value.FirstOrDefault(), StringComparer.OrdinalIgnoreCase); - var stream = !headerOnly ? await response.Content.ReadAsStreamAsync().ConfigureAwait(false) : null; + var data = !headerOnly ? await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false) : null; - return new RestResponse(response.StatusCode, headers, stream); + return new RestResponse(response.StatusCode, headers, new ReadOnlyBuffer(data)); } private static readonly HttpMethod _patch = new HttpMethod("PATCH"); diff --git a/src/Discord.Net.Rest/Net/Queue/RequestQueue.cs b/src/Discord.Net.Rest/Net/Queue/RequestQueue.cs index 943b76359..a79dbad9d 100644 --- a/src/Discord.Net.Rest/Net/Queue/RequestQueue.cs +++ b/src/Discord.Net.Rest/Net/Queue/RequestQueue.cs @@ -63,7 +63,7 @@ namespace Discord.Net.Queue finally { _tokenLock.Release(); } } - public async Task SendAsync(RestRequest request) + public async Task> SendAsync(RestRequest request) { if (request.Options.CancelToken.CanBeCanceled) request.Options.CancelToken = CancellationTokenSource.CreateLinkedTokenSource(_requestCancelToken, request.Options.CancelToken).Token; diff --git a/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs b/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs index 2cc4b8a10..9cd834e32 100644 --- a/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs +++ b/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs @@ -1,5 +1,4 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; +using Discord.Serialization; using System; #if DEBUG_LIMITS using System.Diagnostics; @@ -13,6 +12,16 @@ namespace Discord.Net.Queue { internal class RequestBucket { + private class Error + { + [ModelProperty("code")] + public int Code { get; set; } + [ModelProperty("message")] + public string Message { get; set; } + } + + private static int _nextId = 0; + private readonly object _lock; private readonly RequestQueue _queue; private int _semaphore; @@ -38,10 +47,9 @@ namespace Discord.Net.Queue LastAttemptAt = DateTimeOffset.UtcNow; } - static int nextId = 0; - public async Task SendAsync(RestRequest request) + public async Task> SendAsync(RestRequest request) { - int id = Interlocked.Increment(ref nextId); + int id = Interlocked.Increment(ref _nextId); #if DEBUG_LIMITS Debug.WriteLine($"[{id}] Start"); #endif @@ -54,7 +62,7 @@ namespace Discord.Net.Queue #if DEBUG_LIMITS Debug.WriteLine($"[{id}] Sending..."); #endif - RateLimitInfo info = default(RateLimitInfo); + var info = default(RateLimitInfo); try { var response = await request.SendAsync().ConfigureAwait(false); @@ -92,17 +100,13 @@ namespace Discord.Net.Queue default: int? code = null; string reason = null; - if (response.Stream != null) + if (response.Data.Length > 0) { try { - using (var reader = new StreamReader(response.Stream)) - using (var jsonReader = new JsonTextReader(reader)) - { - var json = JToken.Load(jsonReader); - try { code = json.Value("code"); } catch { }; - try { reason = json.Value("message"); } catch { }; - } + var error = Serializer.ReadJson(response.Data); + code = error.Code; + reason = error.Message; } catch { } } @@ -114,7 +118,7 @@ namespace Discord.Net.Queue #if DEBUG_LIMITS Debug.WriteLine($"[{id}] Success"); #endif - return response.Stream; + return response.Data; } } //catch (HttpException) { throw; } //Pass through @@ -231,7 +235,7 @@ namespace Discord.Net.Queue #endif } - var now = DateTimeUtils.ToUnixSeconds(DateTimeOffset.UtcNow); + long now = DateTimeUtils.ToUnixSeconds(DateTimeOffset.UtcNow); DateTimeOffset? resetTick = null; //Using X-RateLimit-Remaining causes a race condition diff --git a/src/Discord.Net.Rest/Net/Queue/Requests/JsonRestRequest.cs b/src/Discord.Net.Rest/Net/Queue/Requests/JsonRestRequest.cs index 2949bab3c..3c1f37df8 100644 --- a/src/Discord.Net.Rest/Net/Queue/Requests/JsonRestRequest.cs +++ b/src/Discord.Net.Rest/Net/Queue/Requests/JsonRestRequest.cs @@ -1,21 +1,22 @@ using Discord.Net.Rest; +using System; using System.Threading.Tasks; namespace Discord.Net.Queue { public class JsonRestRequest : RestRequest { - public string Json { get; } + public ReadOnlyBuffer Payload { get; } - public JsonRestRequest(IRestClient client, string method, string endpoint, string json, RequestOptions options) + public JsonRestRequest(IRestClient client, string method, string endpoint, ReadOnlyBuffer payload, RequestOptions options) : base(client, method, endpoint, options) { - Json = json; + Payload = payload; } public override async Task SendAsync() { - return await Client.SendAsync(Method, Endpoint, Json, Options.CancelToken, Options.HeaderOnly, Options.AuditLogReason).ConfigureAwait(false); + return await Client.SendAsync(Method, Endpoint, Payload, Options.CancelToken, Options.HeaderOnly, Options.AuditLogReason).ConfigureAwait(false); } } } diff --git a/src/Discord.Net.Rest/Net/Queue/Requests/WebSocketRequest.cs b/src/Discord.Net.Rest/Net/Queue/Requests/WebSocketRequest.cs index 478289b59..3ef216a68 100644 --- a/src/Discord.Net.Rest/Net/Queue/Requests/WebSocketRequest.cs +++ b/src/Discord.Net.Rest/Net/Queue/Requests/WebSocketRequest.cs @@ -10,14 +10,14 @@ namespace Discord.Net.Queue { public IWebSocketClient Client { get; } public string BucketId { get; } - public byte[] Data { get; } + public ReadOnlyBuffer Data { get; } public bool IsText { get; } public DateTimeOffset? TimeoutAt { get; } public TaskCompletionSource Promise { get; } public RequestOptions Options { get; } public CancellationToken CancelToken { get; internal set; } - public WebSocketRequest(IWebSocketClient client, string bucketId, byte[] data, bool isText, RequestOptions options) + public WebSocketRequest(IWebSocketClient client, string bucketId, ReadOnlyBuffer data, bool isText, RequestOptions options) { Preconditions.NotNull(options, nameof(options)); @@ -32,7 +32,7 @@ namespace Discord.Net.Queue public async Task SendAsync() { - await Client.SendAsync(Data, 0, Data.Length, IsText).ConfigureAwait(false); + await Client.SendAsync(Data, IsText).ConfigureAwait(false); } } } diff --git a/src/Discord.Net.Rest/Serialization/Converters/ArrayPropertyConverter.cs b/src/Discord.Net.Rest/Serialization/Converters/ArrayPropertyConverter.cs new file mode 100644 index 000000000..0e0ee5bfb --- /dev/null +++ b/src/Discord.Net.Rest/Serialization/Converters/ArrayPropertyConverter.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using System.Text.Json; + +namespace Discord.Serialization.Converters +{ + internal class ArrayPropertyConverter : IPropertyConverter + { + private readonly IPropertyConverter _innerConverter; + + public ArrayPropertyConverter(IPropertyConverter innerConverter) + { + _innerConverter = innerConverter; + } + + public T[] ReadJson(JsonReader reader, bool read = true) + { + if ((read && !reader.Read()) || reader.TokenType != JsonTokenType.StartArray) + throw new SerializationException("Bad input, expected StartArray"); + + var list = new List(); + while (reader.Read() && reader.TokenType != JsonTokenType.EndArray) + list.Add(_innerConverter.ReadJson(reader)); + return list.ToArray(); + } + + public void WriteJson(JsonWriter writer, T[] value) + { + writer.WriteArrayStart(); + for (int i = 0; i < value.Length; i++) + _innerConverter.WriteJson(writer, value[i]); + writer.WriteArrayEnd(); + } + } +} diff --git a/src/Discord.Net.Rest/Serialization/Converters/Converter.cs b/src/Discord.Net.Rest/Serialization/Converters/Converter.cs new file mode 100644 index 000000000..9a15ad3a3 --- /dev/null +++ b/src/Discord.Net.Rest/Serialization/Converters/Converter.cs @@ -0,0 +1,103 @@ +using Discord.API; +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace Discord.Serialization.Converters +{ + internal static class Converter + { + private static readonly MethodInfo _makeListConverterFunc + = typeof(Converter).GetTypeInfo().GetDeclaredMethod(nameof(MakeListConverterInternal)); + private static readonly MethodInfo _makeOptionalConverterFunc + = typeof(Converter).GetTypeInfo().GetDeclaredMethod(nameof(MakeOptionalConverterInternal)); + private static readonly MethodInfo _makeNullableConverterFunc + = typeof(Converter).GetTypeInfo().GetDeclaredMethod(nameof(MakeNullableConverterInternal)); + private static readonly MethodInfo _makeEntityOrIdConverterFunc + = typeof(Converter).GetTypeInfo().GetDeclaredMethod(nameof(MakeEntityOrIdConverterInternal)); + + public static IPropertyConverter For() + => (IPropertyConverter)ForInternal(); + private static object ForInternal() + { + var typeInfo = typeof(TProp).GetTypeInfo(); + + //Generics + if (typeof(TProp).IsConstructedGenericType) + { + Type genericType = typeof(TProp).GetGenericTypeDefinition(); + if (genericType == typeof(List<>)) + return MakeListConverter(typeof(TProp).GenericTypeArguments[0]); + else if (genericType == typeof(Optional<>)) + return MakeOptionalConverter(typeof(TProp).GenericTypeArguments[0]); + else if (genericType == typeof(Nullable<>)) + return MakeNullableConverter(typeof(TProp).GenericTypeArguments[0]); + else if (genericType == typeof(EntityOrId<>)) + return MakeEntityOrIdConverter(typeof(TProp).GenericTypeArguments[0]); + } + + //Enums + if (typeInfo.IsEnum) return new EnumPropertyConverter(); + + //Primitives + if (typeof(TProp) == typeof(bool)) return new BooleanPropertyConverter(); + + if (typeof(TProp) == typeof(sbyte)) return new Int8PropertyConverter(); + if (typeof(TProp) == typeof(short)) return new Int16PropertyConverter(); + if (typeof(TProp) == typeof(int)) return new Int32PropertyConverter(); + if (typeof(TProp) == typeof(long)) + { + if (typeInfo.GetCustomAttribute() != null) + return new Int53PropertyConverter(); + else + return new Int64PropertyConverter(); + } + + if (typeof(TProp) == typeof(byte)) return new UInt8PropertyConverter(); + if (typeof(TProp) == typeof(ushort)) return new UInt16PropertyConverter(); + if (typeof(TProp) == typeof(uint)) return new UInt32PropertyConverter(); + if (typeof(TProp) == typeof(ulong)) + { + if (typeInfo.GetCustomAttribute() != null) + return new UInt53PropertyConverter(); + else + return new UInt64PropertyConverter(); + } + + if (typeof(TProp) == typeof(float)) return new SinglePropertyConverter(); + if (typeof(TProp) == typeof(double)) return new DoublePropertyConverter(); + if (typeof(TProp) == typeof(decimal)) return new DecimalPropertyConverter(); + + if (typeof(TProp) == typeof(char)) return new CharPropertyConverter(); + if (typeof(TProp) == typeof(string)) return new StringPropertyConverter(); + + //Structs + if (typeof(TProp) == typeof(DateTime)) return new DateTimePropertyConverter(); + if (typeof(TProp) == typeof(DateTimeOffset)) return new DateTimeOffsetPropertyConverter(); + if (typeof(TProp) == typeof(Image)) return new ImagePropertyConverter(); + + throw new InvalidOperationException($"Unsupported model type: {typeof(TProp).Name}"); + } + + private static IPropertyConverter MakeListConverter(Type innerType) + => _makeListConverterFunc.MakeGenericMethod(innerType).Invoke(null, null) as IPropertyConverter; + private static IPropertyConverter> MakeListConverterInternal() + => new ListPropertyConverter(For()); + + private static IPropertyConverter MakeOptionalConverter(Type innerType) + => _makeOptionalConverterFunc.MakeGenericMethod(innerType).Invoke(null, null) as IPropertyConverter; + private static IPropertyConverter> MakeOptionalConverterInternal() + => new OptionalPropertyConverter(For()); + + private static IPropertyConverter MakeNullableConverter(Type innerType) + => _makeNullableConverterFunc.MakeGenericMethod(innerType).Invoke(null, null) as IPropertyConverter; + private static IPropertyConverter MakeNullableConverterInternal() + where TInnerProp : struct + => new NullablePropertyConverter(For()); + + private static IPropertyConverter MakeEntityOrIdConverter(Type innerType) + => _makeEntityOrIdConverterFunc.MakeGenericMethod(innerType).Invoke(null, null) as IPropertyConverter; + private static IPropertyConverter> MakeEntityOrIdConverterInternal() + => new EntityOrIdPropertyConverter(For()); + } +} diff --git a/src/Discord.Net.Rest/Serialization/Converters/EntityOrIdPropertyConverter.cs b/src/Discord.Net.Rest/Serialization/Converters/EntityOrIdPropertyConverter.cs new file mode 100644 index 000000000..6af9f98eb --- /dev/null +++ b/src/Discord.Net.Rest/Serialization/Converters/EntityOrIdPropertyConverter.cs @@ -0,0 +1,32 @@ +using Discord.API; +using System.Text.Json; + +namespace Discord.Serialization.Converters +{ + internal class EntityOrIdPropertyConverter : IPropertyConverter> + { + private readonly IPropertyConverter _innerConverter; + + public EntityOrIdPropertyConverter(IPropertyConverter innerConverter) + { + _innerConverter = innerConverter; + } + + public EntityOrId ReadJson(JsonReader reader, bool read = true) + { + if (read) + reader.Read(); + if (reader.ValueType == JsonValueType.Number) + return new EntityOrId(reader.GetUInt64()); + return new EntityOrId(_innerConverter.ReadJson(reader)); + } + + public void WriteJson(JsonWriter writer, EntityOrId value) + { + if (value.Object != null) + _innerConverter.WriteJson(writer, value.Object); + else + writer.WriteValue(value.Id); + } + } +} diff --git a/src/Discord.Net.Rest/Serialization/Converters/EnumPropertyConverter.cs b/src/Discord.Net.Rest/Serialization/Converters/EnumPropertyConverter.cs new file mode 100644 index 000000000..3b6f94dcb --- /dev/null +++ b/src/Discord.Net.Rest/Serialization/Converters/EnumPropertyConverter.cs @@ -0,0 +1,12 @@ +using System.Text.Json; + +namespace Discord.Serialization.Converters +{ + internal class EnumPropertyConverter : IPropertyConverter + { + public T ReadJson(JsonReader reader, bool read = true) + => throw new System.NotImplementedException(); + public void WriteJson(JsonWriter writer, T value) + => throw new System.NotImplementedException(); + } +} diff --git a/src/Discord.Net.Rest/Serialization/Converters/IPropertyConverter.cs b/src/Discord.Net.Rest/Serialization/Converters/IPropertyConverter.cs new file mode 100644 index 000000000..bd6d5d552 --- /dev/null +++ b/src/Discord.Net.Rest/Serialization/Converters/IPropertyConverter.cs @@ -0,0 +1,10 @@ +using System.Text.Json; + +namespace Discord.Serialization.Converters +{ + internal interface IPropertyConverter + { + T ReadJson(JsonReader reader, bool read = true); + void WriteJson(JsonWriter writer, T value); + } +} diff --git a/src/Discord.Net.Rest/Serialization/Converters/ImagePropertyConverter.cs b/src/Discord.Net.Rest/Serialization/Converters/ImagePropertyConverter.cs new file mode 100644 index 000000000..f94a68cd4 --- /dev/null +++ b/src/Discord.Net.Rest/Serialization/Converters/ImagePropertyConverter.cs @@ -0,0 +1,12 @@ +using System.Text.Json; + +namespace Discord.Serialization.Converters +{ + internal class ImagePropertyConverter : IPropertyConverter + { + public Image ReadJson(JsonReader reader, bool read = true) + => throw new System.NotImplementedException(); + public void WriteJson(JsonWriter writer, Image value) + => throw new System.NotImplementedException(); + } +} diff --git a/src/Discord.Net.Rest/Serialization/Converters/ListPropertyConverter.cs b/src/Discord.Net.Rest/Serialization/Converters/ListPropertyConverter.cs new file mode 100644 index 000000000..b1627bc5c --- /dev/null +++ b/src/Discord.Net.Rest/Serialization/Converters/ListPropertyConverter.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using System.Text.Json; + +namespace Discord.Serialization.Converters +{ + internal class ListPropertyConverter : IPropertyConverter> + { + private readonly IPropertyConverter _innerConverter; + + public ListPropertyConverter(IPropertyConverter innerConverter) + { + _innerConverter = innerConverter; + } + + public List ReadJson(JsonReader reader, bool read = true) + { + if ((read && !reader.Read()) || reader.TokenType != JsonTokenType.StartArray) + throw new SerializationException("Bad input, expected StartArray"); + + var list = new List(); + while (reader.Read() && reader.TokenType != JsonTokenType.EndArray) + list.Add(_innerConverter.ReadJson(reader)); + return list; + } + + public void WriteJson(JsonWriter writer, List value) + { + writer.WriteArrayStart(); + for (int i = 0; i < value.Count; i++) + _innerConverter.WriteJson(writer, value[i]); + writer.WriteArrayEnd(); + } + } +} diff --git a/src/Discord.Net.Rest/Serialization/Converters/NullablePropertyConverter.cs b/src/Discord.Net.Rest/Serialization/Converters/NullablePropertyConverter.cs new file mode 100644 index 000000000..9be9b984e --- /dev/null +++ b/src/Discord.Net.Rest/Serialization/Converters/NullablePropertyConverter.cs @@ -0,0 +1,32 @@ +using System.Text.Json; + +namespace Discord.Serialization.Converters +{ + internal class NullablePropertyConverter : IPropertyConverter + where T : struct + { + private readonly IPropertyConverter _innerConverter; + + public NullablePropertyConverter(IPropertyConverter innerConverter) + { + _innerConverter = innerConverter; + } + + public T? ReadJson(JsonReader reader, bool read = true) + { + if (read) + reader.Read(); + if (reader.ValueType == JsonValueType.Null) + return null; + return _innerConverter.ReadJson(reader); + } + + public void WriteJson(JsonWriter writer, T? value) + { + if (value.HasValue) + _innerConverter.WriteJson(writer, value.Value); + else + writer.WriteNull(); + } + } +} diff --git a/src/Discord.Net.Rest/Serialization/Converters/OptionalPropertyConverter.cs b/src/Discord.Net.Rest/Serialization/Converters/OptionalPropertyConverter.cs new file mode 100644 index 000000000..608fd5144 --- /dev/null +++ b/src/Discord.Net.Rest/Serialization/Converters/OptionalPropertyConverter.cs @@ -0,0 +1,23 @@ +using System.Text.Json; + +namespace Discord.Serialization.Converters +{ + internal class OptionalPropertyConverter : IPropertyConverter> + { + private readonly IPropertyConverter _innerConverter; + + public OptionalPropertyConverter(IPropertyConverter innerConverter) + { + _innerConverter = innerConverter; + } + + public Optional ReadJson(JsonReader reader, bool read = true) + => new Optional(_innerConverter.ReadJson(reader, read)); + + public void WriteJson(JsonWriter writer, Optional value) + { + if (value.IsSpecified) + _innerConverter.WriteJson(writer, value.Value); + } + } +} diff --git a/src/Discord.Net.Rest/Serialization/Converters/Primitives/BooleanPropertyConverter.cs b/src/Discord.Net.Rest/Serialization/Converters/Primitives/BooleanPropertyConverter.cs new file mode 100644 index 000000000..e55acd113 --- /dev/null +++ b/src/Discord.Net.Rest/Serialization/Converters/Primitives/BooleanPropertyConverter.cs @@ -0,0 +1,21 @@ +using System.Text.Json; + +namespace Discord.Serialization.Converters +{ + internal class BooleanPropertyConverter : IPropertyConverter + { + public bool ReadJson(JsonReader reader, bool read = true) + { + if (read) + reader.Read(); + switch (reader.ValueType) + { + case JsonValueType.True: return true; + case JsonValueType.False: return false; + default: throw new SerializationException("Bad input, expected False or True"); + } + } + public void WriteJson(JsonWriter writer, bool value) + => writer.WriteValue(value); + } +} diff --git a/src/Discord.Net.Rest/Serialization/Converters/Primitives/CharPropertyConverter.cs b/src/Discord.Net.Rest/Serialization/Converters/Primitives/CharPropertyConverter.cs new file mode 100644 index 000000000..23aaa88aa --- /dev/null +++ b/src/Discord.Net.Rest/Serialization/Converters/Primitives/CharPropertyConverter.cs @@ -0,0 +1,18 @@ +using System.Text.Json; + +namespace Discord.Serialization.Converters +{ + internal class CharPropertyConverter : IPropertyConverter + { + public char ReadJson(JsonReader reader, bool read = true) + { + if (read) + reader.Read(); + if (reader.ValueType != JsonValueType.String) + throw new SerializationException("Bad input, expected String"); + return reader.GetChar(); + } + public void WriteJson(JsonWriter writer, char value) + => writer.WriteValue(value); + } +} diff --git a/src/Discord.Net.Rest/Serialization/Converters/Primitives/DateTimeOffsetPropertyConverter.cs b/src/Discord.Net.Rest/Serialization/Converters/Primitives/DateTimeOffsetPropertyConverter.cs new file mode 100644 index 000000000..e484a9196 --- /dev/null +++ b/src/Discord.Net.Rest/Serialization/Converters/Primitives/DateTimeOffsetPropertyConverter.cs @@ -0,0 +1,19 @@ +using System; +using System.Text.Json; + +namespace Discord.Serialization.Converters +{ + internal class DateTimeOffsetPropertyConverter : IPropertyConverter + { + public DateTimeOffset ReadJson(JsonReader reader, bool read = true) + { + if (read) + reader.Read(); + if (reader.ValueType != JsonValueType.String) + throw new SerializationException("Bad input, expected String"); + return reader.GetDateTimeOffset(); + } + public void WriteJson(JsonWriter writer, DateTimeOffset value) + => writer.WriteValue(value); + } +} diff --git a/src/Discord.Net.Rest/Serialization/Converters/Primitives/DateTimePropertyConverter.cs b/src/Discord.Net.Rest/Serialization/Converters/Primitives/DateTimePropertyConverter.cs new file mode 100644 index 000000000..7ddb39084 --- /dev/null +++ b/src/Discord.Net.Rest/Serialization/Converters/Primitives/DateTimePropertyConverter.cs @@ -0,0 +1,19 @@ +using System; +using System.Text.Json; + +namespace Discord.Serialization.Converters +{ + internal class DateTimePropertyConverter : IPropertyConverter + { + public DateTime ReadJson(JsonReader reader, bool read = true) + { + if (read) + reader.Read(); + if (reader.ValueType != JsonValueType.String) + throw new SerializationException("Bad input, expected String"); + return reader.GetDateTime(); + } + public void WriteJson(JsonWriter writer, DateTime value) + => writer.WriteValue(value); + } +} diff --git a/src/Discord.Net.Rest/Serialization/Converters/Primitives/DecimalPropertyConverter.cs b/src/Discord.Net.Rest/Serialization/Converters/Primitives/DecimalPropertyConverter.cs new file mode 100644 index 000000000..786d12ebe --- /dev/null +++ b/src/Discord.Net.Rest/Serialization/Converters/Primitives/DecimalPropertyConverter.cs @@ -0,0 +1,18 @@ +using System.Text.Json; + +namespace Discord.Serialization.Converters +{ + internal class DecimalPropertyConverter : IPropertyConverter + { + public decimal ReadJson(JsonReader reader, bool read = true) + { + if (read) + reader.Read(); + if (reader.ValueType != JsonValueType.Number) + throw new SerializationException("Bad input, expected Number"); + return reader.GetDecimal(); + } + public void WriteJson(JsonWriter writer, decimal value) + => writer.WriteValue(value.ToString()); + } +} diff --git a/src/Discord.Net.Rest/Serialization/Converters/Primitives/DoublePropertyConverter.cs b/src/Discord.Net.Rest/Serialization/Converters/Primitives/DoublePropertyConverter.cs new file mode 100644 index 000000000..87590464d --- /dev/null +++ b/src/Discord.Net.Rest/Serialization/Converters/Primitives/DoublePropertyConverter.cs @@ -0,0 +1,18 @@ +using System.Text.Json; + +namespace Discord.Serialization.Converters +{ + internal class DoublePropertyConverter : IPropertyConverter + { + public double ReadJson(JsonReader reader, bool read = true) + { + if (read) + reader.Read(); + if (reader.ValueType != JsonValueType.Number) + throw new SerializationException("Bad input, expected Number"); + return reader.GetDouble(); + } + public void WriteJson(JsonWriter writer, double value) + => writer.WriteValue(value.ToString()); + } +} diff --git a/src/Discord.Net.Rest/Serialization/Converters/Primitives/Int16PropertyConverter.cs b/src/Discord.Net.Rest/Serialization/Converters/Primitives/Int16PropertyConverter.cs new file mode 100644 index 000000000..a80137878 --- /dev/null +++ b/src/Discord.Net.Rest/Serialization/Converters/Primitives/Int16PropertyConverter.cs @@ -0,0 +1,18 @@ +using System.Text.Json; + +namespace Discord.Serialization.Converters +{ + internal class Int16PropertyConverter : IPropertyConverter + { + public short ReadJson(JsonReader reader, bool read = true) + { + if (read) + reader.Read(); + if (reader.ValueType != JsonValueType.Number) + throw new SerializationException("Bad input, expected Number"); + return reader.GetInt16(); + } + public void WriteJson(JsonWriter writer, short value) + => writer.WriteValue(value); + } +} diff --git a/src/Discord.Net.Rest/Serialization/Converters/Primitives/Int32PropertyConverter.cs b/src/Discord.Net.Rest/Serialization/Converters/Primitives/Int32PropertyConverter.cs new file mode 100644 index 000000000..af189753d --- /dev/null +++ b/src/Discord.Net.Rest/Serialization/Converters/Primitives/Int32PropertyConverter.cs @@ -0,0 +1,18 @@ +using System.Text.Json; + +namespace Discord.Serialization.Converters +{ + internal class Int32PropertyConverter : IPropertyConverter + { + public int ReadJson(JsonReader reader, bool read = true) + { + if (read) + reader.Read(); + if (reader.ValueType != JsonValueType.Number) + throw new SerializationException("Bad input, expected Number"); + return reader.GetInt32(); + } + public void WriteJson(JsonWriter writer, int value) + => writer.WriteValue(value); + } +} diff --git a/src/Discord.Net.Rest/Serialization/Converters/Primitives/Int53PropertyConverter.cs b/src/Discord.Net.Rest/Serialization/Converters/Primitives/Int53PropertyConverter.cs new file mode 100644 index 000000000..d8bbd64fc --- /dev/null +++ b/src/Discord.Net.Rest/Serialization/Converters/Primitives/Int53PropertyConverter.cs @@ -0,0 +1,18 @@ +using System.Text.Json; + +namespace Discord.Serialization.Converters +{ + internal class Int53PropertyConverter : IPropertyConverter + { + public long ReadJson(JsonReader reader, bool read = true) + { + if (read) + reader.Read(); + if (reader.ValueType != JsonValueType.Number) + throw new SerializationException("Bad input, expected Number"); + return reader.GetInt64(); + } + public void WriteJson(JsonWriter writer, long value) + => writer.WriteValue(value); + } +} diff --git a/src/Discord.Net.Rest/Serialization/Converters/Primitives/Int64PropertyConverter.cs b/src/Discord.Net.Rest/Serialization/Converters/Primitives/Int64PropertyConverter.cs new file mode 100644 index 000000000..ad6dec00a --- /dev/null +++ b/src/Discord.Net.Rest/Serialization/Converters/Primitives/Int64PropertyConverter.cs @@ -0,0 +1,18 @@ +using System.Text.Json; + +namespace Discord.Serialization.Converters +{ + internal class Int64PropertyConverter : IPropertyConverter + { + public long ReadJson(JsonReader reader, bool read = true) + { + if (read) + reader.Read(); + if (reader.ValueType != JsonValueType.String) + throw new SerializationException("Bad input, expected String"); + return reader.GetInt64(); + } + public void WriteJson(JsonWriter writer, long value) + => writer.WriteValue(value.ToString()); + } +} diff --git a/src/Discord.Net.Rest/Serialization/Converters/Primitives/Int8PropertyConverter.cs b/src/Discord.Net.Rest/Serialization/Converters/Primitives/Int8PropertyConverter.cs new file mode 100644 index 000000000..de725d531 --- /dev/null +++ b/src/Discord.Net.Rest/Serialization/Converters/Primitives/Int8PropertyConverter.cs @@ -0,0 +1,18 @@ +using System.Text.Json; + +namespace Discord.Serialization.Converters +{ + internal class Int8PropertyConverter : IPropertyConverter + { + public sbyte ReadJson(JsonReader reader, bool read = true) + { + if (read) + reader.Read(); + if (reader.ValueType != JsonValueType.Number) + throw new SerializationException("Bad input, expected Number"); + return reader.GetInt8(); + } + public void WriteJson(JsonWriter writer, sbyte value) + => writer.WriteValue(value); + } +} diff --git a/src/Discord.Net.Rest/Serialization/Converters/Primitives/SinglePropertyConverter.cs b/src/Discord.Net.Rest/Serialization/Converters/Primitives/SinglePropertyConverter.cs new file mode 100644 index 000000000..222ae8683 --- /dev/null +++ b/src/Discord.Net.Rest/Serialization/Converters/Primitives/SinglePropertyConverter.cs @@ -0,0 +1,18 @@ +using System.Text.Json; + +namespace Discord.Serialization.Converters +{ + internal class SinglePropertyConverter : IPropertyConverter + { + public float ReadJson(JsonReader reader, bool read = true) + { + if (read) + reader.Read(); + if (reader.ValueType != JsonValueType.Number) + throw new SerializationException("Bad input, expected Number"); + return reader.GetSingle(); + } + public void WriteJson(JsonWriter writer, float value) + => writer.WriteValue(value.ToString()); + } +} diff --git a/src/Discord.Net.Rest/Serialization/Converters/Primitives/StringPropertyConverter.cs b/src/Discord.Net.Rest/Serialization/Converters/Primitives/StringPropertyConverter.cs new file mode 100644 index 000000000..328adb885 --- /dev/null +++ b/src/Discord.Net.Rest/Serialization/Converters/Primitives/StringPropertyConverter.cs @@ -0,0 +1,18 @@ +using System.Text.Json; + +namespace Discord.Serialization.Converters +{ + internal class StringPropertyConverter : IPropertyConverter + { + public string ReadJson(JsonReader reader, bool read = true) + { + if (read) + reader.Read(); + if (reader.ValueType != JsonValueType.String) + throw new SerializationException("Bad input, expected String"); + return reader.GetString(); + } + public void WriteJson(JsonWriter writer, string value) + => writer.WriteValue(value); + } +} diff --git a/src/Discord.Net.Rest/Serialization/Converters/Primitives/UInt16PropertyConverter.cs b/src/Discord.Net.Rest/Serialization/Converters/Primitives/UInt16PropertyConverter.cs new file mode 100644 index 000000000..2bb0bee85 --- /dev/null +++ b/src/Discord.Net.Rest/Serialization/Converters/Primitives/UInt16PropertyConverter.cs @@ -0,0 +1,18 @@ +using System.Text.Json; + +namespace Discord.Serialization.Converters +{ + internal class UInt16PropertyConverter : IPropertyConverter + { + public ushort ReadJson(JsonReader reader, bool read = true) + { + if (read) + reader.Read(); + if (reader.ValueType != JsonValueType.Number) + throw new SerializationException("Bad input, expected Number"); + return reader.GetUInt16(); + } + public void WriteJson(JsonWriter writer, ushort value) + => writer.WriteValue(value); + } +} diff --git a/src/Discord.Net.Rest/Serialization/Converters/Primitives/UInt32PropertyConverter.cs b/src/Discord.Net.Rest/Serialization/Converters/Primitives/UInt32PropertyConverter.cs new file mode 100644 index 000000000..a55f23804 --- /dev/null +++ b/src/Discord.Net.Rest/Serialization/Converters/Primitives/UInt32PropertyConverter.cs @@ -0,0 +1,18 @@ +using System.Text.Json; + +namespace Discord.Serialization.Converters +{ + internal class UInt32PropertyConverter : IPropertyConverter + { + public uint ReadJson(JsonReader reader, bool read = true) + { + if (read) + reader.Read(); + if (reader.ValueType != JsonValueType.Number) + throw new SerializationException("Bad input, expected Number"); + return reader.GetUInt32(); + } + public void WriteJson(JsonWriter writer, uint value) + => writer.WriteValue(value); + } +} diff --git a/src/Discord.Net.Rest/Serialization/Converters/Primitives/UInt53PropertyConverter.cs b/src/Discord.Net.Rest/Serialization/Converters/Primitives/UInt53PropertyConverter.cs new file mode 100644 index 000000000..49502ce5e --- /dev/null +++ b/src/Discord.Net.Rest/Serialization/Converters/Primitives/UInt53PropertyConverter.cs @@ -0,0 +1,18 @@ +using System.Text.Json; + +namespace Discord.Serialization.Converters +{ + internal class UInt53PropertyConverter : IPropertyConverter + { + public ulong ReadJson(JsonReader reader, bool read = true) + { + if (read) + reader.Read(); + if (reader.ValueType != JsonValueType.Number) + throw new SerializationException("Bad input, expected Number"); + return reader.GetUInt64(); + } + public void WriteJson(JsonWriter writer, ulong value) + => writer.WriteValue(value); + } +} diff --git a/src/Discord.Net.Rest/Serialization/Converters/Primitives/UInt64PropertyConverter.cs b/src/Discord.Net.Rest/Serialization/Converters/Primitives/UInt64PropertyConverter.cs new file mode 100644 index 000000000..07eec7ea3 --- /dev/null +++ b/src/Discord.Net.Rest/Serialization/Converters/Primitives/UInt64PropertyConverter.cs @@ -0,0 +1,18 @@ +using System.Text.Json; + +namespace Discord.Serialization.Converters +{ + internal class UInt64PropertyConverter : IPropertyConverter + { + public ulong ReadJson(JsonReader reader, bool read = true) + { + if (read) + reader.Read(); + if (reader.ValueType != JsonValueType.String) + throw new SerializationException("Bad input, expected String"); + return reader.GetUInt64(); + } + public void WriteJson(JsonWriter writer, ulong value) + => writer.WriteValue(value.ToString()); + } +} diff --git a/src/Discord.Net.Rest/Serialization/Converters/Primitives/UInt8PropertyConverter.cs b/src/Discord.Net.Rest/Serialization/Converters/Primitives/UInt8PropertyConverter.cs new file mode 100644 index 000000000..af62d8c05 --- /dev/null +++ b/src/Discord.Net.Rest/Serialization/Converters/Primitives/UInt8PropertyConverter.cs @@ -0,0 +1,18 @@ +using System.Text.Json; + +namespace Discord.Serialization.Converters +{ + internal class UInt8PropertyConverter : IPropertyConverter + { + public byte ReadJson(JsonReader reader, bool read = true) + { + if (read) + reader.Read(); + if (reader.ValueType != JsonValueType.Number) + throw new SerializationException("Bad input, expected Number"); + return reader.GetUInt8(); + } + public void WriteJson(JsonWriter writer, byte value) + => writer.WriteValue(value); + } +} diff --git a/src/Discord.Net.Rest/API/Int53Attribute.cs b/src/Discord.Net.Rest/Serialization/Int53Attribute.cs similarity index 59% rename from src/Discord.Net.Rest/API/Int53Attribute.cs rename to src/Discord.Net.Rest/Serialization/Int53Attribute.cs index 70ef2f185..caed9b540 100644 --- a/src/Discord.Net.Rest/API/Int53Attribute.cs +++ b/src/Discord.Net.Rest/Serialization/Int53Attribute.cs @@ -1,7 +1,6 @@ -#pragma warning disable CS1591 -using System; +using System; -namespace Discord.API +namespace Discord.Serialization { [AttributeUsage(AttributeTargets.Property)] internal class Int53Attribute : Attribute { } diff --git a/src/Discord.Net.Rest/Serialization/JsonConverters/ArrayConverter.cs b/src/Discord.Net.Rest/Serialization/JsonConverters/ArrayConverter.cs deleted file mode 100644 index a6b09eb51..000000000 --- a/src/Discord.Net.Rest/Serialization/JsonConverters/ArrayConverter.cs +++ /dev/null @@ -1,59 +0,0 @@ -using Newtonsoft.Json; -using System; -using System.Collections.Generic; - -namespace Discord.Serialization.JsonConverters -{ - internal class ArrayConverter : JsonConverter - { - private readonly JsonConverter _innerConverter; - - public override bool CanConvert(Type objectType) => true; - public override bool CanRead => true; - public override bool CanWrite => true; - - public ArrayConverter(JsonConverter innerConverter) - { - _innerConverter = innerConverter; - } - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - var result = new List(); - if (reader.TokenType == JsonToken.StartArray) - { - reader.Read(); - while (reader.TokenType != JsonToken.EndArray) - { - T obj; - if (_innerConverter != null) - obj = (T)_innerConverter.ReadJson(reader, typeof(T), null, serializer); - else - obj = serializer.Deserialize(reader); - result.Add(obj); - reader.Read(); - } - } - return result.ToArray(); - } - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - if (value != null) - { - writer.WriteStartArray(); - var a = (T[])value; - for (int i = 0; i < a.Length; i++) - { - if (_innerConverter != null) - _innerConverter.WriteJson(writer, a[i], serializer); - else - serializer.Serialize(writer, a[i], typeof(T)); - } - - writer.WriteEndArray(); - } - else - writer.WriteNull(); - } - } -} diff --git a/src/Discord.Net.Rest/Serialization/JsonConverters/DiscordContractResolver.cs b/src/Discord.Net.Rest/Serialization/JsonConverters/DiscordContractResolver.cs deleted file mode 100644 index 4345f37e1..000000000 --- a/src/Discord.Net.Rest/Serialization/JsonConverters/DiscordContractResolver.cs +++ /dev/null @@ -1,102 +0,0 @@ -using Discord.API; -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; - -namespace Discord.Serialization.JsonConverters -{ - internal class DiscordContractResolver : DefaultContractResolver - { - private static readonly TypeInfo _ienumerable = typeof(IEnumerable).GetTypeInfo(); - private static readonly MethodInfo _shouldSerialize = typeof(DiscordContractResolver).GetTypeInfo().GetDeclaredMethod("ShouldSerialize"); - - protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) - { - var property = base.CreateProperty(member, memberSerialization); - if (property.Ignored) - return property; - - if (member is PropertyInfo propInfo) - { - var converter = GetConverter(property, propInfo, propInfo.PropertyType, 0); - if (converter != null) - { - property.Converter = converter; - property.MemberConverter = converter; - } - } - else - throw new InvalidOperationException($"{member.DeclaringType.FullName}.{member.Name} is not a property."); - return property; - } - - private static JsonConverter GetConverter(JsonProperty property, PropertyInfo propInfo, Type type, int depth) - { - if (type.IsArray) - return MakeGenericConverter(property, propInfo, typeof(ArrayConverter<>), type.GetElementType(), depth); - if (type.IsConstructedGenericType) - { - Type genericType = type.GetGenericTypeDefinition(); - if (depth == 0 && genericType == typeof(Optional<>)) - { - var typeInput = propInfo.DeclaringType; - var innerTypeOutput = type.GenericTypeArguments[0]; - - var getter = typeof(Func<,>).MakeGenericType(typeInput, type); - var getterDelegate = propInfo.GetMethod.CreateDelegate(getter); - var shouldSerialize = _shouldSerialize.MakeGenericMethod(typeInput, innerTypeOutput); - var shouldSerializeDelegate = (Func)shouldSerialize.CreateDelegate(typeof(Func)); - property.ShouldSerialize = x => shouldSerializeDelegate(x, getterDelegate); - - return MakeGenericConverter(property, propInfo, typeof(OptionalConverter<>), innerTypeOutput, depth); - } - else if (genericType == typeof(Nullable<>)) - return MakeGenericConverter(property, propInfo, typeof(NullableConverter<>), type.GenericTypeArguments[0], depth); - else if (genericType == typeof(EntityOrId<>)) - return MakeGenericConverter(property, propInfo, typeof(UInt64EntityOrIdConverter<>), type.GenericTypeArguments[0], depth); - } - - //Primitives - bool hasInt53 = propInfo.GetCustomAttribute() != null; - if (!hasInt53) - { - if (type == typeof(ulong)) - return UInt64Converter.Instance; - } - - //Enums - if (type == typeof(PermissionTarget)) - return PermissionTargetConverter.Instance; - if (type == typeof(UserStatus)) - return UserStatusConverter.Instance; - - //Special - if (type == typeof(API.Image)) - return ImageConverter.Instance; - - //Entities - var typeInfo = type.GetTypeInfo(); - if (typeInfo.ImplementedInterfaces.Any(x => x == typeof(IEntity))) - return UInt64EntityConverter.Instance; - if (typeInfo.ImplementedInterfaces.Any(x => x == typeof(IEntity))) - return StringEntityConverter.Instance; - - return null; - } - - private static bool ShouldSerialize(object owner, Delegate getter) - { - return (getter as Func>)((TOwner)owner).IsSpecified; - } - - private static JsonConverter MakeGenericConverter(JsonProperty property, PropertyInfo propInfo, Type converterType, Type innerType, int depth) - { - var genericType = converterType.MakeGenericType(innerType).GetTypeInfo(); - var innerConverter = GetConverter(property, propInfo, innerType, depth + 1); - return genericType.DeclaredConstructors.First().Invoke(new object[] { innerConverter }) as JsonConverter; - } - } -} diff --git a/src/Discord.Net.Rest/Serialization/JsonConverters/ImageConverter.cs b/src/Discord.Net.Rest/Serialization/JsonConverters/ImageConverter.cs deleted file mode 100644 index aa3b1c167..000000000 --- a/src/Discord.Net.Rest/Serialization/JsonConverters/ImageConverter.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Newtonsoft.Json; -using System; -using Model = Discord.API.Image; - -namespace Discord.Serialization.JsonConverters -{ - internal class ImageConverter : JsonConverter - { - public static readonly ImageConverter Instance = new ImageConverter(); - - public override bool CanConvert(Type objectType) => true; - public override bool CanRead => true; - public override bool CanWrite => true; - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - throw new InvalidOperationException(); - } - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - var image = (Model)value; - - if (image.Stream != null) - { - byte[] bytes = new byte[image.Stream.Length - image.Stream.Position]; - image.Stream.Read(bytes, 0, bytes.Length); - - string base64 = Convert.ToBase64String(bytes); - writer.WriteValue($"data:image/jpeg;base64,{base64}"); - } - else if (image.Hash != null) - writer.WriteValue(image.Hash); - } - } -} diff --git a/src/Discord.Net.Rest/Serialization/JsonConverters/NullableConverter.cs b/src/Discord.Net.Rest/Serialization/JsonConverters/NullableConverter.cs deleted file mode 100644 index 4bab750fa..000000000 --- a/src/Discord.Net.Rest/Serialization/JsonConverters/NullableConverter.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Newtonsoft.Json; -using System; - -namespace Discord.Serialization.JsonConverters -{ - internal class NullableConverter : JsonConverter - where T : struct - { - private readonly JsonConverter _innerConverter; - - public override bool CanConvert(Type objectType) => true; - public override bool CanRead => true; - public override bool CanWrite => true; - - public NullableConverter(JsonConverter innerConverter) - { - _innerConverter = innerConverter; - } - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - object value = reader.Value; - if (value == null) - return null; - else - { - T obj; - if (_innerConverter != null) - obj = (T)_innerConverter.ReadJson(reader, typeof(T), null, serializer); - else - obj = serializer.Deserialize(reader); - return obj; - } - } - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - if (value == null) - writer.WriteNull(); - else - { - var nullable = (T?)value; - if (_innerConverter != null) - _innerConverter.WriteJson(writer, nullable.Value, serializer); - else - serializer.Serialize(writer, nullable.Value, typeof(T)); - } - } - } -} diff --git a/src/Discord.Net.Rest/Serialization/JsonConverters/OptionalConverter.cs b/src/Discord.Net.Rest/Serialization/JsonConverters/OptionalConverter.cs deleted file mode 100644 index b3b75331f..000000000 --- a/src/Discord.Net.Rest/Serialization/JsonConverters/OptionalConverter.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Newtonsoft.Json; -using System; - -namespace Discord.Serialization.JsonConverters -{ - internal class OptionalConverter : JsonConverter - { - private readonly JsonConverter _innerConverter; - - public override bool CanConvert(Type objectType) => true; - public override bool CanRead => true; - public override bool CanWrite => true; - - public OptionalConverter(JsonConverter innerConverter) - { - _innerConverter = innerConverter; - } - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - T obj; - if (_innerConverter != null) - obj = (T)_innerConverter.ReadJson(reader, typeof(T), null, serializer); - else - obj = serializer.Deserialize(reader); - return new Optional(obj); - } - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - value = ((Optional)value).Value; - if (_innerConverter != null) - _innerConverter.WriteJson(writer, value, serializer); - else - serializer.Serialize(writer, value, typeof(T)); - } - } -} diff --git a/src/Discord.Net.Rest/Serialization/JsonConverters/PermissionTargetConverter.cs b/src/Discord.Net.Rest/Serialization/JsonConverters/PermissionTargetConverter.cs deleted file mode 100644 index 4f01436a8..000000000 --- a/src/Discord.Net.Rest/Serialization/JsonConverters/PermissionTargetConverter.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Newtonsoft.Json; -using System; - -namespace Discord.Serialization.JsonConverters -{ - internal class PermissionTargetConverter : JsonConverter - { - public static readonly PermissionTargetConverter Instance = new PermissionTargetConverter(); - - public override bool CanConvert(Type objectType) => true; - public override bool CanRead => true; - public override bool CanWrite => true; - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - switch ((string)reader.Value) - { - case "member": - return PermissionTarget.User; - case "role": - return PermissionTarget.Role; - default: - throw new JsonSerializationException("Unknown permission target"); - } - } - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - switch ((PermissionTarget)value) - { - case PermissionTarget.User: - writer.WriteValue("member"); - break; - case PermissionTarget.Role: - writer.WriteValue("role"); - break; - default: - throw new JsonSerializationException("Invalid permission target"); - } - } - } -} diff --git a/src/Discord.Net.Rest/Serialization/JsonConverters/StringEntityConverter.cs b/src/Discord.Net.Rest/Serialization/JsonConverters/StringEntityConverter.cs deleted file mode 100644 index fe7bd9af5..000000000 --- a/src/Discord.Net.Rest/Serialization/JsonConverters/StringEntityConverter.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Newtonsoft.Json; -using System; - -namespace Discord.Serialization.JsonConverters -{ - internal class StringEntityConverter : JsonConverter - { - public static readonly StringEntityConverter Instance = new StringEntityConverter(); - - public override bool CanConvert(Type objectType) => true; - public override bool CanRead => false; - public override bool CanWrite => true; - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - throw new InvalidOperationException(); - } - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - if (value != null) - writer.WriteValue((value as IEntity).Id); - else - writer.WriteNull(); - } - } -} diff --git a/src/Discord.Net.Rest/Serialization/JsonConverters/UInt64Converter.cs b/src/Discord.Net.Rest/Serialization/JsonConverters/UInt64Converter.cs deleted file mode 100644 index 2b5b1724a..000000000 --- a/src/Discord.Net.Rest/Serialization/JsonConverters/UInt64Converter.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Newtonsoft.Json; -using System; -using System.Globalization; - -namespace Discord.Serialization.JsonConverters -{ - internal class UInt64Converter : JsonConverter - { - public static readonly UInt64Converter Instance = new UInt64Converter(); - - public override bool CanConvert(Type objectType) => true; - public override bool CanRead => true; - public override bool CanWrite => true; - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - return ulong.Parse((string)reader.Value, NumberStyles.None, CultureInfo.InvariantCulture); - } - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - writer.WriteValue(((ulong)value).ToString(CultureInfo.InvariantCulture)); - } - } -} diff --git a/src/Discord.Net.Rest/Serialization/JsonConverters/UInt64EntityConverter.cs b/src/Discord.Net.Rest/Serialization/JsonConverters/UInt64EntityConverter.cs deleted file mode 100644 index 6726d1344..000000000 --- a/src/Discord.Net.Rest/Serialization/JsonConverters/UInt64EntityConverter.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Newtonsoft.Json; -using System; -using System.Globalization; - -namespace Discord.Serialization.JsonConverters -{ - internal class UInt64EntityConverter : JsonConverter - { - public static readonly UInt64EntityConverter Instance = new UInt64EntityConverter(); - - public override bool CanConvert(Type objectType) => true; - public override bool CanRead => false; - public override bool CanWrite => true; - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - throw new InvalidOperationException(); - } - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - if (value != null) - writer.WriteValue((value as IEntity).Id.ToString(CultureInfo.InvariantCulture)); - else - writer.WriteNull(); - } - } -} diff --git a/src/Discord.Net.Rest/Serialization/JsonConverters/UInt64EntityOrIdConverter.cs b/src/Discord.Net.Rest/Serialization/JsonConverters/UInt64EntityOrIdConverter.cs deleted file mode 100644 index fb599e80f..000000000 --- a/src/Discord.Net.Rest/Serialization/JsonConverters/UInt64EntityOrIdConverter.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Discord.API; -using Newtonsoft.Json; -using System; - -namespace Discord.Serialization.JsonConverters -{ - internal class UInt64EntityOrIdConverter : JsonConverter - { - private readonly JsonConverter _innerConverter; - - public override bool CanConvert(Type objectType) => true; - public override bool CanRead => true; - public override bool CanWrite => false; - - public UInt64EntityOrIdConverter(JsonConverter innerConverter) - { - _innerConverter = innerConverter; - } - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - switch (reader.TokenType) - { - case JsonToken.String: - case JsonToken.Integer: - return new EntityOrId(ulong.Parse(reader.ReadAsString())); - default: - T obj; - if (_innerConverter != null) - obj = (T)_innerConverter.ReadJson(reader, typeof(T), null, serializer); - else - obj = serializer.Deserialize(reader); - return new EntityOrId(obj); - } - } - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - throw new InvalidOperationException(); - } - } -} diff --git a/src/Discord.Net.Rest/Serialization/JsonConverters/UserStatusConverter.cs b/src/Discord.Net.Rest/Serialization/JsonConverters/UserStatusConverter.cs deleted file mode 100644 index 77ecf53ba..000000000 --- a/src/Discord.Net.Rest/Serialization/JsonConverters/UserStatusConverter.cs +++ /dev/null @@ -1,58 +0,0 @@ -using Newtonsoft.Json; -using System; - -namespace Discord.Serialization.JsonConverters -{ - internal class UserStatusConverter : JsonConverter - { - public static readonly UserStatusConverter Instance = new UserStatusConverter(); - - public override bool CanConvert(Type objectType) => true; - public override bool CanRead => true; - public override bool CanWrite => true; - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - switch ((string)reader.Value) - { - case "online": - return UserStatus.Online; - case "idle": - return UserStatus.Idle; - case "dnd": - return UserStatus.DoNotDisturb; - case "invisible": - return UserStatus.Invisible; //Should never happen - case "offline": - return UserStatus.Offline; - default: - throw new JsonSerializationException("Unknown user status"); - } - } - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - switch ((UserStatus)value) - { - case UserStatus.Online: - writer.WriteValue("online"); - break; - case UserStatus.Idle: - case UserStatus.AFK: - writer.WriteValue("idle"); - break; - case UserStatus.DoNotDisturb: - writer.WriteValue("dnd"); - break; - case UserStatus.Invisible: - writer.WriteValue("invisible"); - break; - case UserStatus.Offline: - writer.WriteValue("offline"); - break; - default: - throw new JsonSerializationException("Invalid user status"); - } - } - } -} diff --git a/src/Discord.Net.Rest/Serialization/ModelMap.cs b/src/Discord.Net.Rest/Serialization/ModelMap.cs new file mode 100644 index 000000000..737a470a7 --- /dev/null +++ b/src/Discord.Net.Rest/Serialization/ModelMap.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text.Json; + +namespace Discord.Serialization +{ + internal static class ModelMap + { + private static readonly ConcurrentDictionary _cache = new ConcurrentDictionary(); + + internal static ModelMap For() + where T : class, new() + { + return _cache.GetOrAdd(typeof(T), _ => + { + var type = typeof(T).GetTypeInfo(); + var properties = new Dictionary>(); + + var propInfos = type.DeclaredProperties.ToArray(); + for (int i = 0; i < propInfos.Length; i++) + { + var propInfo = propInfos[i]; + if (!propInfo.CanRead || !propInfo.CanWrite) + continue; + + var propMap = PropertyMap.Create(propInfo); + properties.Add(propMap.Key, propMap); + } + + return new ModelMap(properties); + }) as ModelMap; + } + } + + internal class ModelMap + where T : class, new() + { + private readonly PropertyMap[] _propertyList; + private readonly Dictionary> _properties; + + public ModelMap(Dictionary> properties) + { + _properties = properties; + _propertyList = _properties.Values.ToArray(); + } + + public T ReadJson(JsonReader reader) + { + var model = new T(); + + if (!reader.Read() || reader.TokenType != JsonTokenType.StartObject) + throw new InvalidOperationException("Bad input, expected StartObject"); + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + return model; + if (reader.TokenType != JsonTokenType.PropertyName) + throw new InvalidOperationException("Bad input, expected PropertyName"); + + string key = reader.GetString(); + if (_properties.TryGetValue(key, out var property)) + property.ReadJson(model, reader); + else + reader.Skip(); //Unknown property, skip + + if (!reader.Read()) + throw new InvalidOperationException("Bad input, expected Value"); + } + throw new InvalidOperationException("Bad input, expected EndObject"); + } + public void WriteJson(T model, JsonWriter writer) + { + writer.WriteObjectStart(); + for (int i = 0; i < _propertyList.Length; i++) + { + var property = _propertyList[i]; + writer.WriteStartAttribute(property.Key); + property.WriteJson(model, writer); + } + writer.WriteObjectEnd(); + } + } +} diff --git a/src/Discord.Net.Rest/Serialization/ModelPropertyAttribute.cs b/src/Discord.Net.Rest/Serialization/ModelPropertyAttribute.cs new file mode 100644 index 000000000..e69da541a --- /dev/null +++ b/src/Discord.Net.Rest/Serialization/ModelPropertyAttribute.cs @@ -0,0 +1,15 @@ +using System; + +namespace Discord.Serialization +{ + internal class ModelPropertyAttribute : Attribute + { + public string Key { get; } + public bool IgnoreNull { get; set; } + + public ModelPropertyAttribute(string key) + { + Key = key; + } + } +} diff --git a/src/Discord.Net.Rest/Serialization/PropertyMap.cs b/src/Discord.Net.Rest/Serialization/PropertyMap.cs new file mode 100644 index 000000000..0ac7057a2 --- /dev/null +++ b/src/Discord.Net.Rest/Serialization/PropertyMap.cs @@ -0,0 +1,61 @@ +using Discord.Serialization.Converters; +using System; +using System.Reflection; +using System.Text.Json; + +namespace Discord.Serialization +{ + internal static class PropertyMap + { + public static PropertyMap Create(PropertyInfo propInfo) + { + var type = typeof(PropertyMap<,>).MakeGenericType(typeof(TModel), propInfo.PropertyType); + return Activator.CreateInstance(type, propInfo) as PropertyMap; + } + } + + internal abstract class PropertyMap + { + public string Key { get; protected set; } + + public abstract void WriteJson(TModel model, JsonWriter writer); + public abstract void ReadJson(TModel model, JsonReader reader); + } + + internal class PropertyMap : PropertyMap + { + private readonly IPropertyConverter _converter; + private readonly Func _getFunc; + private readonly Action _setFunc; + + public PropertyMap(PropertyInfo propInfo) + { + var jsonProperty = propInfo.GetCustomAttribute(); + if (jsonProperty != null) + Key = jsonProperty.Key; + else + Key = propInfo.Name; + + _getFunc = propInfo.GetMethod.CreateDelegate(typeof(Func)) as Func; + _setFunc = propInfo.SetMethod.CreateDelegate(typeof(Action)) as Action; + + _converter = Converter.For(); + } + + private TProp GetValue(TModel model) + => _getFunc(model); + private void SetValue(TModel model, TProp prop) + => _setFunc(model, prop); + + public override void WriteJson(TModel model, JsonWriter writer) + { + var value = GetValue(model); + _converter.WriteJson(writer, value); + } + public override void ReadJson(TModel model, JsonReader reader) + { + var value = _converter.ReadJson(reader); + SetValue(model, value); + } + } +} diff --git a/src/Discord.Net.Rest/Serialization/SerializationException.cs b/src/Discord.Net.Rest/Serialization/SerializationException.cs new file mode 100644 index 000000000..66297de9e --- /dev/null +++ b/src/Discord.Net.Rest/Serialization/SerializationException.cs @@ -0,0 +1,16 @@ +using System; + +namespace Discord.Serialization +{ + internal class SerializationException : Exception + { + public SerializationException() + : base("Serialization failed") + { + } + public SerializationException(string message) + : base(message) + { + } + } +} diff --git a/src/Discord.Net.Rest/Serialization/Serializer.cs b/src/Discord.Net.Rest/Serialization/Serializer.cs index bbcce8bc3..d205bb84d 100644 --- a/src/Discord.Net.Rest/Serialization/Serializer.cs +++ b/src/Discord.Net.Rest/Serialization/Serializer.cs @@ -1,9 +1,7 @@ -using Discord.Serialization.JsonConverters; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using System; -using System.IO; +using System; using System.Text; +using System.Text.Formatting; +using System.Text.Json; using System.Threading.Tasks; namespace Discord.Serialization @@ -12,22 +10,14 @@ namespace Discord.Serialization { public static ScopedSerializer Global { get; } = new ScopedSerializer(); - public static T FromJson(Stream stream) => Global.FromJson(stream); - public static T FromJson(StreamReader reader) => Global.FromJson(reader); - public static T FromJson(JsonTextReader reader) => Global.FromJson(reader); - public static T FromJson(JToken token) => Global.FromJson(token); - - public static void ToJson(Stream stream, T obj) => Global.ToJson(stream, obj); - public static void ToJson(StreamWriter writer, T obj) => Global.ToJson(writer, obj); - public static void ToJson(JsonTextWriter writer, T obj) => Global.ToJson(writer, obj); + public static T ReadJson(ReadOnlyBuffer data) where T : class, new() => Global.ReadJson(data); + public static void WriteJson(ArrayFormatter data, T obj) where T : class, new() => Global.WriteJson(data, obj); public static ScopedSerializer CreateScope() => new ScopedSerializer(); } internal class ScopedSerializer { - private readonly JsonSerializer _serializer; - private readonly AsyncEvent> _errorEvent = new AsyncEvent>(); public event Func Error { @@ -35,46 +25,11 @@ namespace Discord.Serialization remove { _errorEvent.Remove(value); } } - internal ScopedSerializer() - { - _serializer = new JsonSerializer - { - DateFormatString = "yyyy-MM-ddTHH:mm:ssZ", - ContractResolver = new DiscordContractResolver() - }; - _serializer.Error += (s, e) => - { - _errorEvent.InvokeAsync(e.ErrorContext.Error).GetAwaiter().GetResult(); - e.ErrorContext.Handled = true; - }; - } - - public T FromJson(Stream stream) - { - using (var reader = new StreamReader(stream, Encoding.UTF8, false, 1024, true)) //1KB buffer - return FromJson(reader); - } - public T FromJson(TextReader reader) - { - using (var jsonReader = new JsonTextReader(reader) { CloseInput = false }) - return FromJson(jsonReader); - } - public T FromJson(JsonTextReader reader) - => _serializer.Deserialize(reader); - public T FromJson(JToken token) - => token.ToObject(_serializer); - - public void ToJson(Stream stream, T obj) - { - using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, false)) //1KB buffer - ToJson(writer, obj); - } - public void ToJson(TextWriter writer, T obj) - { - using (var jsonWriter = new JsonTextWriter(writer) { CloseOutput = false }) - ToJson(jsonWriter, obj); - } - public void ToJson(JsonTextWriter writer, T obj) - => _serializer.Serialize(writer, obj); + public T ReadJson(ReadOnlyBuffer data) + where T : class, new() + => ModelMap.For().ReadJson(new JsonReader(data.Span, SymbolTable.InvariantUtf8)); + public void WriteJson(ArrayFormatter data, T obj) + where T : class, new() + => ModelMap.For().WriteJson(obj, new JsonWriter(data)); } } diff --git a/src/Discord.Net.Rest/_corefxlab/System.Text.Json/System/Text/Json/JsonConstants.cs b/src/Discord.Net.Rest/_corefxlab/System.Text.Json/System/Text/Json/JsonConstants.cs new file mode 100644 index 000000000..604f781e9 --- /dev/null +++ b/src/Discord.Net.Rest/_corefxlab/System.Text.Json/System/Text/Json/JsonConstants.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace System.Text.Json +{ + static class JsonConstants + { + #region Formatting constants + + public static readonly ParsedFormat NumberFormat = new ParsedFormat('D'); + public static readonly ParsedFormat DateTimeFormat = new ParsedFormat('O'); + public static readonly ParsedFormat GuidFormat = new ParsedFormat('D'); + + #endregion Formatting constants + + #region Control characters + + public const byte OpenBrace = (byte)'{'; + public const byte CloseBrace = (byte)'}'; + public const byte OpenBracket = (byte)'['; + public const byte CloseBracket = (byte)']'; + public const byte Space = (byte)' '; + public const byte CarriageReturn = (byte)'\r'; + public const byte LineFeed = (byte)'\n'; + public const byte ListSeperator = (byte)','; + public const byte KeyValueSeperator = (byte)':'; + public const byte Quote = (byte)'"'; + + #endregion Control characters + + #region Common values + + public static readonly byte[] TrueValue = { (byte)'t', (byte)'r', (byte)'u', (byte)'e' }; + public static readonly byte[] FalseValue = { (byte)'f', (byte)'a', (byte)'l', (byte)'s', (byte)'e' }; + public static readonly byte[] NullValue = { (byte)'n', (byte)'u', (byte)'l', (byte)'l' }; + + #endregion Common values + } +} diff --git a/src/Discord.Net.Rest/_corefxlab/System.Text.Json/System/Text/Json/JsonEncoderState.cs b/src/Discord.Net.Rest/_corefxlab/System.Text.Json/System/Text/Json/JsonEncoderState.cs new file mode 100644 index 000000000..541afa291 --- /dev/null +++ b/src/Discord.Net.Rest/_corefxlab/System.Text.Json/System/Text/Json/JsonEncoderState.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace System.Text.Json +{ + enum JsonEncoderState + { + UseFastUtf8 = 0, + UseFastUtf16 = 1, + UseFullEncoder = 2, + } +} diff --git a/src/Discord.Net.Rest/_corefxlab/System.Text.Json/System/Text/Json/JsonParseObject.cs b/src/Discord.Net.Rest/_corefxlab/System.Text.Json/System/Text/Json/JsonParseObject.cs new file mode 100644 index 000000000..82b926a11 --- /dev/null +++ b/src/Discord.Net.Rest/_corefxlab/System.Text.Json/System/Text/Json/JsonParseObject.cs @@ -0,0 +1,364 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Binary; +using System.Buffers; +using System.Collections.Generic; +using System.Text.Utf8; + +namespace System.Text.Json +{ + public struct JsonObject + { + private BufferPool _pool; + private OwnedBuffer _dbMemory; + private ReadOnlySpan _db; + private ReadOnlySpan _values; + + public static JsonObject Parse(ReadOnlySpan utf8Json) + { + var parser = new JsonParser(); + var result = parser.Parse(utf8Json); + return result; + } + + public static JsonObject Parse(ReadOnlySpan utf8Json, BufferPool pool = null) + { + var parser = new JsonParser(); + var result = parser.Parse(utf8Json, pool); + return result; + } + + internal JsonObject(ReadOnlySpan values, ReadOnlySpan db, BufferPool pool = null, OwnedBuffer dbMemory = null) + { + _db = db; + _values = values; + _pool = pool; + _dbMemory = dbMemory; + } + + public bool TryGetValue(Utf8String propertyName, out JsonObject value) + { + var record = Record; + + if (record.Length == 0) { + throw new KeyNotFoundException(); + } + if (record.Type != JsonValueType.Object) { + throw new InvalidOperationException(); + } + + for (int i = DbRow.Size; i <= _db.Length; i += DbRow.Size) { + record = _db.Slice(i).Read(); + + if (!record.IsSimpleValue) { + i += record.Length * DbRow.Size; + continue; + } + + if (new Utf8String(_values.Slice(record.Location, record.Length)) == propertyName) { + int newStart = i + DbRow.Size; + int newEnd = newStart + DbRow.Size; + + record = _db.Slice(newStart).Read(); + + if (!record.IsSimpleValue) { + newEnd = newEnd + DbRow.Size * record.Length; + } + + value = new JsonObject(_values, _db.Slice(newStart, newEnd - newStart)); + return true; + } + + var valueType = _db.Slice(i + DbRow.Size + 8).Read(); + if (valueType != JsonValueType.Object && valueType != JsonValueType.Array) { + i += DbRow.Size; + } + } + + value = default; + return false; + } + + public bool TryGetValue(string propertyName, out JsonObject value) + { + var record = Record; + + if (record.Length == 0) { + throw new KeyNotFoundException(); + } + + if (record.Type != JsonValueType.Object) { + throw new InvalidOperationException(); + } + + for (int i = DbRow.Size; i <= _db.Length; i += DbRow.Size) { + record = _db.Slice(i).Read(); + + if (!record.IsSimpleValue) { + i += record.Length * DbRow.Size; + continue; + } + + if (new Utf8String(_values.Slice(record.Location, record.Length)) == propertyName) { + int newStart = i + DbRow.Size; + int newEnd = newStart + DbRow.Size; + + record = _db.Slice(newStart).Read(); + + if (!record.IsSimpleValue) { + newEnd = newEnd + DbRow.Size * record.Length; + } + + value = new JsonObject(_values, _db.Slice(newStart, newEnd - newStart)); + return true; + } + + var valueType = _db.Slice(i + DbRow.Size + 8).Read(); + if (valueType != JsonValueType.Object && valueType != JsonValueType.Array) { + i += DbRow.Size; + } + } + + value = default; + return false; + } + + public JsonObject this[Utf8String name] + { + get { + JsonObject value; + if(TryGetValue(name, out value)) { + return value; + } + throw new KeyNotFoundException(); + } + } + + public JsonObject this[string name] { + get { + JsonObject value; + if (TryGetValue(name, out value)) { + return value; + } + throw new KeyNotFoundException(); + } + } + + public JsonObject this[int index] { + get { + var record = Record; + + if (index < 0 || index >= record.Length) { + throw new IndexOutOfRangeException(); + } + + if (record.Type != JsonValueType.Array) { + throw new InvalidOperationException(); + } + + int counter = 0; + for (int i = DbRow.Size; i <= _db.Length; i += DbRow.Size) { + record = _db.Slice(i).Read(); + + if (index == counter) { + int newStart = i; + int newEnd = i + DbRow.Size; + + if (!record.IsSimpleValue) { + newEnd = newEnd + DbRow.Size * record.Length; + } + return new JsonObject(_values, _db.Slice(newStart, newEnd - newStart)); + } + + if (!record.IsSimpleValue) { + i += record.Length * DbRow.Size; + } + + counter++; + } + + throw new IndexOutOfRangeException(); + } + } + + public int ArrayLength + { + get + { + var record = Record; + if (record.Type != JsonValueType.Array) + { + throw new InvalidOperationException(); + } + return record.Length; + } + } + + public static explicit operator string(JsonObject json) + { + var utf8 = (Utf8String)json; + return utf8.ToString(); + } + + public static explicit operator Utf8String(JsonObject json) + { + var record = json.Record; + if (!record.IsSimpleValue) { + throw new InvalidCastException(); + } + + return new Utf8String(json._values.Slice(record.Location, record.Length)); + } + + public static explicit operator bool(JsonObject json) + { + var record = json.Record; + if (!record.IsSimpleValue) { + throw new InvalidCastException(); + } + + if (record.Length < 4 || record.Length > 5) { + throw new InvalidCastException(); + } + + var slice = json._values.Slice(record.Location); + + bool result; + if(!PrimitiveParser.InvariantUtf8.TryParseBoolean(slice, out result)){ + throw new InvalidCastException(); + } + return result; + } + + public static explicit operator int(JsonObject json) + { + var record = json.Record; + if (!record.IsSimpleValue) { + throw new InvalidCastException(); + } + + var slice = json._values.Slice(record.Location); + + int result; + if (!PrimitiveParser.InvariantUtf8.TryParseInt32(slice, out result)) { + throw new InvalidCastException(); + } + return result; + } + + public static explicit operator double(JsonObject json) + { + var record = json.Record; + if (!record.IsSimpleValue) { + throw new InvalidCastException(); + } + + int count = record.Location; + bool isNegative = false; + var nextByte = json._values[count]; + if (nextByte == '-') { + isNegative = true; + count++; + nextByte = json._values[count]; + } + + if (nextByte < '0' || nextByte > '9' || count - record.Location >= record.Length) { + throw new InvalidCastException(); + } + + int integerPart = 0; + while (nextByte >= '0' && nextByte <= '9' && count - record.Location < record.Length) { + int digit = nextByte - '0'; + integerPart = integerPart * 10 + digit; + count++; + nextByte = json._values[count]; + } + + double result = integerPart; + + int decimalPart = 0; + if (nextByte == '.') { + count++; + int numberOfDigits = count; + nextByte = json._values[count]; + while (nextByte >= '0' && nextByte <= '9' && count - record.Location < record.Length) { + int digit = nextByte - '0'; + decimalPart = decimalPart * 10 + digit; + count++; + nextByte = json._values[count]; + } + numberOfDigits = count - numberOfDigits; + double divisor = Math.Pow(10, numberOfDigits); + result += decimalPart / divisor; + } + + int exponentPart = 0; + bool isExpNegative = false; + if (nextByte == 'e' || nextByte == 'E') { + count++; + nextByte = json._values[count]; + if (nextByte == '-' || nextByte == '+') { + if (nextByte == '-') { + isExpNegative = true; + } + count++; + } + nextByte = json._values[count]; + while (nextByte >= '0' && nextByte <= '9' && count - record.Location < record.Location) { + int digit = nextByte - '0'; + exponentPart = exponentPart * 10 + digit; + count++; + nextByte = json._values[count]; + } + + result *= (Math.Pow(10, isExpNegative ? exponentPart * -1 : exponentPart)); + } + + if (count - record.Location > record.Length) { + throw new InvalidCastException(); + } + + return isNegative ? result * -1 : result; + + } + + internal DbRow Record => _db.Read(); + public JsonValueType Type => _db.Slice(8).Read(); + + public enum JsonValueType : byte + { + String = 0, + Number = 1, + Object = 2, + Array = 3, + True = 4, + False = 5, + Null = 6 + } + + public bool HasValue() + { + var record = Record; + + if (!record.IsSimpleValue) { + if (record.Length == 0) return false; + return true; + } else { + if (_values[record.Location - 1] == '"' && _values[record.Location + 4] == '"') { + return true; + } + return (_values[record.Location] != 'n' || _values[record.Location + 1] != 'u' || _values[record.Location + 2] != 'l' || _values[record.Location + 3] != 'l'); + } + } + + public void Dispose() + { + if (_pool == null) throw new InvalidOperationException("only root object can (and should) be disposed."); + _db = ReadOnlySpan.Empty; + _values = ReadOnlySpan.Empty; + _dbMemory.Dispose(); + _dbMemory = null; + } + } +} diff --git a/src/Discord.Net.Rest/_corefxlab/System.Text.Json/System/Text/Json/JsonParser.cs b/src/Discord.Net.Rest/_corefxlab/System.Text.Json/System/Text/Json/JsonParser.cs new file mode 100644 index 000000000..191c781c8 --- /dev/null +++ b/src/Discord.Net.Rest/_corefxlab/System.Text.Json/System/Text/Json/JsonParser.cs @@ -0,0 +1,523 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Binary; +using System.Buffers; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text.Utf8; + +namespace System.Text.Json +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + internal struct DbRow + { + public static int Size; + + public int Location; // index in JSON payload + public int Length; // length of text in JSON payload + public JsonObject.JsonValueType Type; // type of JSON construct (e.g. Object, Array, Number) + + public const int UnknownNumberOfRows = -1; + + public DbRow(JsonObject.JsonValueType type, int valueIndex, int lengthOrNumberOfRows = UnknownNumberOfRows) + { + Location = valueIndex; + Length = lengthOrNumberOfRows; + Type = type; + } + + public bool IsSimpleValue => Type != JsonObject.JsonValueType.Object && Type != JsonObject.JsonValueType.Array; + + static DbRow() + { + unsafe { Size = sizeof(DbRow); } + } + } + + internal struct TwoStacks + { + Buffer _memory; + int topOfStackObj; + int topOfStackArr; + int capacity; + int objectStackCount; + int arrayStackCount; + + public int ObjectStackCount => objectStackCount; + + public int ArrayStackCount => arrayStackCount; + + public bool IsFull { + get { + return objectStackCount >= capacity || arrayStackCount >= capacity; + } + } + + public TwoStacks(Buffer db) + { + _memory = db; + topOfStackObj = _memory.Length; + topOfStackArr = _memory.Length; + objectStackCount = 0; + arrayStackCount = 0; + capacity = _memory.Length/8; + } + + public bool TryPushObject(int value) + { + if (!IsFull) { + _memory.Slice(topOfStackObj - 8).Span.Write(value); + topOfStackObj -= 8; + objectStackCount++; + return true; + } + return false; + } + + public bool TryPushArray(int value) + { + if (!IsFull) { + _memory.Slice(topOfStackArr - 4).Span.Write(value); + topOfStackArr -= 8; + arrayStackCount++; + return true; + } + return false; + } + + public int PopObject() + { + objectStackCount--; + var value = _memory.Slice(topOfStackObj).Span.Read(); + topOfStackObj += 8; + return value; + } + + public int PopArray() + { + arrayStackCount--; + var value = _memory.Slice(topOfStackArr + 4).Span.Read(); + topOfStackArr += 8; + return value; + } + + internal void Resize(Buffer newStackMemory) + { + _memory.Slice(0, Math.Max(objectStackCount, arrayStackCount) * 8).Span.CopyTo(newStackMemory.Span); + _memory = newStackMemory; + } + } + + internal struct JsonParser + { + private Buffer _db; + private ReadOnlySpan _values; // TODO: this should be ReadOnlyMemory + private Buffer _scratchMemory; + private OwnedBuffer _scratchManager; + BufferPool _pool; + TwoStacks _stack; + + private int _valuesIndex; + private int _dbIndex; + + private int _insideObject; + private int _insideArray; + private JsonTokenType _tokenType; + private bool _jsonStartIsObject; + + private enum JsonTokenType + { + // Start = 0 state reserved for internal use + ObjectStart = 1, + ObjectEnd = 2, + ArrayStart = 3, + ArrayEnd = 4, + Property = 5, + Value = 6 + }; + + private static readonly byte[] s_false = new Utf8String("false").Bytes.ToArray(); + private static readonly byte[] s_true = new Utf8String("true").Bytes.ToArray(); + private static readonly byte[] s_null = new Utf8String("null").Bytes.ToArray(); + + public JsonObject Parse(ReadOnlySpan utf8Json, BufferPool pool = null) + { + _pool = pool; + if (_pool == null) _pool = BufferPool.Default; + _scratchManager = _pool.Rent(utf8Json.Length * 4); + _scratchMemory = _scratchManager.Buffer; + + int dbLength = _scratchMemory.Length / 2; + _db = _scratchMemory.Slice(0, dbLength); + _stack = new TwoStacks(_scratchMemory.Slice(dbLength)); + + _values = utf8Json; + _insideObject = 0; + _insideArray = 0; + _tokenType = 0; + _valuesIndex = 0; + _dbIndex = 0; + _jsonStartIsObject = false; + + SkipWhitespace(); + + _jsonStartIsObject = _values[_valuesIndex] == '{'; + + int arrayItemsCount = 0; + int numberOfRowsForMembers = 0; + + while (Read()) { + var tokenType = _tokenType; + switch (tokenType) { + case JsonTokenType.ObjectStart: + AppendDbRow(JsonObject.JsonValueType.Object, _valuesIndex); + while(!_stack.TryPushObject(numberOfRowsForMembers)) { + ResizeDb(); + } + numberOfRowsForMembers = 0; + break; + case JsonTokenType.ObjectEnd: + _db.Span.Slice(FindLocation(_stack.ObjectStackCount - 1, true)).Write(numberOfRowsForMembers); + numberOfRowsForMembers += _stack.PopObject(); + break; + case JsonTokenType.ArrayStart: + AppendDbRow(JsonObject.JsonValueType.Array, _valuesIndex); + while (!_stack.TryPushArray(arrayItemsCount)) { + ResizeDb(); + } + arrayItemsCount = 0; + break; + case JsonTokenType.ArrayEnd: + _db.Span.Slice(FindLocation(_stack.ArrayStackCount - 1, false)).Write(arrayItemsCount); + arrayItemsCount = _stack.PopArray(); + break; + case JsonTokenType.Property: + ParsePropertyName(); + ParseValue(); + numberOfRowsForMembers++; + numberOfRowsForMembers++; + break; + case JsonTokenType.Value: + ParseValue(); + arrayItemsCount++; + numberOfRowsForMembers++; + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + var result = new JsonObject(_values, _db.Slice(0, _dbIndex).Span, _pool, _scratchManager); + _scratchManager = null; + return result; + } + + private void ResizeDb() + { + var oldData = _scratchMemory.Span; + var newScratch = _pool.Rent(_scratchMemory.Length * 2); + int dbLength = newScratch.Length / 2; + + var newDb = newScratch.Buffer.Slice(0, dbLength); + _db.Slice(0, _valuesIndex).Span.CopyTo(newDb.Span); + _db = newDb; + + var newStackMemory = newScratch.Buffer.Slice(dbLength); + _stack.Resize(newStackMemory); + _scratchManager.Dispose(); + _scratchManager = newScratch; + } + + private int FindLocation(int index, bool lookingForObject) + { + int rowNumber = 0; + int numFound = 0; + + while (true) { + int rowStartOffset = rowNumber * DbRow.Size; + var row = _db.Slice(rowStartOffset).Span.Read(); + + int lengthOffset = rowStartOffset + 4; + + if (row.Length == -1 && (lookingForObject ? row.Type == JsonObject.JsonValueType.Object : row.Type == JsonObject.JsonValueType.Array)) { + numFound++; + } + + if (index == numFound - 1) { + return lengthOffset; + } else { + if (row.Length > 0 && (row.Type == JsonObject.JsonValueType.Object || row.Type == JsonObject.JsonValueType.Array)) { + rowNumber += row.Length; + } + rowNumber++; + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool Read() + { + var canRead = _valuesIndex < _values.Length; + if (canRead) MoveToNextTokenType(); + return canRead; + } + + private JsonObject.JsonValueType PeekType() + { + SkipWhitespace(); + + var nextByte = _values[_valuesIndex]; + + if (nextByte == '"') { + return JsonObject.JsonValueType.String; + } + + if (nextByte == '{') { + return JsonObject.JsonValueType.Object; + } + + if (nextByte == '[') { + return JsonObject.JsonValueType.Array; + } + + if (nextByte == 't') { + return JsonObject.JsonValueType.True; + } + + if (nextByte == 'f') { + return JsonObject.JsonValueType.False; + } + + if (nextByte == 'n') { + return JsonObject.JsonValueType.Null; + } + + if (nextByte == '-' || (nextByte >= '0' && nextByte <= '9')) { + return JsonObject.JsonValueType.Number; + } + + throw new FormatException("Invalid json, tried to read char '" + nextByte + " at " + _valuesIndex + "'."); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ParsePropertyName() + { + SkipWhitespace(); + ParseString(); + _valuesIndex++; + } + + private void ParseValue() + { + var type = PeekType(); + switch (type) { + case JsonObject.JsonValueType.String: + ParseString(); + break; + case JsonObject.JsonValueType.Number: + ParseNumber(); + break; + case JsonObject.JsonValueType.True: + ParseLiteral(JsonObject.JsonValueType.True, s_true); + return; + case JsonObject.JsonValueType.False: + ParseLiteral(JsonObject.JsonValueType.False, s_false); + break; + case JsonObject.JsonValueType.Null: + ParseLiteral(JsonObject.JsonValueType.Null, s_null); + break; + case JsonObject.JsonValueType.Object: + case JsonObject.JsonValueType.Array: + break; + default: + throw new ArgumentException("Invalid json value type '" + type + "'."); + } + } + + private void ParseString() + { + _valuesIndex++; // eat quote + + var indexOfClosingQuote = _valuesIndex; + do { + indexOfClosingQuote = _values.Slice(indexOfClosingQuote).IndexOf((byte)'"'); + } while (AreNumOfBackSlashesAtEndOfStringOdd(_valuesIndex + indexOfClosingQuote - 2)); + + AppendDbRow(JsonObject.JsonValueType.String, _valuesIndex, indexOfClosingQuote); + + _valuesIndex += indexOfClosingQuote + 1; + SkipWhitespace(); + } + + private void ParseNumber() + { + var nextIndex = _valuesIndex; + + var nextByte = _values[nextIndex]; + if (nextByte == '-') { + nextIndex++; + } + + nextByte = _values[nextIndex]; + while (nextByte >= '0' && nextByte <= '9') { + nextIndex++; + nextByte = _values[nextIndex]; + } + + if (nextByte == '.') { + nextIndex++; + } + + nextByte = _values[nextIndex]; + while (nextByte >= '0' && nextByte <= '9') { + nextIndex++; + nextByte = _values[nextIndex]; + } + + if (nextByte == 'e' || nextByte == 'E') { + nextIndex++; + nextByte = _values[nextIndex]; + if (nextByte == '-' || nextByte == '+') { + nextIndex++; + } + nextByte = _values[nextIndex]; + while (nextByte >= '0' && nextByte <= '9') { + nextIndex++; + nextByte = _values[nextIndex]; + } + } + + var length = nextIndex - _valuesIndex; + + AppendDbRow(JsonObject.JsonValueType.Number, _valuesIndex, length); + + _valuesIndex += length; + SkipWhitespace(); + } + + private void ParseLiteral(JsonObject.JsonValueType literal, ReadOnlySpan expected) + { + if (!_values.Slice(_valuesIndex).StartsWith(expected)) { + throw new FormatException("Invalid json, tried to read " + literal.ToString()); + } + AppendDbRow(literal, _valuesIndex, expected.Length); + _valuesIndex += expected.Length; + SkipWhitespace(); + } + + private bool AppendDbRow(JsonObject.JsonValueType type, int valueIndex, int LengthOrNumberOfRows = DbRow.UnknownNumberOfRows) + { + var newIndex = _dbIndex + DbRow.Size; + if (newIndex >= _db.Length) { + ResizeDb(); + } + + var dbRow = new DbRow(type, valueIndex, LengthOrNumberOfRows); + _db.Span.Slice(_dbIndex).Write(dbRow); + _dbIndex = newIndex; + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void SkipWhitespace() + { + while (Utf8String.IsWhiteSpace(_values[_valuesIndex])) { + _valuesIndex++; + } + } + + private void MoveToNextTokenType() + { + SkipWhitespace(); + + var nextByte = _values[_valuesIndex]; + + switch (_tokenType) { + case JsonTokenType.ObjectStart: + if (nextByte != '}') { + _tokenType = JsonTokenType.Property; + return; + } + break; + case JsonTokenType.ObjectEnd: + if (nextByte == ',') { + _valuesIndex++; + if (_insideObject == _insideArray) { + _tokenType = !_jsonStartIsObject ? JsonTokenType.Property : JsonTokenType.Value; + return; + } + _tokenType = _insideObject > _insideArray ? JsonTokenType.Property : JsonTokenType.Value; + return; + } + break; + case JsonTokenType.ArrayStart: + if (nextByte != ']') { + _tokenType = JsonTokenType.Value; + return; + } + break; + case JsonTokenType.ArrayEnd: + if (nextByte == ',') { + _valuesIndex++; + if (_insideObject == _insideArray) { + _tokenType = !_jsonStartIsObject ? JsonTokenType.Property : JsonTokenType.Value; + return; + } + _tokenType = _insideObject > _insideArray ? JsonTokenType.Property : JsonTokenType.Value; + return; + } + break; + case JsonTokenType.Property: + if (nextByte == ',') { + _valuesIndex++; + return; + } + break; + case JsonTokenType.Value: + if (nextByte == ',') { + _valuesIndex++; + return; + } + break; + } + + _valuesIndex++; + switch (nextByte) { + case (byte)'{': + _insideObject++; + _tokenType = JsonTokenType.ObjectStart; + return; + case (byte)'}': + _insideObject--; + _tokenType = JsonTokenType.ObjectEnd; + return; + case (byte)'[': + _insideArray++; + _tokenType = JsonTokenType.ArrayStart; + return; + case (byte)']': + _insideArray--; + _tokenType = JsonTokenType.ArrayEnd; + return; + default: + throw new FormatException("Unable to get next token type. Check json format."); + } + } + + private bool AreNumOfBackSlashesAtEndOfStringOdd(int count) + { + var length = count - _valuesIndex; + if (length < 0) return false; + var nextByte = _values[count]; + if (nextByte != '\\') return false; + var numOfBackSlashes = 0; + while (nextByte == '\\') { + numOfBackSlashes++; + if ((length - numOfBackSlashes) < 0) return numOfBackSlashes % 2 != 0; + nextByte = _values[count - numOfBackSlashes]; + } + return numOfBackSlashes % 2 != 0; + } + } +} diff --git a/src/Discord.Net.Rest/_corefxlab/System.Text.Json/System/Text/Json/JsonReader.cs b/src/Discord.Net.Rest/_corefxlab/System.Text.Json/System/Text/Json/JsonReader.cs new file mode 100644 index 000000000..22b526db8 --- /dev/null +++ b/src/Discord.Net.Rest/_corefxlab/System.Text.Json/System/Text/Json/JsonReader.cs @@ -0,0 +1,832 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; + +namespace System.Text.Json +{ + public struct JsonReader + { + // We are using a ulong to represent our nested state, so we can only go 64 levels deep. + private const int MaxDepth = sizeof(ulong) * 8; + + private readonly JsonEncoderState _encoderState; + private readonly SymbolTable _symbolTable; + + private ReadOnlySpan _buffer; + + // Depth tracks the recursive depth of the nested objects / arrays within the JSON data. + internal int _depth; + + // This mask represents a tiny stack to track the state during nested transitions. + // The first bit represents the state of the current level (1 == object, 0 == array). + // Each subsequent bit is the parent / containing type (object or array). Since this + // reader does a linear scan, we only need to keep a single path as we go through the data. + private ulong _containerMask; + + // These next 2 properties are used to check for whether we can take the fast path + // for invariant UTF-8 or UTF-16 processing. Otherwise, we need to go through the + // slow path which makes use of the (possibly generic) encoder. + private bool UseFastUtf8 => _encoderState == JsonEncoderState.UseFastUtf8; + private bool UseFastUtf16 => _encoderState == JsonEncoderState.UseFastUtf16; + + // These properties are helpers for determining the current state of the reader + private bool IsRoot => _depth == 1; + private bool InArray => (_containerMask & 1) == 0 && (_depth > 0); + private bool InObject => (_containerMask & 1) == 1; + + /// + /// Gets the token type of the last processed token in the JSON stream. + /// + public JsonTokenType TokenType { get; private set; } + + /// + /// Gets the value as a ReadOnlySpan of the last processed token. The contents of this + /// is only relevant when is or + /// . Otherwise, this value should be set to + /// . + /// + public ReadOnlySpan Value { get; private set; } + + /// + /// Gets the JSON value type of the last processed token. The contents of this + /// is only relevant when is or + /// . + /// + public JsonValueType ValueType { get; private set; } + + /// + /// Gets the encoder instance used when the reader was constructed. + /// + public SymbolTable SymbolTable => _symbolTable; + + /// + /// Constructs a new JsonReader instance. This is a stack-only type. + /// + /// The value to consume. + /// An encoder used for decoding bytes from into characters. + public JsonReader(ReadOnlySpan data, SymbolTable symbolTable) + { + _buffer = data; + _symbolTable = symbolTable; + _depth = 0; + _containerMask = 0; + + if (_symbolTable == SymbolTable.InvariantUtf8) + _encoderState = JsonEncoderState.UseFastUtf8; + else if (_symbolTable == SymbolTable.InvariantUtf16) + _encoderState = JsonEncoderState.UseFastUtf16; + else + _encoderState = JsonEncoderState.UseFullEncoder; + + TokenType = JsonTokenType.None; + Value = ReadOnlySpan.Empty; + ValueType = JsonValueType.Unknown; + } + + /// + /// Read the next token from the data buffer. + /// + /// True if the token was read successfully, else false. + public bool Read() + { + ref byte bytes = ref _buffer.DangerousGetPinnableReference(); + int length = _buffer.Length; + int skip = SkipWhiteSpace(ref bytes, length); + + ref byte next = ref Unsafe.Add(ref bytes, skip); + length -= skip; + + int step = GetNextCharAscii(ref next, length, out char ch); + if (step == 0) return false; + + switch (TokenType) + { + case JsonTokenType.None: + if (ch == JsonConstants.OpenBrace) + StartObject(); + else if (ch == JsonConstants.OpenBracket) + StartArray(); + else + throw new JsonReaderException(); + break; + + case JsonTokenType.StartObject: + if (ch == JsonConstants.CloseBrace) + EndObject(); + else + step = ConsumePropertyName(ref next, length); + break; + + case JsonTokenType.StartArray: + if (ch == JsonConstants.CloseBracket) + EndArray(); + else + step = ConsumeValue(ch, step, ref next, length); + break; + + case JsonTokenType.PropertyName: + step = ConsumeValue(ch, step, ref next, length); + if (step == 0) return false; + break; + + case JsonTokenType.EndArray: + case JsonTokenType.EndObject: + case JsonTokenType.Value: + step = ConsumeNext(ch, step, ref next, length); + if (step == 0) return false; + break; + } + + _buffer = _buffer.Slice(skip + step); + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void StartObject() + { + if (_depth > MaxDepth) + throw new JsonReaderException(); + + _depth++; + _containerMask = (_containerMask << 1) | 1; + TokenType = JsonTokenType.StartObject; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void EndObject() + { + if (!InObject || _depth <= 0) + throw new JsonReaderException(); + + _depth--; + _containerMask >>= 1; + TokenType = JsonTokenType.EndObject; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void StartArray() + { + if (_depth > MaxDepth) + throw new JsonReaderException(); + + _depth++; + _containerMask = (_containerMask << 1); + TokenType = JsonTokenType.StartArray; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void EndArray() + { + if (!InArray || _depth <= 0) + throw new JsonReaderException(); + + _depth--; + _containerMask >>= 1; + TokenType = JsonTokenType.EndArray; + } + + /// + /// This method consumes the next token regardless of whether we are inside an object or an array. + /// For an object, it reads the next property name token. For an array, it just reads the next value. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int ConsumeNext(char marker, int markerBytes, ref byte src, int length) + { + int skip = markerBytes; + + switch (marker) + { + case (char)JsonConstants.ListSeperator: + { + skip += SkipWhiteSpace(ref Unsafe.Add(ref src, markerBytes), length - markerBytes); + length -= skip; + ref byte next = ref Unsafe.Add(ref src, skip); + if (InObject) + return skip + ConsumePropertyName(ref next, length); + else if (InArray) + { + int step = GetNextCharAscii(ref next, length, out char ch); + if (step == 0) return 0; + return skip + ConsumeValue(ch, step, ref next, length); + } + else + throw new JsonReaderException(); + } + + case (char)JsonConstants.CloseBrace: + EndObject(); + return skip; + + case (char)JsonConstants.CloseBracket: + EndArray(); + return skip; + + default: + throw new JsonReaderException(); + } + } + + /// + /// This method contains the logic for processing the next value token and determining + /// what type of data it is. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int ConsumeValue(char marker, int markerBytes, ref byte src, int length) + { + TokenType = JsonTokenType.Value; + + switch (marker) + { + case (char)JsonConstants.Quote: + return ConsumeString(ref src, length); + + case (char)JsonConstants.OpenBrace: + StartObject(); + ValueType = JsonValueType.Object; + return markerBytes; + + case (char)JsonConstants.OpenBracket: + StartArray(); + ValueType = JsonValueType.Array; + return markerBytes; + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return ConsumeNumber(ref src, length); + + case '-': + int step = GetNextCharAscii(ref src, length, out char ch); + if (step == 0) throw new JsonReaderException(); + return (ch == 'I') + ? ConsumeInfinity(ref src, length, true) + : ConsumeNumber(ref src, length); + + case 'f': + return ConsumeFalse(ref src, length); + + case 't': + return ConsumeTrue(ref src, length); + + case 'n': + return ConsumeNull(ref src, length); + + case 'u': + return ConsumeUndefined(ref src, length); + + case 'N': + return ConsumeNaN(ref src, length); + + case 'I': + return ConsumeInfinity(ref src, length, false); + + case '/': + // TODO: Comments? + throw new NotImplementedException(); + } + + return 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int ConsumeNumber(ref byte src, int length) + { + if (UseFastUtf8) + { + int idx = 0; + // Scan until we find a list separator, array end, or object end. + while (idx < length) + { + ref byte b = ref Unsafe.Add(ref src, idx); + if (b == JsonConstants.ListSeperator || b == JsonConstants.CloseBrace || b == JsonConstants.CloseBracket) + break; + idx++; + } + + // Calculate the real start of the number based on our current buffer location. + int startIndex = (int)Unsafe.ByteOffset(ref _buffer.DangerousGetPinnableReference(), ref src); + + Value = _buffer.Slice(startIndex, idx); + ValueType = JsonValueType.Number; + return idx; + } + else if (UseFastUtf16) + { + ref char chars = ref Unsafe.As(ref src); + int idx = 0; + length >>= 1; // Each char is 2 bytes. + + // Scan until we find a list separator, array end, or object end. + while (idx < length) + { + ref char b = ref Unsafe.Add(ref chars, idx); + if (b == JsonConstants.ListSeperator || b == JsonConstants.CloseBrace || b == JsonConstants.CloseBracket) + break; + idx++; + } + + // Calculate the real start of the number based on our current buffer location. + int startIndex = (int)Unsafe.ByteOffset(ref _buffer.DangerousGetPinnableReference(), ref src); + + // consumed is in characters, but our buffer is in bytes, so we need to double it for buffer slicing. + int bytesConsumed = idx << 1; + + Value = _buffer.Slice(startIndex, bytesConsumed); + ValueType = JsonValueType.Number; + return bytesConsumed; + } + else + throw new NotImplementedException(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int ConsumeNaN(ref byte src, int length) + { + Value = ReadOnlySpan.Empty; + ValueType = JsonValueType.NaN; + + if (UseFastUtf8) + { + if (length < 3 + || Unsafe.Add(ref src, 0) != 'N' + || Unsafe.Add(ref src, 1) != 'a' + || Unsafe.Add(ref src, 2) != 'N') + { + throw new JsonReaderException(); + } + + return 3; + } + else if (UseFastUtf16) + { + ref char chars = ref Unsafe.As(ref src); + if (length < 6 + || Unsafe.Add(ref chars, 0) != 'N' + || Unsafe.Add(ref chars, 1) != 'a' + || Unsafe.Add(ref chars, 2) != 'N') + { + throw new JsonReaderException(); + } + + return 6; + } + else + throw new NotImplementedException(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int ConsumeNull(ref byte src, int length) + { + Value = ReadOnlySpan.Empty; + ValueType = JsonValueType.Null; + + if (UseFastUtf8) + { + if (length < 4 + || Unsafe.Add(ref src, 0) != 'n' + || Unsafe.Add(ref src, 1) != 'u' + || Unsafe.Add(ref src, 2) != 'l' + || Unsafe.Add(ref src, 3) != 'l') + { + throw new JsonReaderException(); + } + + return 4; + } + else if (UseFastUtf16) + { + ref char chars = ref Unsafe.As(ref src); + if (length < 8 + || Unsafe.Add(ref chars, 0) != 'n' + || Unsafe.Add(ref chars, 1) != 'u' + || Unsafe.Add(ref chars, 2) != 'l' + || Unsafe.Add(ref chars, 3) != 'l') + { + throw new JsonReaderException(); + } + + return 8; + } + else + throw new NotImplementedException(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int ConsumeInfinity(ref byte src, int length, bool negative) + { + Value = ReadOnlySpan.Empty; + ValueType = !negative ? JsonValueType.Infinity : JsonValueType.NegativeInfinity; + + int idx = negative ? 1 : 0; + if (UseFastUtf8) + { + if (length < 8 + idx + || Unsafe.Add(ref src, idx++) != 'I' + || Unsafe.Add(ref src, idx++) != 'n' + || Unsafe.Add(ref src, idx++) != 'f' + || Unsafe.Add(ref src, idx++) != 'i' + || Unsafe.Add(ref src, idx++) != 'n' + || Unsafe.Add(ref src, idx++) != 'i' + || Unsafe.Add(ref src, idx++) != 't' + || Unsafe.Add(ref src, idx++) != 'y') + { + throw new JsonReaderException(); + } + + return idx; + } + else if (UseFastUtf16) + { + ref char chars = ref Unsafe.As(ref src); + if (length < 16 + idx + || Unsafe.Add(ref chars, idx++) != 'I' + || Unsafe.Add(ref chars, idx++) != 'n' + || Unsafe.Add(ref chars, idx++) != 'f' + || Unsafe.Add(ref chars, idx++) != 'i' + || Unsafe.Add(ref chars, idx++) != 'n' + || Unsafe.Add(ref chars, idx++) != 'i' + || Unsafe.Add(ref chars, idx++) != 't' + || Unsafe.Add(ref chars, idx++) != 'y') + { + throw new JsonReaderException(); + } + + return idx; + } + else + throw new NotImplementedException(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int ConsumeUndefined(ref byte src, int length) + { + Value = ReadOnlySpan.Empty; + ValueType = JsonValueType.Undefined; + + if (UseFastUtf8) + { + if (length < 9 + || Unsafe.Add(ref src, 0) != 'u' + || Unsafe.Add(ref src, 1) != 'n' + || Unsafe.Add(ref src, 2) != 'd' + || Unsafe.Add(ref src, 3) != 'e' + || Unsafe.Add(ref src, 4) != 'f' + || Unsafe.Add(ref src, 5) != 'i' + || Unsafe.Add(ref src, 6) != 'n' + || Unsafe.Add(ref src, 7) != 'e' + || Unsafe.Add(ref src, 8) != 'd') + { + throw new JsonReaderException(); + } + + return 9; + } + else if (UseFastUtf16) + { + ref char chars = ref Unsafe.As(ref src); + if (length < 18 + || Unsafe.Add(ref chars, 0) != 'u' + || Unsafe.Add(ref chars, 1) != 'n' + || Unsafe.Add(ref chars, 2) != 'd' + || Unsafe.Add(ref chars, 3) != 'e' + || Unsafe.Add(ref chars, 4) != 'f' + || Unsafe.Add(ref chars, 5) != 'i' + || Unsafe.Add(ref chars, 6) != 'n' + || Unsafe.Add(ref chars, 7) != 'e' + || Unsafe.Add(ref chars, 8) != 'd') + { + throw new JsonReaderException(); + } + + return 18; + } + else + throw new NotImplementedException(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int ConsumeFalse(ref byte src, int length) + { + Value = ReadOnlySpan.Empty; + ValueType = JsonValueType.False; + + if (UseFastUtf8) + { + if (length < 5 + || Unsafe.Add(ref src, 0) != 'f' + || Unsafe.Add(ref src, 1) != 'a' + || Unsafe.Add(ref src, 2) != 'l' + || Unsafe.Add(ref src, 3) != 's' + || Unsafe.Add(ref src, 4) != 'e') + { + throw new JsonReaderException(); + } + + return 5; + } + else if (UseFastUtf16) + { + ref char chars = ref Unsafe.As(ref src); + if (length < 10 + || Unsafe.Add(ref chars, 0) != 'f' + || Unsafe.Add(ref chars, 1) != 'a' + || Unsafe.Add(ref chars, 2) != 'l' + || Unsafe.Add(ref chars, 3) != 's' + || Unsafe.Add(ref chars, 4) != 'e') + { + throw new JsonReaderException(); + } + + return 10; + } + else + throw new NotImplementedException(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int ConsumeTrue(ref byte src, int length) + { + Value = ReadOnlySpan.Empty; + ValueType = JsonValueType.True; + + if (UseFastUtf8) + { + if (length < 4 + || Unsafe.Add(ref src, 0) != 't' + || Unsafe.Add(ref src, 1) != 'r' + || Unsafe.Add(ref src, 2) != 'u' + || Unsafe.Add(ref src, 3) != 'e') + { + throw new JsonReaderException(); + } + + return 4; + } + else if (UseFastUtf16) + { + ref char chars = ref Unsafe.As(ref src); + if (length < 8 + || Unsafe.Add(ref chars, 0) != 't' + || Unsafe.Add(ref chars, 1) != 'r' + || Unsafe.Add(ref chars, 2) != 'u' + || Unsafe.Add(ref chars, 3) != 'e') + { + throw new JsonReaderException(); + } + + return 8; + } + else + throw new NotImplementedException(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int ConsumePropertyName(ref byte src, int length) + { + if (UseFastUtf8) + return ConsumePropertyNameUtf8(ref src, length); + else if (UseFastUtf16) + return ConsumePropertyNameUtf16(ref src, length); + else + return ConsumePropertyNameSlow(ref src, length); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int ConsumePropertyNameUtf8(ref byte src, int length) + { + int consumed = ConsumeStringUtf8(ref src, length); + if (consumed == 0) throw new JsonReaderException(); + + consumed += SkipWhiteSpaceUtf8(ref Unsafe.Add(ref src, consumed), length - consumed); + if (consumed >= length) throw new JsonReaderException(); + + // The next character must be a key / value seperator. Validate and skip. + if (Unsafe.Add(ref src, consumed++) != JsonConstants.KeyValueSeperator) + throw new JsonReaderException(); + + TokenType = JsonTokenType.PropertyName; + return consumed; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int ConsumePropertyNameUtf16(ref byte src, int length) + { + int consumed = ConsumeStringUtf16(ref src, length); + if (consumed == 0) throw new JsonReaderException(); + + consumed += SkipWhiteSpaceUtf16(ref Unsafe.Add(ref src, consumed), length - consumed); + if (consumed >= length) throw new JsonReaderException(); + + // The next character must be a key / value seperator + if (Unsafe.As(ref Unsafe.Add(ref src, consumed)) != JsonConstants.KeyValueSeperator) + throw new JsonReaderException(); + + consumed += 2; // Skip the key / value seperator + TokenType = JsonTokenType.PropertyName; + return consumed; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int ConsumePropertyNameSlow(ref byte src, int length) + { + throw new NotImplementedException(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int ConsumeString(ref byte src, int length) + { + if (UseFastUtf8) + return ConsumeStringUtf8(ref src, length); + else if (UseFastUtf16) + return ConsumeStringUtf16(ref src, length); + else + return ConsumeStringSlow(ref src, length); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int ConsumeStringUtf8(ref byte src, int length) + { + // The first character MUST be a JSON string quote + if (src != JsonConstants.Quote) throw new JsonReaderException(); + + // If we are in this method, the first char is already known to be a JSON quote character. + // Skip through the bytes until we find the closing JSON quote character. + int idx = 1; + while (idx < length && Unsafe.Add(ref src, idx++) != JsonConstants.Quote) ; + + // If we hit the end of the source and never saw an ending quote, then fail. + if (idx == length && Unsafe.Add(ref src, idx - 1) != JsonConstants.Quote) + throw new JsonReaderException(); + + // Calculate the real start of the property name based on our current buffer location. + // Also, skip the opening JSON quote character. + int startIndex = (int)Unsafe.ByteOffset(ref _buffer.DangerousGetPinnableReference(), ref src) + 1; + + Value = _buffer.Slice(startIndex, idx - 2); // -2 to exclude the quote characters. + ValueType = JsonValueType.String; + return idx; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int ConsumeStringUtf16(ref byte src, int length) + { + ref char chars = ref Unsafe.As(ref src); + length >>= 1; // sizeof(char) is 2, so we have half as many characters as count. + + // The first character MUST be a JSON string quote + if (chars != JsonConstants.Quote) throw new JsonReaderException(); + + // Skip through the bytes until we find the closing JSON quote character. + int idx = 1; + while (idx < length && Unsafe.Add(ref chars, idx++) != JsonConstants.Quote) ; + + // If we hit the end of the source and never saw an ending quote, then fail. + if (idx == length && Unsafe.Add(ref chars, idx - 1) != JsonConstants.Quote) + throw new JsonReaderException(); + + // Calculate the real start of the property name based on our current buffer location. + // Also, skip the opening JSON quote character. + int startIndex = (int)Unsafe.ByteOffset(ref _buffer.DangerousGetPinnableReference(), ref src) + 2; + + // idx is in characters, but our buffer is in bytes, so we need to double it for buffer slicing. + // Also, remove the quote characters as well. + length = (idx - 2) << 1; + + Value = _buffer.Slice(startIndex, length); + ValueType = JsonValueType.String; + return idx * sizeof(char); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int ConsumeStringSlow(ref byte src, int length) + { + throw new NotImplementedException(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int SkipWhiteSpace(ref byte src, int length) + { + if (UseFastUtf8) + return SkipWhiteSpaceUtf8(ref src, length); + else if (UseFastUtf16) + return SkipWhiteSpaceUtf16(ref src, length); + else + return SkipWhiteSpaceSlow(ref src, length); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int SkipWhiteSpaceUtf8(ref byte src, int length) + { + int idx = 0; + while (idx < length) + { + switch (Unsafe.Add(ref src, idx)) + { + case (byte)JsonConstants.Space: + case (byte)JsonConstants.CarriageReturn: + case (byte)JsonConstants.LineFeed: + case (byte)'\t': + idx++; + break; + + default: + return idx; + } + } + + return idx; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int SkipWhiteSpaceUtf16(ref byte src, int length) + { + ref char chars = ref Unsafe.As(ref src); + length >>= 1; + + int idx = 0; + while (idx < length) + { + switch (Unsafe.Add(ref chars, idx)) + { + case (char)JsonConstants.Space: + case (char)JsonConstants.CarriageReturn: + case (char)JsonConstants.LineFeed: + case '\t': + idx++; + break; + + default: + return idx * sizeof(char); + } + } + + return idx * sizeof(char); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int SkipWhiteSpaceSlow(ref byte src, int length) + { + int idx = 0; + while (idx < length) + { + int skip = GetNextCharAscii(ref Unsafe.Add(ref src, idx), length, out char ch); + if (skip == 0) + break; + + switch (ch) + { + case (char)JsonConstants.Space: + case (char)JsonConstants.CarriageReturn: + case (char)JsonConstants.LineFeed: + case '\t': + idx += skip; + break; + + default: + return idx; + } + } + + return idx; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int GetNextCharAscii(ref byte src, int length, out char ch) + { + if (UseFastUtf8) + { + if (length < 1) + { + ch = default; + return 0; + } + + ch = (char)src; + return 1; + } + else if (UseFastUtf16) + { + if (length < 2) + { + ch = default; + return 0; + } + + ch = Unsafe.As(ref src); + return 2; + } + else + { + throw new NotImplementedException(); + } + } + } +} diff --git a/src/Discord.Net.Rest/_corefxlab/System.Text.Json/System/Text/Json/JsonReaderException.cs b/src/Discord.Net.Rest/_corefxlab/System.Text.Json/System/Text/Json/JsonReaderException.cs new file mode 100644 index 000000000..57b30a0f5 --- /dev/null +++ b/src/Discord.Net.Rest/_corefxlab/System.Text.Json/System/Text/Json/JsonReaderException.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + + +namespace System.Text.Json +{ + public class JsonReaderException : Exception + { + public JsonReaderException() : base() { } + } +} diff --git a/src/Discord.Net.Rest/_corefxlab/System.Text.Json/System/Text/Json/JsonTokenType.cs b/src/Discord.Net.Rest/_corefxlab/System.Text.Json/System/Text/Json/JsonTokenType.cs new file mode 100644 index 000000000..4e915c535 --- /dev/null +++ b/src/Discord.Net.Rest/_corefxlab/System.Text.Json/System/Text/Json/JsonTokenType.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace System.Text.Json +{ + public enum JsonTokenType + { + None, + StartObject, + EndObject, + StartArray, + EndArray, + PropertyName, + Comment, + Value, + Null, + Undefined, + } +} diff --git a/src/Discord.Net.Rest/_corefxlab/System.Text.Json/System/Text/Json/JsonValueType.cs b/src/Discord.Net.Rest/_corefxlab/System.Text.Json/System/Text/Json/JsonValueType.cs new file mode 100644 index 000000000..25ad2c891 --- /dev/null +++ b/src/Discord.Net.Rest/_corefxlab/System.Text.Json/System/Text/Json/JsonValueType.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace System.Text.Json +{ + public enum JsonValueType + { + Unknown, + Object, + Array, + Number, + String, + True, + False, + Null, + Undefined, + NaN, + Infinity, + NegativeInfinity, + } +} diff --git a/src/Discord.Net.Rest/_corefxlab/System.Text.Json/System/Text/Json/JsonWriter.cs b/src/Discord.Net.Rest/_corefxlab/System.Text.Json/System/Text/Json/JsonWriter.cs new file mode 100644 index 000000000..cb9d16f1d --- /dev/null +++ b/src/Discord.Net.Rest/_corefxlab/System.Text.Json/System/Text/Json/JsonWriter.cs @@ -0,0 +1,588 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; +using System.Text.Formatting; + +namespace System.Text.Json +{ + public struct JsonWriter + { + readonly bool _prettyPrint; + readonly ITextOutput _output; + readonly JsonEncoderState _encoderState; + + int _indent; + bool _firstItem; + + // These next 2 properties are used to check for whether we can take the fast path + // for invariant UTF-8 or UTF-16 processing. Otherwise, we need to go through the + // slow path which makes use of the (possibly generic) encoder. + private bool UseFastUtf8 => _encoderState == JsonEncoderState.UseFastUtf8; + private bool UseFastUtf16 => _encoderState == JsonEncoderState.UseFastUtf16; + + /// + /// Constructs a JSON writer with a specified . + /// + /// An instance of used for writing bytes to an output channel. + /// Specifies whether to add whitespace to the output text for user readability. + public JsonWriter(ITextOutput output, bool prettyPrint = false) + { + _output = output; + _prettyPrint = prettyPrint; + + _indent = -1; + _firstItem = true; + + var symbolTable = output.SymbolTable; + if (symbolTable == SymbolTable.InvariantUtf8) + _encoderState = JsonEncoderState.UseFastUtf8; + else if (symbolTable == SymbolTable.InvariantUtf16) + _encoderState = JsonEncoderState.UseFastUtf16; + else + _encoderState = JsonEncoderState.UseFullEncoder; + } + + /// + /// Write the starting tag of an object. This is used for adding an object to an + /// array of other items. If this is used while inside a nested object, the property + /// name will be missing and result in invalid JSON. + /// + public void WriteObjectStart() + { + WriteItemSeperator(); + WriteSpacing(false); + WriteControl(JsonConstants.OpenBrace); + + _firstItem = true; + _indent++; + } + + /// + /// Write the starting tag of an object. This is used for adding an object to a + /// nested object. If this is used while inside a nested array, the property + /// name will be written and result in invalid JSON. + /// + /// The name of the property (i.e. key) within the containing object. + public void WriteObjectStart(string name) + { + WriteStartAttribute(name); + WriteControl(JsonConstants.OpenBrace); + + _firstItem = true; + _indent++; + } + + /// + /// Writes the end tag for an object. + /// + public void WriteObjectEnd() + { + _firstItem = false; + _indent--; + WriteSpacing(); + WriteControl(JsonConstants.CloseBrace); + } + + /// + /// Write the starting tag of an array. This is used for adding an array to a nested + /// array of other items. If this is used while inside a nested object, the property + /// name will be missing and result in invalid JSON. + /// + public void WriteArrayStart() + { + WriteItemSeperator(); + WriteSpacing(); + WriteControl(JsonConstants.OpenBracket); + + _firstItem = true; + _indent++; + } + + /// + /// Write the starting tag of an array. This is used for adding an array to a + /// nested object. If this is used while inside a nested array, the property + /// name will be written and result in invalid JSON. + /// + /// The name of the property (i.e. key) within the containing object. + public void WriteArrayStart(string name) + { + WriteStartAttribute(name); + WriteControl(JsonConstants.OpenBracket); + + _firstItem = true; + _indent++; + } + + /// + /// Writes the end tag for an array. + /// + public void WriteArrayEnd() + { + _firstItem = false; + _indent--; + WriteSpacing(); + WriteControl(JsonConstants.CloseBracket); + } + + /// + /// Write a quoted string value along with a property name into the current object. + /// + /// The name of the property (i.e. key) within the containing object. + /// The string value that will be quoted within the JSON data. + public void WriteAttribute(string name, string value) + { + WriteStartAttribute(name); + WriteQuotedString(value); + } + + /// + /// Write a signed integer value along with a property name into the current object. + /// + /// The name of the property (i.e. key) within the containing object. + /// The signed integer value to be written to JSON data. + public void WriteAttribute(string name, long value) + { + WriteStartAttribute(name); + WriteNumber(value); + } + + /// + /// Write an unsigned integer value along with a property name into the current object. + /// + /// The name of the property (i.e. key) within the containing object. + /// The unsigned integer value to be written to JSON data. + public void WriteAttribute(string name, ulong value) + { + WriteStartAttribute(name); + WriteNumber(value); + } + + /// + /// Write a boolean value along with a property name into the current object. + /// + /// The name of the property (i.e. key) within the containing object. + /// The boolean value to be written to JSON data. + public void WriteAttribute(string name, bool value) + { + WriteStartAttribute(name); + if (value) + WriteJsonValue(JsonConstants.TrueValue); + else + WriteJsonValue(JsonConstants.FalseValue); + } + + /// + /// Write a value along with a property name into the current object. + /// + /// The name of the property (i.e. key) within the containing object. + /// The value to be written to JSON data. + public void WriteAttribute(string name, DateTime value) + { + WriteStartAttribute(name); + WriteDateTime(value); + } + + /// + /// Write a value along with a property name into the current object. + /// + /// The name of the property (i.e. key) within the containing object. + /// The value to be written to JSON data. + public void WriteAttribute(string name, DateTimeOffset value) + { + WriteStartAttribute(name); + WriteDateTimeOffset(value); + } + + /// + /// Write a value along with a property name into the current object. + /// + /// The name of the property (i.e. key) within the containing object. + /// The value to be written to JSON data. + public void WriteAttribute(string name, Guid value) + { + WriteStartAttribute(name); + WriteGuid(value); + } + + /// + /// Write a null value along with a property name into the current object. + /// + /// The name of the property (i.e. key) within the containing object. + public void WriteAttributeNull(string name) + { + WriteStartAttribute(name); + WriteJsonValue(JsonConstants.NullValue); + } + + /// + /// Writes a quoted string value into the current array. + /// + /// The string value that will be quoted within the JSON data. + public void WriteValue(string value) + { + WriteItemSeperator(); + _firstItem = false; + WriteSpacing(); + WriteQuotedString(value); + } + + /// + /// Write a signed integer value into the current array. + /// + /// The signed integer value to be written to JSON data. + public void WriteValue(long value) + { + WriteItemSeperator(); + _firstItem = false; + WriteSpacing(); + WriteNumber(value); + } + + /// + /// Write a unsigned integer value into the current array. + /// + /// The unsigned integer value to be written to JSON data. + public void WriteValue(ulong value) + { + WriteItemSeperator(); + _firstItem = false; + WriteSpacing(); + WriteNumber(value); + } + + /// + /// Write a boolean value into the current array. + /// + /// The boolean value to be written to JSON data. + public void WriteValue(bool value) + { + WriteItemSeperator(); + _firstItem = false; + WriteSpacing(); + if (value) + WriteJsonValue(JsonConstants.TrueValue); + else + WriteJsonValue(JsonConstants.FalseValue); + } + + /// + /// Write a value into the current array. + /// + /// The value to be written to JSON data. + public void WriteValue(DateTime value) + { + WriteItemSeperator(); + _firstItem = false; + WriteSpacing(); + WriteDateTime(value); + } + + /// + /// Write a value into the current array. + /// + /// The value to be written to JSON data. + public void WriteValue(DateTimeOffset value) + { + WriteItemSeperator(); + _firstItem = false; + WriteSpacing(); + WriteDateTimeOffset(value); + } + + /// + /// Write a value into the current array. + /// + /// The value to be written to JSON data. + public void WriteValue(Guid value) + { + WriteItemSeperator(); + _firstItem = false; + WriteSpacing(); + WriteGuid(value); + } + + /// + /// Write a null value into the current array. + /// + public void WriteNull() + { + WriteItemSeperator(); + _firstItem = false; + WriteSpacing(); + WriteJsonValue(JsonConstants.NullValue); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void WriteStartAttribute(string name) + { + WriteItemSeperator(); + _firstItem = false; + + WriteSpacing(); + WriteQuotedString(name); + WriteControl(JsonConstants.KeyValueSeperator); + + if (_prettyPrint) + WriteControl(JsonConstants.Space); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteControl(byte value) + { + if (UseFastUtf8) + { + EnsureBuffer(1).DangerousGetPinnableReference() = value; + _output.Advance(1); + } + else if (UseFastUtf16) + { + var buffer = EnsureBuffer(2); + Unsafe.As(ref buffer.DangerousGetPinnableReference()) = (char)value; + _output.Advance(2); + } + else + { + var buffer = _output.Buffer; + int written; + while (!_output.SymbolTable.TryEncode(value, buffer, out written)) + buffer = EnsureBuffer(); + + _output.Advance(written); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteQuotedString(string value) + { + WriteControl(JsonConstants.Quote); + // TODO: We need to handle escaping. + Write(value.AsSpan()); + WriteControl(JsonConstants.Quote); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteNumber(long value) + { + var buffer = _output.Buffer; + int written; + while (!value.TryFormat(buffer, out written, JsonConstants.NumberFormat, _output.SymbolTable)) + buffer = EnsureBuffer(); + + _output.Advance(written); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteNumber(ulong value) + { + var buffer = _output.Buffer; + int written; + while (!value.TryFormat(buffer, out written, JsonConstants.NumberFormat, _output.SymbolTable)) + buffer = EnsureBuffer(); + + _output.Advance(written); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteDateTime(DateTime value) + { + var buffer = _output.Buffer; + int written; + while (!value.TryFormat(buffer, out written, JsonConstants.DateTimeFormat, _output.SymbolTable)) + buffer = EnsureBuffer(); + + _output.Advance(written); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteDateTimeOffset(DateTimeOffset value) + { + var buffer = _output.Buffer; + int written; + while (!value.TryFormat(buffer, out written, JsonConstants.DateTimeFormat, _output.SymbolTable)) + buffer = EnsureBuffer(); + + _output.Advance(written); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteGuid(Guid value) + { + var buffer = _output.Buffer; + int written; + while (!value.TryFormat(buffer, out written, JsonConstants.GuidFormat, _output.SymbolTable)) + buffer = EnsureBuffer(); + + _output.Advance(written); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void Write(ReadOnlySpan value) + { + ReadOnlySpan source = value.AsBytes(); + + if (UseFastUtf8) + { + Span destination = _output.Buffer; + + while (true) + { + var status = Encoders.Utf16.ToUtf8(source, destination, out int consumed, out int written); + if (status == Buffers.TransformationStatus.Done) + { + _output.Advance(written); + return; + } + + if (status == Buffers.TransformationStatus.DestinationTooSmall) + { + destination = EnsureBuffer(); + continue; + } + + // This is a failure due to bad input. This shouldn't happen under normal circumstances. + throw new FormatException(); + } + } + else if (UseFastUtf16) + { + Span destination = EnsureBuffer(source.Length); + source.CopyTo(destination); + _output.Advance(source.Length); + } + else + { + Span destination = _output.Buffer; + if (!_output.SymbolTable.TryEncode(source, destination, out int consumed, out int written)) + destination = EnsureBuffer(); + + _output.Advance(written); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteJsonValue(ReadOnlySpan values) + { + var buffer = _output.Buffer; + int written; + while (!_output.SymbolTable.TryEncode(values, buffer, out int consumed, out written)) + buffer = EnsureBuffer(); + + _output.Advance(written); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteItemSeperator() + { + if (_firstItem) return; + + WriteControl(JsonConstants.ListSeperator); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteSpacing(bool newLine = true) + { + if (!_prettyPrint) return; + + if (UseFastUtf8) + WriteSpacingUtf8(newLine); + else if (UseFastUtf16) + WriteSpacingUtf16(newLine); + else + WriteSpacingSlow(newLine); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteSpacingSlow(bool newLine) + { + if (newLine) + { + WriteControl(JsonConstants.CarriageReturn); + WriteControl(JsonConstants.LineFeed); + } + + int indent = _indent; + while (indent-- >= 0) + { + WriteControl(JsonConstants.Space); + WriteControl(JsonConstants.Space); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteSpacingUtf8(bool newline) + { + var indent = _indent; + var bytesNeeded = newline ? 2 : 0; + bytesNeeded += (indent + 1) * 2; + + var buffer = EnsureBuffer(bytesNeeded); + ref byte utf8Bytes = ref buffer.DangerousGetPinnableReference(); + int idx = 0; + + if (newline) + { + Unsafe.Add(ref utf8Bytes, idx++) = JsonConstants.CarriageReturn; + Unsafe.Add(ref utf8Bytes, idx++) = JsonConstants.LineFeed; + } + + while (indent-- >= 0) + { + Unsafe.Add(ref utf8Bytes, idx++) = JsonConstants.Space; + Unsafe.Add(ref utf8Bytes, idx++) = JsonConstants.Space; + } + + _output.Advance(bytesNeeded); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WriteSpacingUtf16(bool newline) + { + var indent = _indent; + var bytesNeeded = newline ? 2 : 0; + bytesNeeded += (indent + 1) * 2; + bytesNeeded *= sizeof(char); + + var buffer = EnsureBuffer(bytesNeeded); + var span = buffer.NonPortableCast(); + ref char utf16Bytes = ref span.DangerousGetPinnableReference(); + int idx = 0; + + if (newline) + { + Unsafe.Add(ref utf16Bytes, idx++) = (char)JsonConstants.CarriageReturn; + Unsafe.Add(ref utf16Bytes, idx++) = (char)JsonConstants.LineFeed; + } + + while (indent-- >= 0) + { + Unsafe.Add(ref utf16Bytes, idx++) = (char)JsonConstants.Space; + Unsafe.Add(ref utf16Bytes, idx++) = (char)JsonConstants.Space; + } + + _output.Advance(bytesNeeded); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private Span EnsureBuffer(int needed = 256) + { + // Anytime we need to request an enlarge for the buffer, we may as well ask for something + // larger than we are likely to need. + const int BufferEnlargeCount = 1024; + + var buffer = _output.Buffer; + var currentSize = buffer.Length; + if (currentSize >= needed) + return buffer; + + _output.Enlarge(BufferEnlargeCount); + buffer = _output.Buffer; + + int newSize = buffer.Length; + if (newSize < needed || newSize <= currentSize) + throw new OutOfMemoryException(); + + return buffer; + } + } +} diff --git a/src/Discord.Net.Rpc/API/Rpc/AuthenticateParams.cs b/src/Discord.Net.Rpc/API/Rpc/AuthenticateParams.cs index f4b69a3b7..10d9896d9 100644 --- a/src/Discord.Net.Rpc/API/Rpc/AuthenticateParams.cs +++ b/src/Discord.Net.Rpc/API/Rpc/AuthenticateParams.cs @@ -1,11 +1,11 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rpc { internal class AuthenticateParams { - [JsonProperty("access_token")] + [ModelProperty("access_token")] public string AccessToken { get; set; } } } diff --git a/src/Discord.Net.Rpc/API/Rpc/AuthenticateResponse.cs b/src/Discord.Net.Rpc/API/Rpc/AuthenticateResponse.cs index 6c6cba957..daf32670f 100644 --- a/src/Discord.Net.Rpc/API/Rpc/AuthenticateResponse.cs +++ b/src/Discord.Net.Rpc/API/Rpc/AuthenticateResponse.cs @@ -1,18 +1,18 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; using System; namespace Discord.API.Rpc { internal class AuthenticateResponse { - [JsonProperty("application")] + [ModelProperty("application")] public Application Application { get; set; } - [JsonProperty("expires")] + [ModelProperty("expires")] public DateTimeOffset Expires { get; set; } - [JsonProperty("user")] + [ModelProperty("user")] public User User { get; set; } - [JsonProperty("scopes")] + [ModelProperty("scopes")] public string[] Scopes { get; set; } } } diff --git a/src/Discord.Net.Rpc/API/Rpc/AuthorizeParams.cs b/src/Discord.Net.Rpc/API/Rpc/AuthorizeParams.cs index 91678d97c..a4b2a7d9d 100644 --- a/src/Discord.Net.Rpc/API/Rpc/AuthorizeParams.cs +++ b/src/Discord.Net.Rpc/API/Rpc/AuthorizeParams.cs @@ -1,16 +1,16 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; using System.Collections.Generic; namespace Discord.API.Rpc { internal class AuthorizeParams { - [JsonProperty("client_id")] + [ModelProperty("client_id")] public string ClientId { get; set; } - [JsonProperty("scopes")] + [ModelProperty("scopes")] public IReadOnlyCollection Scopes { get; set; } - [JsonProperty("rpc_token")] + [ModelProperty("rpc_token")] public Optional RpcToken { get; set; } } } diff --git a/src/Discord.Net.Rpc/API/Rpc/AuthorizeResponse.cs b/src/Discord.Net.Rpc/API/Rpc/AuthorizeResponse.cs index 42a9138fe..1ff3f7c18 100644 --- a/src/Discord.Net.Rpc/API/Rpc/AuthorizeResponse.cs +++ b/src/Discord.Net.Rpc/API/Rpc/AuthorizeResponse.cs @@ -1,11 +1,11 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rpc { internal class AuthorizeResponse { - [JsonProperty("code")] + [ModelProperty("code")] public string Code { get; set; } } } diff --git a/src/Discord.Net.Rpc/API/Rpc/Channel.cs b/src/Discord.Net.Rpc/API/Rpc/Channel.cs index 0fc7ac0ee..e88b32230 100644 --- a/src/Discord.Net.Rpc/API/Rpc/Channel.cs +++ b/src/Discord.Net.Rpc/API/Rpc/Channel.cs @@ -1,34 +1,34 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rpc { internal class Channel { //Shared - [JsonProperty("id")] + [ModelProperty("id")] public ulong Id { get; set; } - [JsonProperty("type")] + [ModelProperty("type")] public ChannelType Type { get; set; } //GuildChannel - [JsonProperty("guild_id")] + [ModelProperty("guild_id")] public Optional GuildId { get; set; } - [JsonProperty("name")] + [ModelProperty("name")] public Optional Name { get; set; } - [JsonProperty("position")] + [ModelProperty("position")] public Optional Position { get; set; } //IMessageChannel - [JsonProperty("messages")] + [ModelProperty("messages")] public Message[] Messages { get; set; } //VoiceChannel - [JsonProperty("bitrate")] + [ModelProperty("bitrate")] public Optional Bitrate { get; set; } - [JsonProperty("user_limit")] + [ModelProperty("user_limit")] public Optional UserLimit { get; set; } - [JsonProperty("voice_states")] + [ModelProperty("voice_states")] public ExtendedVoiceState[] VoiceStates { get; set; } } } diff --git a/src/Discord.Net.Rpc/API/Rpc/ChannelSubscriptionParams.cs b/src/Discord.Net.Rpc/API/Rpc/ChannelSubscriptionParams.cs index 7443a6f7d..1b7dfc4aa 100644 --- a/src/Discord.Net.Rpc/API/Rpc/ChannelSubscriptionParams.cs +++ b/src/Discord.Net.Rpc/API/Rpc/ChannelSubscriptionParams.cs @@ -1,11 +1,11 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rpc { internal class ChannelSubscriptionParams { - [JsonProperty("channel_id")] + [ModelProperty("channel_id")] public ulong ChannelId { get; set; } } } diff --git a/src/Discord.Net.Rpc/API/Rpc/ChannelSummary.cs b/src/Discord.Net.Rpc/API/Rpc/ChannelSummary.cs index 43e59c1a1..bc7d072e3 100644 --- a/src/Discord.Net.Rpc/API/Rpc/ChannelSummary.cs +++ b/src/Discord.Net.Rpc/API/Rpc/ChannelSummary.cs @@ -1,14 +1,14 @@ -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rpc { internal class ChannelSummary { - [JsonProperty("id")] + [ModelProperty("id")] public ulong Id { get; set; } - [JsonProperty("name")] + [ModelProperty("name")] public string Name { get; set; } - [JsonProperty("type")] + [ModelProperty("type")] public ChannelType Type { get; set; } } } diff --git a/src/Discord.Net.Rpc/API/Rpc/ErrorEvent.cs b/src/Discord.Net.Rpc/API/Rpc/ErrorEvent.cs index c59275154..5432eca1f 100644 --- a/src/Discord.Net.Rpc/API/Rpc/ErrorEvent.cs +++ b/src/Discord.Net.Rpc/API/Rpc/ErrorEvent.cs @@ -1,13 +1,13 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rpc { internal class ErrorEvent { - [JsonProperty("code")] + [ModelProperty("code")] public int Code { get; set; } - [JsonProperty("message")] + [ModelProperty("message")] public string Message { get; set; } } } diff --git a/src/Discord.Net.Rpc/API/Rpc/ExtendedVoiceState.cs b/src/Discord.Net.Rpc/API/Rpc/ExtendedVoiceState.cs index 6722d3a29..c56ca36b0 100644 --- a/src/Discord.Net.Rpc/API/Rpc/ExtendedVoiceState.cs +++ b/src/Discord.Net.Rpc/API/Rpc/ExtendedVoiceState.cs @@ -1,21 +1,21 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rpc { internal class ExtendedVoiceState { - [JsonProperty("user")] + [ModelProperty("user")] public User User { get; set; } - [JsonProperty("voice_state")] + [ModelProperty("voice_state")] public Optional VoiceState { get; set; } - [JsonProperty("nick")] + [ModelProperty("nick")] public Optional Nickname { get; set; } - [JsonProperty("volume")] + [ModelProperty("volume")] public Optional Volume { get; set; } - [JsonProperty("mute")] + [ModelProperty("mute")] public Optional Mute { get; set; } - [JsonProperty("pan")] + [ModelProperty("pan")] public Optional Pan { get; set; } } } diff --git a/src/Discord.Net.Rpc/API/Rpc/GetChannelParams.cs b/src/Discord.Net.Rpc/API/Rpc/GetChannelParams.cs index 4c0e18600..7e94db8ec 100644 --- a/src/Discord.Net.Rpc/API/Rpc/GetChannelParams.cs +++ b/src/Discord.Net.Rpc/API/Rpc/GetChannelParams.cs @@ -1,11 +1,11 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rpc { internal class GetChannelParams { - [JsonProperty("channel_id")] + [ModelProperty("channel_id")] public ulong ChannelId { get; set; } } } diff --git a/src/Discord.Net.Rpc/API/Rpc/GetChannelsParams.cs b/src/Discord.Net.Rpc/API/Rpc/GetChannelsParams.cs index 61e4886ef..481b8f8ef 100644 --- a/src/Discord.Net.Rpc/API/Rpc/GetChannelsParams.cs +++ b/src/Discord.Net.Rpc/API/Rpc/GetChannelsParams.cs @@ -1,11 +1,11 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rpc { internal class GetChannelsParams { - [JsonProperty("guild_id")] + [ModelProperty("guild_id")] public ulong GuildId { get; set; } } } diff --git a/src/Discord.Net.Rpc/API/Rpc/GetChannelsResponse.cs b/src/Discord.Net.Rpc/API/Rpc/GetChannelsResponse.cs index 004da31b5..adc60f693 100644 --- a/src/Discord.Net.Rpc/API/Rpc/GetChannelsResponse.cs +++ b/src/Discord.Net.Rpc/API/Rpc/GetChannelsResponse.cs @@ -1,12 +1,12 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; using System.Collections.Generic; namespace Discord.API.Rpc { internal class GetChannelsResponse { - [JsonProperty("channels")] + [ModelProperty("channels")] public IReadOnlyCollection Channels { get; set; } } } diff --git a/src/Discord.Net.Rpc/API/Rpc/GetGuildParams.cs b/src/Discord.Net.Rpc/API/Rpc/GetGuildParams.cs index 54d5018d0..8fafbaf39 100644 --- a/src/Discord.Net.Rpc/API/Rpc/GetGuildParams.cs +++ b/src/Discord.Net.Rpc/API/Rpc/GetGuildParams.cs @@ -1,11 +1,11 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rpc { internal class GetGuildParams { - [JsonProperty("guild_id")] + [ModelProperty("guild_id")] public ulong GuildId { get; set; } } } diff --git a/src/Discord.Net.Rpc/API/Rpc/GetGuildsResponse.cs b/src/Discord.Net.Rpc/API/Rpc/GetGuildsResponse.cs index 4d57ae491..7f97e04d3 100644 --- a/src/Discord.Net.Rpc/API/Rpc/GetGuildsResponse.cs +++ b/src/Discord.Net.Rpc/API/Rpc/GetGuildsResponse.cs @@ -1,11 +1,11 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rpc { internal class GetGuildsResponse { - [JsonProperty("guilds")] + [ModelProperty("guilds")] public GuildSummary[] Guilds { get; set; } } } diff --git a/src/Discord.Net.Rpc/API/Rpc/Guild.cs b/src/Discord.Net.Rpc/API/Rpc/Guild.cs index fde5ef2ee..9ddda173c 100644 --- a/src/Discord.Net.Rpc/API/Rpc/Guild.cs +++ b/src/Discord.Net.Rpc/API/Rpc/Guild.cs @@ -1,18 +1,18 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; using System.Collections.Generic; namespace Discord.API.Rpc { internal class Guild { - [JsonProperty("id")] + [ModelProperty("id")] public ulong Id { get; set; } - [JsonProperty("name")] + [ModelProperty("name")] public string Name { get; set; } - [JsonProperty("icon_url")] + [ModelProperty("icon_url")] public string IconUrl { get; set; } - [JsonProperty("members")] + [ModelProperty("members")] public IEnumerable Members { get; set; } } } diff --git a/src/Discord.Net.Rpc/API/Rpc/GuildMember.cs b/src/Discord.Net.Rpc/API/Rpc/GuildMember.cs index be8fba9dc..24c71bad3 100644 --- a/src/Discord.Net.Rpc/API/Rpc/GuildMember.cs +++ b/src/Discord.Net.Rpc/API/Rpc/GuildMember.cs @@ -1,15 +1,15 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rpc { internal class GuildMember { - [JsonProperty("user")] + [ModelProperty("user")] public User User { get; set; } - [JsonProperty("status")] + [ModelProperty("status")] public UserStatus Status { get; set; } - /*[JsonProperty("activity")] + /*[ModelProperty("activity")] public object Activity { get; set; }*/ } } diff --git a/src/Discord.Net.Rpc/API/Rpc/GuildStatusEvent.cs b/src/Discord.Net.Rpc/API/Rpc/GuildStatusEvent.cs index 3cfbf3454..730ebd636 100644 --- a/src/Discord.Net.Rpc/API/Rpc/GuildStatusEvent.cs +++ b/src/Discord.Net.Rpc/API/Rpc/GuildStatusEvent.cs @@ -1,13 +1,13 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rpc { internal class GuildStatusEvent { - [JsonProperty("guild")] + [ModelProperty("guild")] public Guild Guild { get; set; } - [JsonProperty("online")] + [ModelProperty("online")] public int Online { get; set; } } } diff --git a/src/Discord.Net.Rpc/API/Rpc/GuildSubscriptionParams.cs b/src/Discord.Net.Rpc/API/Rpc/GuildSubscriptionParams.cs index a34c71023..1c92193dd 100644 --- a/src/Discord.Net.Rpc/API/Rpc/GuildSubscriptionParams.cs +++ b/src/Discord.Net.Rpc/API/Rpc/GuildSubscriptionParams.cs @@ -1,11 +1,11 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rpc { internal class GuildSubscriptionParams { - [JsonProperty("guild_id")] + [ModelProperty("guild_id")] public ulong GuildId { get; set; } } } diff --git a/src/Discord.Net.Rpc/API/Rpc/GuildSummary.cs b/src/Discord.Net.Rpc/API/Rpc/GuildSummary.cs index 09928e16e..abfed2889 100644 --- a/src/Discord.Net.Rpc/API/Rpc/GuildSummary.cs +++ b/src/Discord.Net.Rpc/API/Rpc/GuildSummary.cs @@ -1,12 +1,12 @@ -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rpc { internal class GuildSummary { - [JsonProperty("id")] + [ModelProperty("id")] public ulong Id { get; set; } - [JsonProperty("name")] + [ModelProperty("name")] public string Name { get; set; } } } diff --git a/src/Discord.Net.Rpc/API/Rpc/Message.cs b/src/Discord.Net.Rpc/API/Rpc/Message.cs index 6cbd364bb..6fdaecedb 100644 --- a/src/Discord.Net.Rpc/API/Rpc/Message.cs +++ b/src/Discord.Net.Rpc/API/Rpc/Message.cs @@ -1,17 +1,17 @@ -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rpc { internal class Message : Discord.API.Message { - [JsonProperty("blocked")] + [ModelProperty("blocked")] public Optional IsBlocked { get; } - [JsonProperty("content_parsed")] + [ModelProperty("content_parsed")] public Optional ContentParsed { get; } - [JsonProperty("author_color")] + [ModelProperty("author_color")] public Optional AuthorColor { get; } //#Hex - [JsonProperty("mentions")] + [ModelProperty("mentions")] public new Optional UserMentions { get; set; } } } diff --git a/src/Discord.Net.Rpc/API/Rpc/MessageEvent.cs b/src/Discord.Net.Rpc/API/Rpc/MessageEvent.cs index 4d656d5e3..e9694ceb9 100644 --- a/src/Discord.Net.Rpc/API/Rpc/MessageEvent.cs +++ b/src/Discord.Net.Rpc/API/Rpc/MessageEvent.cs @@ -1,12 +1,12 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rpc { internal class MessageEvent { - [JsonProperty("channel_id")] + [ModelProperty("channel_id")] public ulong ChannelId { get; set; } - [JsonProperty("message")] + [ModelProperty("message")] public Message Message { get; set; } } } diff --git a/src/Discord.Net.Rpc/API/Rpc/Pan.cs b/src/Discord.Net.Rpc/API/Rpc/Pan.cs index dc9cbef0a..ebc6f9668 100644 --- a/src/Discord.Net.Rpc/API/Rpc/Pan.cs +++ b/src/Discord.Net.Rpc/API/Rpc/Pan.cs @@ -1,12 +1,12 @@ -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rpc { internal class Pan { - [JsonProperty("left")] + [ModelProperty("left")] public float Left { get; set; } - [JsonProperty("right")] + [ModelProperty("right")] public float Right { get; set; } } } diff --git a/src/Discord.Net.Rpc/API/Rpc/ReadyEvent.cs b/src/Discord.Net.Rpc/API/Rpc/ReadyEvent.cs index 8de69405f..72ca6e0eb 100644 --- a/src/Discord.Net.Rpc/API/Rpc/ReadyEvent.cs +++ b/src/Discord.Net.Rpc/API/Rpc/ReadyEvent.cs @@ -1,13 +1,13 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rpc { internal class ReadyEvent { - [JsonProperty("v")] + [ModelProperty("v")] public int Version { get; set; } - [JsonProperty("config")] + [ModelProperty("config")] public RpcConfig Config { get; set; } } } diff --git a/src/Discord.Net.Rpc/API/Rpc/RpcConfig.cs b/src/Discord.Net.Rpc/API/Rpc/RpcConfig.cs index 4a8928a0d..b37c3ee41 100644 --- a/src/Discord.Net.Rpc/API/Rpc/RpcConfig.cs +++ b/src/Discord.Net.Rpc/API/Rpc/RpcConfig.cs @@ -1,15 +1,15 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rpc { internal class RpcConfig { - [JsonProperty("cdn_host")] + [ModelProperty("cdn_host")] public string CdnHost { get; set; } - [JsonProperty("api_endpoint")] + [ModelProperty("api_endpoint")] public string ApiEndpoint { get; set; } - [JsonProperty("environment")] + [ModelProperty("environment")] public string Environment { get; set; } } } diff --git a/src/Discord.Net.Rpc/API/Rpc/SelectChannelParams.cs b/src/Discord.Net.Rpc/API/Rpc/SelectChannelParams.cs index 6fc9314a3..55461e70d 100644 --- a/src/Discord.Net.Rpc/API/Rpc/SelectChannelParams.cs +++ b/src/Discord.Net.Rpc/API/Rpc/SelectChannelParams.cs @@ -1,13 +1,13 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rpc { internal class SelectChannelParams { - [JsonProperty("channel_id")] + [ModelProperty("channel_id")] public ulong? ChannelId { get; set; } - [JsonProperty("force")] + [ModelProperty("force")] public Optional Force { get; set; } } } diff --git a/src/Discord.Net.Rpc/API/Rpc/SetLocalVolumeParams.cs b/src/Discord.Net.Rpc/API/Rpc/SetLocalVolumeParams.cs index 345ad906d..f0b6ad154 100644 --- a/src/Discord.Net.Rpc/API/Rpc/SetLocalVolumeParams.cs +++ b/src/Discord.Net.Rpc/API/Rpc/SetLocalVolumeParams.cs @@ -1,11 +1,11 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rpc { internal class SetLocalVolumeParams { - [JsonProperty("volume")] + [ModelProperty("volume")] public int Volume { get; set; } } } diff --git a/src/Discord.Net.Rpc/API/Rpc/SetLocalVolumeResponse.cs b/src/Discord.Net.Rpc/API/Rpc/SetLocalVolumeResponse.cs index 33927b7d9..471f9e963 100644 --- a/src/Discord.Net.Rpc/API/Rpc/SetLocalVolumeResponse.cs +++ b/src/Discord.Net.Rpc/API/Rpc/SetLocalVolumeResponse.cs @@ -1,13 +1,13 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rpc { internal class SetLocalVolumeResponse { - [JsonProperty("user_id")] + [ModelProperty("user_id")] public ulong UserId { get; set; } - [JsonProperty("volume")] + [ModelProperty("volume")] public int Volume { get; set; } } } diff --git a/src/Discord.Net.Rpc/API/Rpc/SpeakingEvent.cs b/src/Discord.Net.Rpc/API/Rpc/SpeakingEvent.cs index 913d7d768..1dde8cd48 100644 --- a/src/Discord.Net.Rpc/API/Rpc/SpeakingEvent.cs +++ b/src/Discord.Net.Rpc/API/Rpc/SpeakingEvent.cs @@ -1,11 +1,11 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rpc { internal class SpeakingEvent { - [JsonProperty("user_id")] + [ModelProperty("user_id")] public ulong UserId { get; set; } } } diff --git a/src/Discord.Net.Rpc/API/Rpc/SubscriptionResponse.cs b/src/Discord.Net.Rpc/API/Rpc/SubscriptionResponse.cs index 76adc8d1b..46c5fac70 100644 --- a/src/Discord.Net.Rpc/API/Rpc/SubscriptionResponse.cs +++ b/src/Discord.Net.Rpc/API/Rpc/SubscriptionResponse.cs @@ -1,11 +1,11 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rpc { internal class SubscriptionResponse { - [JsonProperty("evt")] + [ModelProperty("evt")] public string Event { get; set; } } } diff --git a/src/Discord.Net.Rpc/API/Rpc/UserVoiceSettings.cs b/src/Discord.Net.Rpc/API/Rpc/UserVoiceSettings.cs index ff338a4a4..702d8ebe2 100644 --- a/src/Discord.Net.Rpc/API/Rpc/UserVoiceSettings.cs +++ b/src/Discord.Net.Rpc/API/Rpc/UserVoiceSettings.cs @@ -1,18 +1,18 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rpc { internal class UserVoiceSettings { - [JsonProperty("userId")] + [ModelProperty("userId")] internal ulong UserId { get; set; } - [JsonProperty("pan")] + [ModelProperty("pan")] public Optional Pan { get; set; } - [JsonProperty("volume")] + [ModelProperty("volume")] public Optional Volume { get; set; } - [JsonProperty("mute")] + [ModelProperty("mute")] public Optional Mute { get; set; } } } diff --git a/src/Discord.Net.Rpc/API/Rpc/VoiceDevice.cs b/src/Discord.Net.Rpc/API/Rpc/VoiceDevice.cs index 52bdef8a3..c0078cd6f 100644 --- a/src/Discord.Net.Rpc/API/Rpc/VoiceDevice.cs +++ b/src/Discord.Net.Rpc/API/Rpc/VoiceDevice.cs @@ -1,12 +1,12 @@ -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rpc { internal class VoiceDevice { - [JsonProperty("id")] + [ModelProperty("id")] public string Id { get; set; } - [JsonProperty("name")] + [ModelProperty("name")] public string Name { get; set; } } } diff --git a/src/Discord.Net.Rpc/API/Rpc/VoiceDeviceSettings.cs b/src/Discord.Net.Rpc/API/Rpc/VoiceDeviceSettings.cs index c06eb2c02..35f9b42b7 100644 --- a/src/Discord.Net.Rpc/API/Rpc/VoiceDeviceSettings.cs +++ b/src/Discord.Net.Rpc/API/Rpc/VoiceDeviceSettings.cs @@ -1,14 +1,14 @@ -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rpc { internal class VoiceDeviceSettings { - [JsonProperty("device_id")] + [ModelProperty("device_id")] public Optional DeviceId { get; set; } - [JsonProperty("volume")] + [ModelProperty("volume")] public Optional Volume { get; set; } - [JsonProperty("available_devices")] + [ModelProperty("available_devices")] public Optional AvailableDevices { get; set; } } } diff --git a/src/Discord.Net.Rpc/API/Rpc/VoiceMode.cs b/src/Discord.Net.Rpc/API/Rpc/VoiceMode.cs index 35e9d453e..cfb403419 100644 --- a/src/Discord.Net.Rpc/API/Rpc/VoiceMode.cs +++ b/src/Discord.Net.Rpc/API/Rpc/VoiceMode.cs @@ -1,18 +1,18 @@ -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rpc { internal class VoiceMode { - [JsonProperty("type")] + [ModelProperty("type")] public Optional Type { get; set; } - [JsonProperty("auto_threshold")] + [ModelProperty("auto_threshold")] public Optional AutoThreshold { get; set; } - [JsonProperty("threshold")] + [ModelProperty("threshold")] public Optional Threshold { get; set; } - [JsonProperty("shortcut")] + [ModelProperty("shortcut")] public Optional Shortcut { get; set; } - [JsonProperty("delay")] + [ModelProperty("delay")] public Optional Delay { get; set; } } } diff --git a/src/Discord.Net.Rpc/API/Rpc/VoiceSettings.cs b/src/Discord.Net.Rpc/API/Rpc/VoiceSettings.cs index 11fb3b6a8..c504ea802 100644 --- a/src/Discord.Net.Rpc/API/Rpc/VoiceSettings.cs +++ b/src/Discord.Net.Rpc/API/Rpc/VoiceSettings.cs @@ -1,26 +1,26 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rpc { internal class VoiceSettings { - [JsonProperty("input")] + [ModelProperty("input")] public VoiceDeviceSettings Input { get; set; } - [JsonProperty("output")] + [ModelProperty("output")] public VoiceDeviceSettings Output { get; set; } - [JsonProperty("mode")] + [ModelProperty("mode")] public VoiceMode Mode { get; set; } - [JsonProperty("automatic_gain_control")] + [ModelProperty("automatic_gain_control")] public Optional AutomaticGainControl { get; set; } - [JsonProperty("echo_cancellation")] + [ModelProperty("echo_cancellation")] public Optional EchoCancellation { get; set; } - [JsonProperty("noise_suppression")] + [ModelProperty("noise_suppression")] public Optional NoiseSuppression { get; set; } - [JsonProperty("qos")] + [ModelProperty("qos")] public Optional QualityOfService { get; set; } - [JsonProperty("silence_warning")] + [ModelProperty("silence_warning")] public Optional SilenceWarning { get; set; } } } diff --git a/src/Discord.Net.Rpc/API/Rpc/VoiceShortcut.cs b/src/Discord.Net.Rpc/API/Rpc/VoiceShortcut.cs index 65e258033..749ae39bc 100644 --- a/src/Discord.Net.Rpc/API/Rpc/VoiceShortcut.cs +++ b/src/Discord.Net.Rpc/API/Rpc/VoiceShortcut.cs @@ -1,15 +1,15 @@ using Discord.Rpc; -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Rpc { internal class VoiceShortcut { - [JsonProperty("type")] + [ModelProperty("type")] public Optional Type { get; set; } - [JsonProperty("code")] + [ModelProperty("code")] public Optional Code { get; set; } - [JsonProperty("name")] + [ModelProperty("name")] public Optional Name { get; set; } } } diff --git a/src/Discord.Net.Rpc/API/RpcFrame.cs b/src/Discord.Net.Rpc/API/RpcFrame.cs index 523378b04..0bdd9f809 100644 --- a/src/Discord.Net.Rpc/API/RpcFrame.cs +++ b/src/Discord.Net.Rpc/API/RpcFrame.cs @@ -1,20 +1,20 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; using System; namespace Discord.API.Rpc { internal class RpcFrame { - [JsonProperty("cmd")] + [ModelProperty("cmd")] public string Cmd { get; set; } - [JsonProperty("nonce")] + [ModelProperty("nonce")] public Optional Nonce { get; set; } - [JsonProperty("evt")] + [ModelProperty("evt")] public Optional Event { get; set; } - [JsonProperty("data")] - public Optional Data { get; set; } - [JsonProperty("args")] + [ModelProperty("data")] + public Optional> Data { get; set; } + [ModelProperty("args")] public object Args { get; set; } } } diff --git a/src/Discord.Net.Rpc/DiscordRpcApiClient.cs b/src/Discord.Net.Rpc/DiscordRpcApiClient.cs index 019dbd635..f669fc642 100644 --- a/src/Discord.Net.Rpc/DiscordRpcApiClient.cs +++ b/src/Discord.Net.Rpc/DiscordRpcApiClient.cs @@ -5,14 +5,13 @@ using Discord.Net.Rest; using Discord.Net.WebSockets; using Discord.Rpc; using Discord.Serialization; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Text; +using System.Text.Formatting; using System.Threading; using System.Threading.Tasks; @@ -22,11 +21,12 @@ namespace Discord.API { private interface IRpcRequest { - Task SetResultAsync(JToken data); - Task SetExceptionAsync(JToken data); + Task SetResultAsync(ReadOnlyBuffer data); + Task SetExceptionAsync(ReadOnlyBuffer data); } private class RpcRequest : IRpcRequest + where T : class, new() { public TaskCompletionSource Promise { get; set; } @@ -39,13 +39,13 @@ namespace Discord.API Promise.TrySetCanceled(); //Doesn't need to be async, we're already in a separate task }); } - public Task SetResultAsync(JToken data) + public Task SetResultAsync(ReadOnlyBuffer data) { - return Promise.TrySetResultAsync(Serializer.FromJson(data)); + return Promise.TrySetResultAsync(Serializer.ReadJson(data)); } - public Task SetExceptionAsync(JToken data) + public Task SetExceptionAsync(ReadOnlyBuffer data) { - var error = Serializer.FromJson(data); + var error = Serializer.ReadJson(data); return Promise.TrySetExceptionAsync(new RpcException(error.Code, error.Message)); } } @@ -55,8 +55,8 @@ namespace Discord.API public event Func SentRpcMessage { add { _sentRpcMessageEvent.Add(value); } remove { _sentRpcMessageEvent.Remove(value); } } private readonly AsyncEvent> _sentRpcMessageEvent = new AsyncEvent>(); - public event Func, Optional, Task> ReceivedRpcEvent { add { _receivedRpcEvent.Add(value); } remove { _receivedRpcEvent.Remove(value); } } - private readonly AsyncEvent, Optional, Task>> _receivedRpcEvent = new AsyncEvent, Optional, Task>>(); + public event Func, Optional>, Task> ReceivedRpcEvent { add { _receivedRpcEvent.Add(value); } remove { _receivedRpcEvent.Remove(value); } } + private readonly AsyncEvent, Optional>, Task>> _receivedRpcEvent = new AsyncEvent, Optional>, Task>>(); public event Func Disconnected { add { _disconnectedEvent.Add(value); } remove { _disconnectedEvent.Remove(value); } } private readonly AsyncEvent> _disconnectedEvent = new AsyncEvent>(); @@ -71,9 +71,9 @@ namespace Discord.API public ConnectionState ConnectionState { get; private set; } - public DiscordRpcApiClient(string clientId, string userAgent, string origin, RestClientProvider restClientProvider, WebSocketProvider webSocketProvider, - RetryMode defaultRetryMode = RetryMode.AlwaysRetry) - : base(restClientProvider, userAgent, defaultRetryMode) + public DiscordRpcApiClient(string clientId, string userAgent, string origin, RestClientProvider restClientProvider, WebSocketProvider webSocketProvider, + ScopedSerializer serializer, RetryMode defaultRetryMode = RetryMode.AlwaysRetry) + : base(restClientProvider, userAgent, serializer, defaultRetryMode) { _connectionLock = new SemaphoreSlim(1, 1); _clientId = clientId; @@ -87,30 +87,30 @@ namespace Discord.API _webSocketClient = webSocketProvider(); //_webSocketClient.SetHeader("user-agent", DiscordConfig.UserAgent); (Causes issues in .Net 4.6+) _webSocketClient.SetHeader("origin", _origin); - _webSocketClient.BinaryMessage += async (data, index, count) => + _webSocketClient.Message += async (data, isText) => { - using (var compressed = new MemoryStream(data, index + 2, count - 2)) + if (!isText) { - _decompressionStream.Position = 0; - using (var zlib = new DeflateStream(compressed, CompressionMode.Decompress)) - zlib.CopyTo(_decompressionStream); - _decompressionStream.SetLength(_decompressionStream.Position); - - _decompressionStream.Position = 0; - var msg = _serializer.FromJson(_decompressionReader); - if (msg != null) + using (var compressed = new MemoryStream(data.ToArray(), 2, data.Length - 2)) { - await _receivedRpcEvent.InvokeAsync(msg.Cmd, msg.Event, msg.Data).ConfigureAwait(false); - if (msg.Nonce.IsSpecified && msg.Nonce.Value.HasValue) - ProcessMessage(msg); + _decompressionStream.Position = 0; + using (var zlib = new DeflateStream(compressed, CompressionMode.Decompress)) + zlib.CopyTo(_decompressionStream); + _decompressionStream.SetLength(_decompressionStream.Position); + + _decompressionStream.Position = 0; + var msg = _serializer.ReadJson(_decompressionStream.ToReadOnlyBuffer()); + if (msg != null) + { + await _receivedRpcEvent.InvokeAsync(msg.Cmd, msg.Event, msg.Data).ConfigureAwait(false); + if (msg.Nonce.IsSpecified && msg.Nonce.Value.HasValue) + ProcessMessage(msg); + } } } - }; - _webSocketClient.TextMessage += async text => - { - using (var reader = new StringReader(text)) + else { - var msg = _serializer.FromJson(reader); + var msg = _serializer.ReadJson(data); if (msg != null) { await _receivedRpcEvent.InvokeAsync(msg.Cmd, msg.Event, msg.Data).ConfigureAwait(false); @@ -216,29 +216,35 @@ namespace Discord.API } //Core - public async Task SendRpcAsync(string cmd, object payload, Optional evt = default(Optional), RequestOptions options = null) - where TResponse : class + public async Task SendRpcAsync(string cmd, object payload, Optional evt = default, RequestOptions options = null) + where TResponse : class, new() { return await SendRpcAsyncInternal(cmd, payload, evt, options).ConfigureAwait(false); } private async Task SendRpcAsyncInternal(string cmd, object payload, Optional evt, RequestOptions options) - where TResponse : class + where TResponse : class, new() { - byte[] bytes = null; - var guid = Guid.NewGuid(); - payload = new API.Rpc.RpcFrame { Cmd = cmd, Event = evt, Args = payload, Nonce = guid }; - if (payload != null) + if (_formatters.TryDequeue(out var data1)) + data1 = new ArrayFormatter(128, SymbolTable.InvariantUtf8); + if (_formatters.TryDequeue(out var data2)) + data2 = new ArrayFormatter(128, SymbolTable.InvariantUtf8); + try { - string json = SerializeJson(payload); - bytes = Encoding.UTF8.GetBytes(json); - } + var guid = Guid.NewGuid(); + payload = new API.Rpc.RpcFrame { Cmd = cmd, Event = evt, Args = SerializeJson(data1, payload), Nonce = guid }; - var requestTracker = new RpcRequest(options); - _requests[guid] = requestTracker; + var requestTracker = new RpcRequest(options); + _requests[guid] = requestTracker; - await RequestQueue.SendAsync(new WebSocketRequest(_webSocketClient, null, bytes, true, options)).ConfigureAwait(false); - await _sentRpcMessageEvent.InvokeAsync(cmd).ConfigureAwait(false); - return await requestTracker.Promise.Task.ConfigureAwait(false); + await RequestQueue.SendAsync(new WebSocketRequest(_webSocketClient, null, SerializeJson(data2, payload), true, options)).ConfigureAwait(false); + await _sentRpcMessageEvent.InvokeAsync(cmd).ConfigureAwait(false); + return await requestTracker.Promise.Task.ConfigureAwait(false); + } + finally + { + _formatters.Enqueue(data1); + _formatters.Enqueue(data2); + } } //Rpc @@ -392,11 +398,11 @@ namespace Discord.API { if (msg.Event.GetValueOrDefault("") == "ERROR") { - var _ = requestTracker.SetExceptionAsync(msg.Data.GetValueOrDefault() as JToken); + var _ = requestTracker.SetExceptionAsync(msg.Data.GetValueOrDefault()); } else { - var _ = requestTracker.SetResultAsync(msg.Data.GetValueOrDefault() as JToken); + var _ = requestTracker.SetResultAsync(msg.Data.GetValueOrDefault()); } return true; } diff --git a/src/Discord.Net.Rpc/DiscordRpcClient.cs b/src/Discord.Net.Rpc/DiscordRpcClient.cs index dba477808..ac34ab81f 100644 --- a/src/Discord.Net.Rpc/DiscordRpcClient.cs +++ b/src/Discord.Net.Rpc/DiscordRpcClient.cs @@ -1,7 +1,6 @@ using Discord.API.Rpc; using Discord.Logging; using Discord.Rest; -using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -32,7 +31,7 @@ namespace Discord.Rpc : this(clientId, origin, new DiscordRpcConfig()) { } /// Creates a new RPC discord client. public DiscordRpcClient(string clientId, string origin, DiscordRpcConfig config) - : base(config, CreateApiClient(clientId, origin, config)) + : base(config) { _stateLock = new SemaphoreSlim(1, 1); _authorizeLock = new SemaphoreSlim(1, 1); @@ -44,17 +43,16 @@ namespace Discord.Rpc await _rpcLogger.WarningAsync("Serializer Error", ex); }; + SetApiClient(new API.DiscordRpcApiClient(clientId, DiscordRestConfig.UserAgent, origin, config.RestClientProvider, config.WebSocketProvider, _serializer)); + ApiClient.SentRpcMessage += async opCode => await _rpcLogger.DebugAsync($"Sent {opCode}").ConfigureAwait(false); + ApiClient.ReceivedRpcEvent += ProcessMessageAsync; + _connection = new ConnectionManager(_stateLock, _rpcLogger, config.ConnectionTimeout, OnConnectingAsync, OnDisconnectingAsync, x => ApiClient.Disconnected += x); _connection.Connected += () => _connectedEvent.InvokeAsync(); _connection.Disconnected += (ex, recon) => _disconnectedEvent.InvokeAsync(ex); - - ApiClient.SentRpcMessage += async opCode => await _rpcLogger.DebugAsync($"Sent {opCode}").ConfigureAwait(false); - ApiClient.ReceivedRpcEvent += ProcessMessageAsync; } - - private static API.DiscordRpcApiClient CreateApiClient(string clientId, string origin, DiscordRpcConfig config) - => new API.DiscordRpcApiClient(clientId, DiscordRestConfig.UserAgent, origin, config.RestClientProvider, config.WebSocketProvider); + internal override void Dispose(bool disposing) { if (disposing) @@ -282,7 +280,7 @@ namespace Discord.Rpc } } - private async Task ProcessMessageAsync(string cmd, Optional evnt, Optional payload) + private async Task ProcessMessageAsync(string cmd, Optional evnt, Optional> payload) { try { @@ -295,7 +293,7 @@ namespace Discord.Rpc case "READY": { await _rpcLogger.DebugAsync("Received Dispatch (READY)").ConfigureAwait(false); - var data = _serializer.FromJson(payload.Value as JToken); + var data = _serializer.ReadJson(payload.Value); var options = new RequestOptions { @@ -337,7 +335,7 @@ namespace Discord.Rpc case "CHANNEL_CREATE": { await _rpcLogger.DebugAsync("Received Dispatch (CHANNEL_CREATE)").ConfigureAwait(false); - var data = _serializer.FromJson(payload.Value as JToken); + var data = _serializer.ReadJson(payload.Value); var channel = RpcChannelSummary.Create(data); await _channelCreatedEvent.InvokeAsync(channel).ConfigureAwait(false); @@ -348,7 +346,7 @@ namespace Discord.Rpc case "GUILD_CREATE": { await _rpcLogger.DebugAsync("Received Dispatch (GUILD_CREATE)").ConfigureAwait(false); - var data = _serializer.FromJson(payload.Value as JToken); + var data = _serializer.ReadJson(payload.Value); var guild = RpcGuildSummary.Create(data); await _guildCreatedEvent.InvokeAsync(guild).ConfigureAwait(false); @@ -357,7 +355,7 @@ namespace Discord.Rpc case "GUILD_STATUS": { await _rpcLogger.DebugAsync("Received Dispatch (GUILD_STATUS)").ConfigureAwait(false); - var data = _serializer.FromJson(payload.Value as JToken); + var data = _serializer.ReadJson(payload.Value); var guildStatus = RpcGuildStatus.Create(data); await _guildStatusUpdatedEvent.InvokeAsync(guildStatus).ConfigureAwait(false); @@ -368,7 +366,7 @@ namespace Discord.Rpc case "VOICE_STATE_CREATE": { await _rpcLogger.DebugAsync("Received Dispatch (VOICE_STATE_CREATE)").ConfigureAwait(false); - var data = _serializer.FromJson(payload.Value as JToken); + var data = _serializer.ReadJson(payload.Value); var voiceState = RpcVoiceState.Create(this, data); await _voiceStateCreatedEvent.InvokeAsync(voiceState).ConfigureAwait(false); @@ -377,7 +375,7 @@ namespace Discord.Rpc case "VOICE_STATE_UPDATE": { await _rpcLogger.DebugAsync("Received Dispatch (VOICE_STATE_UPDATE)").ConfigureAwait(false); - var data = _serializer.FromJson(payload.Value as JToken); + var data = _serializer.ReadJson(payload.Value); var voiceState = RpcVoiceState.Create(this, data); await _voiceStateUpdatedEvent.InvokeAsync(voiceState).ConfigureAwait(false); @@ -386,7 +384,7 @@ namespace Discord.Rpc case "VOICE_STATE_DELETE": { await _rpcLogger.DebugAsync("Received Dispatch (VOICE_STATE_DELETE)").ConfigureAwait(false); - var data = _serializer.FromJson(payload.Value as JToken); + var data = _serializer.ReadJson(payload.Value); var voiceState = RpcVoiceState.Create(this, data); await _voiceStateDeletedEvent.InvokeAsync(voiceState).ConfigureAwait(false); @@ -396,7 +394,7 @@ namespace Discord.Rpc case "SPEAKING_START": { await _rpcLogger.DebugAsync("Received Dispatch (SPEAKING_START)").ConfigureAwait(false); - var data = _serializer.FromJson(payload.Value as JToken); + var data = _serializer.ReadJson(payload.Value); await _speakingStartedEvent.InvokeAsync(data.UserId).ConfigureAwait(false); } @@ -404,7 +402,7 @@ namespace Discord.Rpc case "SPEAKING_STOP": { await _rpcLogger.DebugAsync("Received Dispatch (SPEAKING_STOP)").ConfigureAwait(false); - var data = _serializer.FromJson(payload.Value as JToken); + var data = _serializer.ReadJson(payload.Value); await _speakingStoppedEvent.InvokeAsync(data.UserId).ConfigureAwait(false); } @@ -412,7 +410,7 @@ namespace Discord.Rpc case "VOICE_SETTINGS_UPDATE": { await _rpcLogger.DebugAsync("Received Dispatch (VOICE_SETTINGS_UPDATE)").ConfigureAwait(false); - var data = _serializer.FromJson(payload.Value as JToken); + var data = _serializer.ReadJson(payload.Value); var settings = VoiceSettings.Create(data); await _voiceSettingsUpdated.InvokeAsync(settings).ConfigureAwait(false); @@ -423,7 +421,7 @@ namespace Discord.Rpc case "MESSAGE_CREATE": { await _rpcLogger.DebugAsync("Received Dispatch (MESSAGE_CREATE)").ConfigureAwait(false); - var data = _serializer.FromJson(payload.Value as JToken); + var data = _serializer.ReadJson(payload.Value); var msg = RpcMessage.Create(this, data.ChannelId, data.Message); await _messageReceivedEvent.InvokeAsync(msg).ConfigureAwait(false); @@ -432,7 +430,7 @@ namespace Discord.Rpc case "MESSAGE_UPDATE": { await _rpcLogger.DebugAsync("Received Dispatch (MESSAGE_UPDATE)").ConfigureAwait(false); - var data = _serializer.FromJson(payload.Value as JToken); + var data = _serializer.ReadJson(payload.Value); var msg = RpcMessage.Create(this, data.ChannelId, data.Message); await _messageUpdatedEvent.InvokeAsync(msg).ConfigureAwait(false); @@ -441,7 +439,7 @@ namespace Discord.Rpc case "MESSAGE_DELETE": { await _rpcLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE)").ConfigureAwait(false); - var data = _serializer.FromJson(payload.Value as JToken); + var data = _serializer.ReadJson(payload.Value); await _messageDeletedEvent.InvokeAsync(data.ChannelId, data.Message.Id).ConfigureAwait(false); } diff --git a/src/Discord.Net.Rpc/Entities/UserVoiceProperties.cs b/src/Discord.Net.Rpc/Entities/UserVoiceProperties.cs index 830ba16a3..17c4878da 100644 --- a/src/Discord.Net.Rpc/Entities/UserVoiceProperties.cs +++ b/src/Discord.Net.Rpc/Entities/UserVoiceProperties.cs @@ -1,18 +1,18 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.Rpc { public class UserVoiceProperties { - [JsonProperty("userId")] + [ModelProperty("userId")] internal ulong UserId { get; set; } - [JsonProperty("pan")] + [ModelProperty("pan")] public Optional Pan { get; set; } - [JsonProperty("volume")] + [ModelProperty("volume")] public Optional Volume { get; set; } - [JsonProperty("mute")] + [ModelProperty("mute")] public Optional Mute { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/ExtendedGuild.cs b/src/Discord.Net.WebSocket/API/Gateway/ExtendedGuild.cs index 910f6d909..7c4fa7a49 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/ExtendedGuild.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/ExtendedGuild.cs @@ -1,25 +1,25 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; using System; namespace Discord.API.Gateway { internal class ExtendedGuild : Guild { - [JsonProperty("unavailable")] + [ModelProperty("unavailable")] public bool? Unavailable { get; set; } - [JsonProperty("member_count")] + [ModelProperty("member_count")] public int MemberCount { get; set; } - [JsonProperty("large")] + [ModelProperty("large")] public bool Large { get; set; } - [JsonProperty("presences")] + [ModelProperty("presences")] public Presence[] Presences { get; set; } - [JsonProperty("members")] + [ModelProperty("members")] public GuildMember[] Members { get; set; } - [JsonProperty("channels")] + [ModelProperty("channels")] public Channel[] Channels { get; set; } - [JsonProperty("joined_at")] + [ModelProperty("joined_at")] public DateTimeOffset JoinedAt { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/GuildBanEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/GuildBanEvent.cs index 59a3304dd..8897beb95 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/GuildBanEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/GuildBanEvent.cs @@ -1,13 +1,13 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Gateway { internal class GuildBanEvent { - [JsonProperty("guild_id")] + [ModelProperty("guild_id")] public ulong GuildId { get; set; } - [JsonProperty("user")] + [ModelProperty("user")] public User User { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/GuildEmojiUpdateEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/GuildEmojiUpdateEvent.cs index 715341dc5..300dd0ef9 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/GuildEmojiUpdateEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/GuildEmojiUpdateEvent.cs @@ -1,13 +1,13 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Gateway { internal class GuildEmojiUpdateEvent { - [JsonProperty("guild_id")] + [ModelProperty("guild_id")] public ulong GuildId { get; set; } - [JsonProperty("emojis")] + [ModelProperty("emojis")] public Emoji[] Emojis { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/GuildMemberAddEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/GuildMemberAddEvent.cs index 350652faf..3d978e070 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/GuildMemberAddEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/GuildMemberAddEvent.cs @@ -1,11 +1,11 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Gateway { internal class GuildMemberAddEvent : GuildMember { - [JsonProperty("guild_id")] + [ModelProperty("guild_id")] public ulong GuildId { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/GuildMemberRemoveEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/GuildMemberRemoveEvent.cs index 501408a7f..ac4b4f0b1 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/GuildMemberRemoveEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/GuildMemberRemoveEvent.cs @@ -1,13 +1,13 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Gateway { internal class GuildMemberRemoveEvent { - [JsonProperty("guild_id")] + [ModelProperty("guild_id")] public ulong GuildId { get; set; } - [JsonProperty("user")] + [ModelProperty("user")] public User User { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/GuildMemberUpdateEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/GuildMemberUpdateEvent.cs index a234d6da5..15a2acb31 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/GuildMemberUpdateEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/GuildMemberUpdateEvent.cs @@ -1,11 +1,11 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Gateway { internal class GuildMemberUpdateEvent : GuildMember { - [JsonProperty("guild_id")] + [ModelProperty("guild_id")] public ulong GuildId { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/GuildMembersChunkEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/GuildMembersChunkEvent.cs index e401d7fa1..d41d0eb54 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/GuildMembersChunkEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/GuildMembersChunkEvent.cs @@ -1,13 +1,13 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Gateway { internal class GuildMembersChunkEvent { - [JsonProperty("guild_id")] + [ModelProperty("guild_id")] public ulong GuildId { get; set; } - [JsonProperty("members")] + [ModelProperty("members")] public GuildMember[] Members { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/GuildRoleCreateEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/GuildRoleCreateEvent.cs index 3409b1c91..04efb3ff1 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/GuildRoleCreateEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/GuildRoleCreateEvent.cs @@ -1,13 +1,13 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Gateway { internal class GuildRoleCreateEvent { - [JsonProperty("guild_id")] + [ModelProperty("guild_id")] public ulong GuildId { get; set; } - [JsonProperty("role")] + [ModelProperty("role")] public Role Role { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/GuildRoleDeleteEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/GuildRoleDeleteEvent.cs index dbdaeff67..ecb504e94 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/GuildRoleDeleteEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/GuildRoleDeleteEvent.cs @@ -1,13 +1,13 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Gateway { internal class GuildRoleDeleteEvent { - [JsonProperty("guild_id")] + [ModelProperty("guild_id")] public ulong GuildId { get; set; } - [JsonProperty("role_id")] + [ModelProperty("role_id")] public ulong RoleId { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/GuildRoleUpdateEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/GuildRoleUpdateEvent.cs index b04ecb182..d335cd1ec 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/GuildRoleUpdateEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/GuildRoleUpdateEvent.cs @@ -1,13 +1,13 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Gateway { internal class GuildRoleUpdateEvent { - [JsonProperty("guild_id")] + [ModelProperty("guild_id")] public ulong GuildId { get; set; } - [JsonProperty("role")] + [ModelProperty("role")] public Role Role { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/GuildSyncEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/GuildSyncEvent.cs index 6b2e6c02f..04859b9a3 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/GuildSyncEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/GuildSyncEvent.cs @@ -1,18 +1,18 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Gateway { internal class GuildSyncEvent { - [JsonProperty("id")] + [ModelProperty("id")] public ulong Id { get; set; } - [JsonProperty("large")] + [ModelProperty("large")] public bool Large { get; set; } - [JsonProperty("presences")] + [ModelProperty("presences")] public Presence[] Presences { get; set; } - [JsonProperty("members")] + [ModelProperty("members")] public GuildMember[] Members { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/HelloEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/HelloEvent.cs index e1ed9463c..60bef8f93 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/HelloEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/HelloEvent.cs @@ -1,11 +1,11 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Gateway { internal class HelloEvent { - [JsonProperty("heartbeat_interval")] + [ModelProperty("heartbeat_interval")] public int HeartbeatInterval { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/IdentifyParams.cs b/src/Discord.Net.WebSocket/API/Gateway/IdentifyParams.cs index e87c58221..895618200 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/IdentifyParams.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/IdentifyParams.cs @@ -1,21 +1,20 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; using System.Collections.Generic; namespace Discord.API.Gateway { - [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class IdentifyParams { - [JsonProperty("token")] + [ModelProperty("token")] public string Token { get; set; } - [JsonProperty("properties")] + [ModelProperty("properties")] public IDictionary Properties { get; set; } - [JsonProperty("large_threshold")] + [ModelProperty("large_threshold")] public int LargeThreshold { get; set; } - [JsonProperty("compress")] + [ModelProperty("compress")] public bool UseCompression { get; set; } - [JsonProperty("shard")] + [ModelProperty("shard")] public Optional ShardingParams { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/MessageDeleteBulkEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/MessageDeleteBulkEvent.cs index aba4a2f1c..5c2cd56ec 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/MessageDeleteBulkEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/MessageDeleteBulkEvent.cs @@ -1,14 +1,14 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; using System.Collections.Generic; namespace Discord.API.Gateway { internal class MessageDeleteBulkEvent { - [JsonProperty("channel_id")] + [ModelProperty("channel_id")] public ulong ChannelId { get; set; } - [JsonProperty("ids")] + [ModelProperty("ids")] public IEnumerable Ids { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/Reaction.cs b/src/Discord.Net.WebSocket/API/Gateway/Reaction.cs index 62de456e2..d322d68c5 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/Reaction.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/Reaction.cs @@ -1,16 +1,16 @@ -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Gateway { internal class Reaction { - [JsonProperty("user_id")] + [ModelProperty("user_id")] public ulong UserId { get; set; } - [JsonProperty("message_id")] + [ModelProperty("message_id")] public ulong MessageId { get; set; } - [JsonProperty("channel_id")] + [ModelProperty("channel_id")] public ulong ChannelId { get; set; } - [JsonProperty("emoji")] + [ModelProperty("emoji")] public Emoji Emoji { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/ReadyEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/ReadyEvent.cs index ab92d8c36..42f0d728f 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/ReadyEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/ReadyEvent.cs @@ -1,5 +1,5 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Gateway { @@ -7,32 +7,32 @@ namespace Discord.API.Gateway { public class ReadState { - [JsonProperty("id")] + [ModelProperty("id")] public string ChannelId { get; set; } - [JsonProperty("mention_count")] + [ModelProperty("mention_count")] public int MentionCount { get; set; } - [JsonProperty("last_message_id")] + [ModelProperty("last_message_id")] public string LastMessageId { get; set; } } - [JsonProperty("v")] + [ModelProperty("v")] public int Version { get; set; } - [JsonProperty("user")] + [ModelProperty("user")] public User User { get; set; } - [JsonProperty("session_id")] + [ModelProperty("session_id")] public string SessionId { get; set; } - [JsonProperty("read_state")] + [ModelProperty("read_state")] public ReadState[] ReadStates { get; set; } - [JsonProperty("guilds")] + [ModelProperty("guilds")] public ExtendedGuild[] Guilds { get; set; } - [JsonProperty("private_channels")] + [ModelProperty("private_channels")] public Channel[] PrivateChannels { get; set; } - [JsonProperty("relationships")] + [ModelProperty("relationships")] public Relationship[] Relationships { get; set; } //Ignored - /*[JsonProperty("user_settings")] - [JsonProperty("user_guild_settings")] - [JsonProperty("tutorial")]*/ + /*[ModelProperty("user_settings")] + [ModelProperty("user_guild_settings")] + [ModelProperty("tutorial")]*/ } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/RecipientEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/RecipientEvent.cs index 336ffd029..bbd7c318b 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/RecipientEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/RecipientEvent.cs @@ -1,13 +1,13 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Gateway { internal class RecipientEvent { - [JsonProperty("user")] + [ModelProperty("user")] public User User { get; set; } - [JsonProperty("channel_id")] + [ModelProperty("channel_id")] public ulong ChannelId { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/RemoveAllReactionsEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/RemoveAllReactionsEvent.cs index 4833c5123..cff11597f 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/RemoveAllReactionsEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/RemoveAllReactionsEvent.cs @@ -1,12 +1,12 @@ -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Gateway { internal class RemoveAllReactionsEvent { - [JsonProperty("channel_id")] + [ModelProperty("channel_id")] public ulong ChannelId { get; set; } - [JsonProperty("message_id")] + [ModelProperty("message_id")] public ulong MessageId { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/RequestMembersParams.cs b/src/Discord.Net.WebSocket/API/Gateway/RequestMembersParams.cs index 6a8d283ed..ac429a7f1 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/RequestMembersParams.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/RequestMembersParams.cs @@ -1,18 +1,17 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; using System.Collections.Generic; namespace Discord.API.Gateway { - [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class RequestMembersParams { - [JsonProperty("query")] + [ModelProperty("query")] public string Query { get; set; } - [JsonProperty("limit")] + [ModelProperty("limit")] public int Limit { get; set; } - [JsonProperty("guild_id")] + [ModelProperty("guild_id")] public IEnumerable GuildIds { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/ResumeParams.cs b/src/Discord.Net.WebSocket/API/Gateway/ResumeParams.cs index ffb46327b..7c259aa6d 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/ResumeParams.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/ResumeParams.cs @@ -1,16 +1,15 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Gateway { - [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class ResumeParams { - [JsonProperty("token")] + [ModelProperty("token")] public string Token { get; set; } - [JsonProperty("session_id")] + [ModelProperty("session_id")] public string SessionId { get; set; } - [JsonProperty("seq")] + [ModelProperty("seq")] public int Sequence { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/ResumedEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/ResumedEvent.cs index d1347beae..6af247b61 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/ResumedEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/ResumedEvent.cs @@ -1,11 +1,11 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Gateway { internal class ResumedEvent { - [JsonProperty("heartbeat_interval")] + [ModelProperty("heartbeat_interval")] public int HeartbeatInterval { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/StatusUpdateParams.cs b/src/Discord.Net.WebSocket/API/Gateway/StatusUpdateParams.cs index fc0964c17..ea4fa4866 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/StatusUpdateParams.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/StatusUpdateParams.cs @@ -1,18 +1,17 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Gateway { - [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class StatusUpdateParams { - [JsonProperty("status")] + [ModelProperty("status")] public UserStatus Status { get; set; } - [JsonProperty("since"), Int53] + [ModelProperty("since"), Int53] public long? IdleSince { get; set; } - [JsonProperty("afk")] + [ModelProperty("afk")] public bool IsAFK { get; set; } - [JsonProperty("game")] + [ModelProperty("game")] public Game Game { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/TypingStartEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/TypingStartEvent.cs index 3cce512bd..bf429f891 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/TypingStartEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/TypingStartEvent.cs @@ -1,15 +1,15 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Gateway { internal class TypingStartEvent { - [JsonProperty("user_id")] + [ModelProperty("user_id")] public ulong UserId { get; set; } - [JsonProperty("channel_id")] + [ModelProperty("channel_id")] public ulong ChannelId { get; set; } - [JsonProperty("timestamp")] + [ModelProperty("timestamp")] public int Timestamp { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/VoiceServerUpdateEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/VoiceServerUpdateEvent.cs index 29167c1cc..a29bdded7 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/VoiceServerUpdateEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/VoiceServerUpdateEvent.cs @@ -1,15 +1,15 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Gateway { internal class VoiceServerUpdateEvent { - [JsonProperty("guild_id")] + [ModelProperty("guild_id")] public ulong GuildId { get; set; } - [JsonProperty("endpoint")] + [ModelProperty("endpoint")] public string Endpoint { get; set; } - [JsonProperty("token")] + [ModelProperty("token")] public string Token { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/VoiceStateUpdateParams.cs b/src/Discord.Net.WebSocket/API/Gateway/VoiceStateUpdateParams.cs index 521160126..88234a2b0 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/VoiceStateUpdateParams.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/VoiceStateUpdateParams.cs @@ -1,19 +1,18 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Gateway { - [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class VoiceStateUpdateParams { - [JsonProperty("self_mute")] + [ModelProperty("self_mute")] public bool SelfMute { get; set; } - [JsonProperty("self_deaf")] + [ModelProperty("self_deaf")] public bool SelfDeaf { get; set; } - [JsonProperty("guild_id")] + [ModelProperty("guild_id")] public ulong? GuildId { get; set; } - [JsonProperty("channel_id")] + [ModelProperty("channel_id")] public ulong? ChannelId { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/SocketFrame.cs b/src/Discord.Net.WebSocket/API/SocketFrame.cs index fae741432..24b7faa60 100644 --- a/src/Discord.Net.WebSocket/API/SocketFrame.cs +++ b/src/Discord.Net.WebSocket/API/SocketFrame.cs @@ -1,17 +1,18 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; +using System; namespace Discord.API { internal class SocketFrame { - [JsonProperty("op")] + [ModelProperty("op")] public int Operation { get; set; } - [JsonProperty("t", NullValueHandling = NullValueHandling.Ignore)] + [ModelProperty("t", IgnoreNull = true)] public string Type { get; set; } - [JsonProperty("s", NullValueHandling = NullValueHandling.Ignore)] + [ModelProperty("s", IgnoreNull = true)] public int? Sequence { get; set; } - [JsonProperty("d")] - public object Payload { get; set; } + [ModelProperty("d")] + public ReadOnlyBuffer Payload { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Voice/IdentifyParams.cs b/src/Discord.Net.WebSocket/API/Voice/IdentifyParams.cs index d446867e1..ce79f51a9 100644 --- a/src/Discord.Net.WebSocket/API/Voice/IdentifyParams.cs +++ b/src/Discord.Net.WebSocket/API/Voice/IdentifyParams.cs @@ -1,17 +1,17 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Voice { internal class IdentifyParams { - [JsonProperty("server_id")] + [ModelProperty("server_id")] public ulong GuildId { get; set; } - [JsonProperty("user_id")] + [ModelProperty("user_id")] public ulong UserId { get; set; } - [JsonProperty("session_id")] + [ModelProperty("session_id")] public string SessionId { get; set; } - [JsonProperty("token")] + [ModelProperty("token")] public string Token { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Voice/ReadyEvent.cs b/src/Discord.Net.WebSocket/API/Voice/ReadyEvent.cs index 2a134ced1..e6736ae95 100644 --- a/src/Discord.Net.WebSocket/API/Voice/ReadyEvent.cs +++ b/src/Discord.Net.WebSocket/API/Voice/ReadyEvent.cs @@ -1,19 +1,19 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Voice { internal class ReadyEvent { - [JsonProperty("ssrc")] + [ModelProperty("ssrc")] public uint SSRC { get; set; } - [JsonProperty("ip")] + [ModelProperty("ip")] public string Ip { get; set; } - [JsonProperty("port")] + [ModelProperty("port")] public ushort Port { get; set; } - [JsonProperty("modes")] + [ModelProperty("modes")] public string[] Modes { get; set; } - [JsonProperty("heartbeat_interval")] + [ModelProperty("heartbeat_interval")] public int HeartbeatInterval { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Voice/SelectProtocolParams.cs b/src/Discord.Net.WebSocket/API/Voice/SelectProtocolParams.cs index 8c577e5b5..ae96e89e5 100644 --- a/src/Discord.Net.WebSocket/API/Voice/SelectProtocolParams.cs +++ b/src/Discord.Net.WebSocket/API/Voice/SelectProtocolParams.cs @@ -1,13 +1,13 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Voice { internal class SelectProtocolParams { - [JsonProperty("protocol")] + [ModelProperty("protocol")] public string Protocol { get; set; } - [JsonProperty("data")] + [ModelProperty("data")] public UdpProtocolInfo Data { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Voice/SessionDescriptionEvent.cs b/src/Discord.Net.WebSocket/API/Voice/SessionDescriptionEvent.cs index 45befadcf..7aed26fdb 100644 --- a/src/Discord.Net.WebSocket/API/Voice/SessionDescriptionEvent.cs +++ b/src/Discord.Net.WebSocket/API/Voice/SessionDescriptionEvent.cs @@ -1,13 +1,13 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Voice { internal class SessionDescriptionEvent { - [JsonProperty("secret_key")] + [ModelProperty("secret_key")] public byte[] SecretKey { get; set; } - [JsonProperty("mode")] + [ModelProperty("mode")] public string Mode { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Voice/SpeakingEvent.cs b/src/Discord.Net.WebSocket/API/Voice/SpeakingEvent.cs index 0272a8f53..819a4938c 100644 --- a/src/Discord.Net.WebSocket/API/Voice/SpeakingEvent.cs +++ b/src/Discord.Net.WebSocket/API/Voice/SpeakingEvent.cs @@ -1,15 +1,15 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Voice { internal class SpeakingEvent { - [JsonProperty("user_id")] + [ModelProperty("user_id")] public ulong UserId { get; set; } - [JsonProperty("ssrc")] + [ModelProperty("ssrc")] public uint Ssrc { get; set; } - [JsonProperty("speaking")] + [ModelProperty("speaking")] public bool Speaking { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Voice/SpeakingParams.cs b/src/Discord.Net.WebSocket/API/Voice/SpeakingParams.cs index abdf90667..8da2f7a0f 100644 --- a/src/Discord.Net.WebSocket/API/Voice/SpeakingParams.cs +++ b/src/Discord.Net.WebSocket/API/Voice/SpeakingParams.cs @@ -1,13 +1,13 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Voice { internal class SpeakingParams { - [JsonProperty("speaking")] + [ModelProperty("speaking")] public bool IsSpeaking { get; set; } - [JsonProperty("delay")] + [ModelProperty("delay")] public int Delay { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Voice/UdpProtocolInfo.cs b/src/Discord.Net.WebSocket/API/Voice/UdpProtocolInfo.cs index 6f4719e7e..141118025 100644 --- a/src/Discord.Net.WebSocket/API/Voice/UdpProtocolInfo.cs +++ b/src/Discord.Net.WebSocket/API/Voice/UdpProtocolInfo.cs @@ -1,15 +1,15 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; +using Discord.Serialization; namespace Discord.API.Voice { internal class UdpProtocolInfo { - [JsonProperty("address")] + [ModelProperty("address")] public string Address { get; set; } - [JsonProperty("port")] + [ModelProperty("port")] public int Port { get; set; } - [JsonProperty("mode")] + [ModelProperty("mode")] public string Mode { get; set; } } } diff --git a/src/Discord.Net.WebSocket/Audio/AudioClient.cs b/src/Discord.Net.WebSocket/Audio/AudioClient.cs index dc0b07373..9b2d9c7b6 100644 --- a/src/Discord.Net.WebSocket/Audio/AudioClient.cs +++ b/src/Discord.Net.WebSocket/Audio/AudioClient.cs @@ -2,7 +2,6 @@ using Discord.Audio.Streams; using Discord.Logging; using Discord.WebSocket; -using Newtonsoft.Json.Linq; using System; using System.Collections.Concurrent; using System.Linq; @@ -51,7 +50,7 @@ namespace Discord.Audio private bool _isDisposed; public SocketGuild Guild { get; } - public DiscordVoiceAPIClient ApiClient { get; private set; } + public DiscordVoiceApiClient ApiClient { get; private set; } public int Latency { get; private set; } public int UdpLatency { get; private set; } public ulong ChannelId { get; internal set; } @@ -73,7 +72,7 @@ namespace Discord.Audio await _audioLogger.WarningAsync("Serializer Error", ex); }; - ApiClient = new DiscordVoiceAPIClient(guild.Id, Discord.WebSocketProvider, Discord.UdpSocketProvider, _serializer); + ApiClient = new DiscordVoiceApiClient(guild.Id, Discord.WebSocketProvider, Discord.UdpSocketProvider, _serializer); ApiClient.SentGatewayMessage += async opCode => await _audioLogger.DebugAsync($"Sent {opCode}").ConfigureAwait(false); ApiClient.SentDiscovery += async () => await _audioLogger.DebugAsync($"Sent Discovery").ConfigureAwait(false); //ApiClient.SentData += async bytes => await _audioLogger.DebugAsync($"Sent {bytes} Bytes").ConfigureAwait(false); @@ -226,7 +225,7 @@ namespace Discord.Audio _streams.Clear(); } - private async Task ProcessMessageAsync(VoiceOpCode opCode, object payload) + private async Task ProcessMessageAsync(VoiceOpCode opCode, ReadOnlyBuffer payload) { _lastMessageTime = Environment.TickCount; @@ -237,12 +236,12 @@ namespace Discord.Audio case VoiceOpCode.Ready: { await _audioLogger.DebugAsync("Received Ready").ConfigureAwait(false); - var data = _serializer.FromJson(payload as JToken); + var data = _serializer.ReadJson(payload); _ssrc = data.SSRC; - if (!data.Modes.Contains(DiscordVoiceAPIClient.Mode)) - throw new InvalidOperationException($"Discord does not support {DiscordVoiceAPIClient.Mode}"); + if (!data.Modes.Contains(DiscordVoiceApiClient.Mode)) + throw new InvalidOperationException($"Discord does not support {DiscordVoiceApiClient.Mode}"); _heartbeatTask = RunHeartbeatAsync(data.HeartbeatInterval, _connection.CancelToken); @@ -253,9 +252,9 @@ namespace Discord.Audio case VoiceOpCode.SessionDescription: { await _audioLogger.DebugAsync("Received SessionDescription").ConfigureAwait(false); - var data = _serializer.FromJson(payload as JToken); + var data = _serializer.ReadJson(payload); - if (data.Mode != DiscordVoiceAPIClient.Mode) + if (data.Mode != DiscordVoiceApiClient.Mode) throw new InvalidOperationException($"Discord selected an unexpected mode: {data.Mode}"); _secretKey = data.SecretKey; @@ -289,7 +288,7 @@ namespace Discord.Audio { await _audioLogger.DebugAsync("Received Speaking").ConfigureAwait(false); - var data = _serializer.FromJson(payload as JToken); + var data = _serializer.ReadJson(payload); _ssrcMap[data.Ssrc] = data.UserId; //TODO: Memory Leak: SSRCs are never cleaned up await _speakingUpdatedEvent.InvokeAsync(data.UserId, data.Speaking); diff --git a/src/Discord.Net.WebSocket/Audio/Opus/OpusEncoder.cs b/src/Discord.Net.WebSocket/Audio/Opus/OpusEncoder.cs index fc0113be9..6191095b2 100644 --- a/src/Discord.Net.WebSocket/Audio/Opus/OpusEncoder.cs +++ b/src/Discord.Net.WebSocket/Audio/Opus/OpusEncoder.cs @@ -19,7 +19,7 @@ namespace Discord.Audio public OpusEncoder(int bitrate, AudioApplication application, int packetLoss) { - if (bitrate < 1 || bitrate > DiscordVoiceAPIClient.MaxBitrate) + if (bitrate < 1 || bitrate > DiscordVoiceApiClient.MaxBitrate) throw new ArgumentOutOfRangeException(nameof(bitrate)); Application = application; diff --git a/src/Discord.Net.WebSocket/Audio/Streams/OutputStream.cs b/src/Discord.Net.WebSocket/Audio/Streams/OutputStream.cs index cba4e3cb6..b9b04faa9 100644 --- a/src/Discord.Net.WebSocket/Audio/Streams/OutputStream.cs +++ b/src/Discord.Net.WebSocket/Audio/Streams/OutputStream.cs @@ -6,10 +6,10 @@ namespace Discord.Audio.Streams /// Wraps an IAudioClient, sending voice data on write. public class OutputStream : AudioOutStream { - private readonly DiscordVoiceAPIClient _client; + private readonly DiscordVoiceApiClient _client; public OutputStream(IAudioClient client) : this((client as AudioClient).ApiClient) { } - internal OutputStream(DiscordVoiceAPIClient client) + internal OutputStream(DiscordVoiceApiClient client) { _client = client; } diff --git a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs index 5355324ea..f9a8c9ab8 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs @@ -11,6 +11,7 @@ using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Text; +using System.Text.Formatting; using System.Threading; using System.Threading.Tasks; @@ -20,14 +21,14 @@ namespace Discord.API { public event Func SentGatewayMessage { add { _sentGatewayMessageEvent.Add(value); } remove { _sentGatewayMessageEvent.Remove(value); } } private readonly AsyncEvent> _sentGatewayMessageEvent = new AsyncEvent>(); - public event Func ReceivedGatewayEvent { add { _receivedGatewayEvent.Add(value); } remove { _receivedGatewayEvent.Remove(value); } } - private readonly AsyncEvent> _receivedGatewayEvent = new AsyncEvent>(); + public event Func, Task> ReceivedGatewayEvent { add { _receivedGatewayEvent.Add(value); } remove { _receivedGatewayEvent.Remove(value); } } + private readonly AsyncEvent, Task>> _receivedGatewayEvent = new AsyncEvent, Task>>(); public event Func Disconnected { add { _disconnectedEvent.Add(value); } remove { _disconnectedEvent.Remove(value); } } private readonly AsyncEvent> _disconnectedEvent = new AsyncEvent>(); + private readonly Span _textBuffer; private readonly MemoryStream _decompressionStream; - private readonly StreamReader _decompressionReader; private CancellationTokenSource _connectCancelToken; private string _gatewayUrl; private bool _isExplicitUrl; @@ -45,30 +46,30 @@ namespace Discord.API _isExplicitUrl = true; _decompressionStream = new MemoryStream(10 * 1024); //10 KB - _decompressionReader = new StreamReader(_decompressionStream); + _textBuffer = new Span(new byte[10 * 1024]); WebSocketClient = webSocketProvider(); //WebSocketClient.SetHeader("user-agent", DiscordConfig.UserAgent); (Causes issues in .NET Framework 4.6+) - WebSocketClient.BinaryMessage += async (data, index, count) => + WebSocketClient.Message += async (data, isText) => { - using (var compressed = new MemoryStream(data, index + 2, count - 2)) + if (!isText) { - _decompressionStream.Position = 0; - using (var zlib = new DeflateStream(compressed, CompressionMode.Decompress)) - zlib.CopyTo(_decompressionStream); - _decompressionStream.SetLength(_decompressionStream.Position); + using (var compressed = new MemoryStream(data.ToArray(), 2, data.Length - 2)) + { + _decompressionStream.Position = 0; + using (var zlib = new DeflateStream(compressed, CompressionMode.Decompress)) + zlib.CopyTo(_decompressionStream); + _decompressionStream.SetLength(_decompressionStream.Position); - _decompressionStream.Position = 0; - var msg = _serializer.FromJson(_decompressionReader); - if (msg != null) - await _receivedGatewayEvent.InvokeAsync((GatewayOpCode)msg.Operation, msg.Sequence, msg.Type, msg.Payload).ConfigureAwait(false); + _decompressionStream.Position = 0; + var msg = _serializer.ReadJson(_decompressionStream.ToReadOnlyBuffer()); + if (msg != null) + await _receivedGatewayEvent.InvokeAsync((GatewayOpCode)msg.Operation, msg.Sequence, msg.Type, msg.Payload).ConfigureAwait(false); + } } - }; - WebSocketClient.TextMessage += async text => - { - using (var reader = new StringReader(text)) + else { - var msg = _serializer.FromJson(reader); + var msg = _serializer.ReadJson(data); if (msg != null) await _receivedGatewayEvent.InvokeAsync((GatewayOpCode)msg.Operation, msg.Sequence, msg.Type, msg.Payload).ConfigureAwait(false); } @@ -174,13 +175,21 @@ namespace Discord.API { CheckState(); - //TODO: Add ETF - byte[] bytes = null; - payload = new SocketFrame { Operation = (int)opCode, Payload = payload }; - if (payload != null) - bytes = Encoding.UTF8.GetBytes(SerializeJson(payload)); - await RequestQueue.SendAsync(new WebSocketRequest(WebSocketClient, null, bytes, true, options)).ConfigureAwait(false); - await _sentGatewayMessageEvent.InvokeAsync(opCode).ConfigureAwait(false); + if (_formatters.TryDequeue(out var data1)) + data1 = new ArrayFormatter(128, SymbolTable.InvariantUtf8); + if (_formatters.TryDequeue(out var data2)) + data2 = new ArrayFormatter(128, SymbolTable.InvariantUtf8); + try + { + payload = new SocketFrame { Operation = (int)opCode, Payload = SerializeJson(data1, payload) }; + await RequestQueue.SendAsync(new WebSocketRequest(WebSocketClient, null, SerializeJson(data2, payload), true, options)).ConfigureAwait(false); + await _sentGatewayMessageEvent.InvokeAsync(opCode).ConfigureAwait(false); + } + finally + { + _formatters.Enqueue(data1); + _formatters.Enqueue(data2); + } } //Gateway diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 11b21b9df..6a4bc7d6f 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -5,13 +5,13 @@ using Discord.Net.Udp; using Discord.Net.WebSockets; using Discord.Rest; using Discord.Serialization; -using Newtonsoft.Json.Linq; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.IO; using System.Linq; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using GameModel = Discord.API.Game; @@ -382,7 +382,7 @@ namespace Discord.WebSocket gameModel).ConfigureAwait(false); } - private async Task ProcessMessageAsync(GatewayOpCode opCode, int? seq, string type, object payload) + private async Task ProcessMessageAsync(GatewayOpCode opCode, int? seq, string type, ReadOnlyBuffer payload) { if (seq != null) _lastSeq = seq.Value; @@ -395,7 +395,7 @@ namespace Discord.WebSocket case GatewayOpCode.Hello: { await _gatewayLogger.DebugAsync("Received Hello").ConfigureAwait(false); - var data = _serializer.FromJson(payload as JToken); + var data = _serializer.ReadJson(payload); _heartbeatTask = RunHeartbeatAsync(data.HeartbeatInterval, _connection.CancelToken); } @@ -428,7 +428,7 @@ namespace Discord.WebSocket _sessionId = null; _lastSeq = 0; - bool retry = (bool)payload; + bool retry = IsTrue(); if (retry) _connection.Reconnect(); //TODO: Untested else @@ -451,7 +451,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (READY)").ConfigureAwait(false); - var data = _serializer.FromJson(payload as JToken); + var data = _serializer.ReadJson(payload); var state = new ClientState(data.Guilds.Length, data.PrivateChannels.Length); var currentUser = SocketSelfUser.Create(this, state, data.User); @@ -521,7 +521,7 @@ namespace Discord.WebSocket //Guilds case "GUILD_CREATE": { - var data = _serializer.FromJson(payload as JToken); + var data = _serializer.ReadJson(payload); if (data.Unavailable == false) { @@ -573,7 +573,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_UPDATE)").ConfigureAwait(false); - var data = _serializer.FromJson(payload as JToken); + var data = _serializer.ReadJson(payload); var guild = State.GetGuild(data.Id); if (guild != null) { @@ -592,7 +592,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_EMOJIS_UPDATE)").ConfigureAwait(false); - var data = _serializer.FromJson(payload as JToken); + var data = _serializer.ReadJson(payload); var guild = State.GetGuild(data.GuildId); if (guild != null) { @@ -610,7 +610,7 @@ namespace Discord.WebSocket case "GUILD_SYNC": { await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_SYNC)").ConfigureAwait(false); - var data = _serializer.FromJson(payload as JToken); + var data = _serializer.ReadJson(payload); var guild = State.GetGuild(data.Id); if (guild != null) { @@ -631,7 +631,7 @@ namespace Discord.WebSocket break; case "GUILD_DELETE": { - var data = _serializer.FromJson(payload as JToken); + var data = _serializer.ReadJson(payload); if (data.Unavailable == true) { type = "GUILD_UNAVAILABLE"; @@ -673,7 +673,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_CREATE)").ConfigureAwait(false); - var data = _serializer.FromJson(payload as JToken); + var data = _serializer.ReadJson(payload); SocketChannel channel = null; if (data.GuildId.IsSpecified) { @@ -705,7 +705,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_UPDATE)").ConfigureAwait(false); - var data = _serializer.FromJson(payload as JToken); + var data = _serializer.ReadJson(payload); var channel = State.GetChannel(data.Id); if (channel != null) { @@ -733,7 +733,7 @@ namespace Discord.WebSocket await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_DELETE)").ConfigureAwait(false); SocketChannel channel = null; - var data = _serializer.FromJson(payload as JToken); + var data = _serializer.ReadJson(payload); if (data.GuildId.IsSpecified) { var guild = State.GetGuild(data.GuildId.Value); @@ -771,7 +771,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBER_ADD)").ConfigureAwait(false); - var data = _serializer.FromJson(payload as JToken); + var data = _serializer.ReadJson(payload); var guild = State.GetGuild(data.GuildId); if (guild != null) { @@ -797,7 +797,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBER_UPDATE)").ConfigureAwait(false); - var data = _serializer.FromJson(payload as JToken); + var data = _serializer.ReadJson(payload); var guild = State.GetGuild(data.GuildId); if (guild != null) { @@ -835,7 +835,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBER_REMOVE)").ConfigureAwait(false); - var data = _serializer.FromJson(payload as JToken); + var data = _serializer.ReadJson(payload); var guild = State.GetGuild(data.GuildId); if (guild != null) { @@ -870,7 +870,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBERS_CHUNK)").ConfigureAwait(false); - var data = _serializer.FromJson(payload as JToken); + var data = _serializer.ReadJson(payload); var guild = State.GetGuild(data.GuildId); if (guild != null) { @@ -894,7 +894,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_RECIPIENT_ADD)").ConfigureAwait(false); - var data = _serializer.FromJson(payload as JToken); + var data = _serializer.ReadJson(payload); if (State.GetChannel(data.ChannelId) is SocketGroupChannel channel) { var user = channel.GetOrAddUser(data.User); @@ -911,7 +911,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_RECIPIENT_REMOVE)").ConfigureAwait(false); - var data = _serializer.FromJson(payload as JToken); + var data = _serializer.ReadJson(payload); if (State.GetChannel(data.ChannelId) is SocketGroupChannel channel) { var user = channel.RemoveUser(data.User.Id); @@ -936,7 +936,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_ROLE_CREATE)").ConfigureAwait(false); - var data = _serializer.FromJson(payload as JToken); + var data = _serializer.ReadJson(payload); var guild = State.GetGuild(data.GuildId); if (guild != null) { @@ -960,7 +960,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_ROLE_UPDATE)").ConfigureAwait(false); - var data = _serializer.FromJson(payload as JToken); + var data = _serializer.ReadJson(payload); var guild = State.GetGuild(data.GuildId); if (guild != null) { @@ -995,7 +995,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_ROLE_DELETE)").ConfigureAwait(false); - var data = _serializer.FromJson(payload as JToken); + var data = _serializer.ReadJson(payload); var guild = State.GetGuild(data.GuildId); if (guild != null) { @@ -1029,7 +1029,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_BAN_ADD)").ConfigureAwait(false); - var data = _serializer.FromJson(payload as JToken); + var data = _serializer.ReadJson(payload); var guild = State.GetGuild(data.GuildId); if (guild != null) { @@ -1055,7 +1055,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_BAN_REMOVE)").ConfigureAwait(false); - var data = _serializer.FromJson(payload as JToken); + var data = _serializer.ReadJson(payload); var guild = State.GetGuild(data.GuildId); if (guild != null) { @@ -1083,7 +1083,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_CREATE)").ConfigureAwait(false); - var data = _serializer.FromJson(payload as JToken); + var data = _serializer.ReadJson(payload); if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) { var guild = (channel as SocketGuildChannel)?.Guild; @@ -1130,7 +1130,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_UPDATE)").ConfigureAwait(false); - var data = _serializer.FromJson(payload as JToken); + var data = _serializer.ReadJson(payload); if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) { var guild = (channel as SocketGuildChannel)?.Guild; @@ -1177,7 +1177,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE)").ConfigureAwait(false); - var data = _serializer.FromJson(payload as JToken); + var data = _serializer.ReadJson(payload); if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) { var guild = (channel as SocketGuildChannel)?.Guild; @@ -1204,7 +1204,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_ADD)").ConfigureAwait(false); - var data = _serializer.FromJson(payload as JToken); + var data = _serializer.ReadJson(payload); if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) { var cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; @@ -1228,7 +1228,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_REMOVE)").ConfigureAwait(false); - var data = _serializer.FromJson(payload as JToken); + var data = _serializer.ReadJson(payload); if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) { var cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; @@ -1252,7 +1252,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_REMOVE_ALL)").ConfigureAwait(false); - var data = _serializer.FromJson(payload as JToken); + var data = _serializer.ReadJson(payload); if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) { var cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; @@ -1274,7 +1274,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE_BULK)").ConfigureAwait(false); - var data = _serializer.FromJson(payload as JToken); + var data = _serializer.ReadJson(payload); if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) { var guild = (channel as SocketGuildChannel)?.Guild; @@ -1305,7 +1305,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (PRESENCE_UPDATE)").ConfigureAwait(false); - var data = _serializer.FromJson(payload as JToken); + var data = _serializer.ReadJson(payload); if (data.GuildId.IsSpecified) { @@ -1364,7 +1364,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (TYPING_START)").ConfigureAwait(false); - var data = _serializer.FromJson(payload as JToken); + var data = _serializer.ReadJson(payload); if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) { var guild = (channel as SocketGuildChannel)?.Guild; @@ -1386,7 +1386,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (USER_UPDATE)").ConfigureAwait(false); - var data = _serializer.FromJson(payload as JToken); + var data = _serializer.ReadJson(payload); if (data.Id == CurrentUser.Id) { var before = CurrentUser.Clone(); @@ -1406,7 +1406,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (VOICE_STATE_UPDATE)").ConfigureAwait(false); - var data = _serializer.FromJson(payload as JToken); + var data = _serializer.ReadJson(payload); SocketUser user; SocketVoiceState before, after; if (data.GuildId != null) @@ -1478,7 +1478,7 @@ namespace Discord.WebSocket { await _gatewayLogger.DebugAsync("Received Dispatch (VOICE_SERVER_UPDATE)").ConfigureAwait(false); - var data = _serializer.FromJson(payload as JToken); + var data = _serializer.ReadJson(payload); var guild = State.GetGuild(data.GuildId); if (guild != null) { @@ -1528,6 +1528,15 @@ namespace Discord.WebSocket { await _gatewayLogger.ErrorAsync($"Error handling {opCode}{(type != null ? $" ({type})" : "")}", ex).ConfigureAwait(false); } + + bool IsTrue() + { + ref var ptr = ref payload.Span.DangerousGetPinnableReference(); + return Unsafe.Add(ref ptr, 0) == (byte)'t' && + Unsafe.Add(ref ptr, 1) == (byte)'r' && + Unsafe.Add(ref ptr, 2) == (byte)'u' && + Unsafe.Add(ref ptr, 3) == (byte)'e'; + } } private async Task RunHeartbeatAsync(int intervalMillis, CancellationToken cancelToken) diff --git a/src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs b/src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs index 7817ef81c..04c3a7dd9 100644 --- a/src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs +++ b/src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs @@ -4,19 +4,20 @@ using Discord.API.Voice; using Discord.Net.Udp; using Discord.Net.WebSockets; using Discord.Serialization; -using Newtonsoft.Json; using System; +using System.Collections.Concurrent; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Compression; using System.Text; +using System.Text.Formatting; using System.Threading; using System.Threading.Tasks; namespace Discord.Audio { - internal class DiscordVoiceAPIClient + internal class DiscordVoiceApiClient { public const int MaxBitrate = 128 * 1024; public const string Mode = "xsalsa20_poly1305"; @@ -30,8 +31,8 @@ namespace Discord.Audio public event Func SentData { add { _sentDataEvent.Add(value); } remove { _sentDataEvent.Remove(value); } } private readonly AsyncEvent> _sentDataEvent = new AsyncEvent>(); - public event Func ReceivedEvent { add { _receivedEvent.Add(value); } remove { _receivedEvent.Remove(value); } } - private readonly AsyncEvent> _receivedEvent = new AsyncEvent>(); + public event Func, Task> ReceivedEvent { add { _receivedEvent.Add(value); } remove { _receivedEvent.Remove(value); } } + private readonly AsyncEvent, Task>> _receivedEvent = new AsyncEvent, Task>>(); public event Func ReceivedPacket { add { _receivedPacketEvent.Add(value); } remove { _receivedPacketEvent.Remove(value); } } private readonly AsyncEvent> _receivedPacketEvent = new AsyncEvent>(); public event Func Disconnected { add { _disconnectedEvent.Add(value); } remove { _disconnectedEvent.Remove(value); } } @@ -40,7 +41,9 @@ namespace Discord.Audio private readonly ScopedSerializer _serializer; private readonly SemaphoreSlim _connectionLock; private readonly MemoryStream _decompressionStream; - private readonly StreamReader _decompressionReader; + private readonly Span _textBuffer; + protected readonly ConcurrentQueue _formatters; + private CancellationTokenSource _connectCancelToken; private IUdpSocket _udp; private bool _isDisposed; @@ -52,39 +55,40 @@ namespace Discord.Audio public ushort UdpPort => _udp.Port; - internal DiscordVoiceAPIClient(ulong guildId, WebSocketProvider webSocketProvider, UdpSocketProvider udpSocketProvider, ScopedSerializer serializer) + internal DiscordVoiceApiClient(ulong guildId, WebSocketProvider webSocketProvider, UdpSocketProvider udpSocketProvider, ScopedSerializer serializer) { GuildId = guildId; _connectionLock = new SemaphoreSlim(1, 1); _udp = udpSocketProvider(); _udp.ReceivedDatagram += (data, index, count) => _receivedPacketEvent.InvokeAsync(data, index, count); _serializer = serializer; + _formatters = new ConcurrentQueue(); _decompressionStream = new MemoryStream(10 * 1024); //10 KB - _decompressionReader = new StreamReader(_decompressionStream); + _textBuffer = new Span(new byte[10 * 1024]); WebSocketClient = webSocketProvider(); //_gatewayClient.SetHeader("user-agent", DiscordConfig.UserAgent); //(Causes issues in .Net 4.6+) - WebSocketClient.BinaryMessage += async (data, index, count) => + WebSocketClient.Message += async (data, isText) => { - using (var compressed = new MemoryStream(data, index + 2, count - 2)) + if (!isText) { - _decompressionStream.Position = 0; - using (var zlib = new DeflateStream(compressed, CompressionMode.Decompress)) - zlib.CopyTo(_decompressionStream); - _decompressionStream.SetLength(_decompressionStream.Position); + using (var compressed = new MemoryStream(data.ToArray(), 2, data.Length - 2)) + { + _decompressionStream.Position = 0; + using (var zlib = new DeflateStream(compressed, CompressionMode.Decompress)) + zlib.CopyTo(_decompressionStream); + _decompressionStream.SetLength(_decompressionStream.Position); - _decompressionStream.Position = 0; - var msg = _serializer.FromJson(_decompressionReader); - if (msg != null) - await _receivedEvent.InvokeAsync((VoiceOpCode)msg.Operation, msg.Payload).ConfigureAwait(false); + _decompressionStream.Position = 0; + var msg = _serializer.ReadJson(_decompressionStream.ToReadOnlyBuffer()); + if (msg != null) + await _receivedEvent.InvokeAsync((VoiceOpCode)msg.Operation, msg.Payload).ConfigureAwait(false); + } } - }; - WebSocketClient.TextMessage += async text => - { - using (var reader = new StringReader(text)) + else { - var msg = _serializer.FromJson(reader); + var msg = _serializer.ReadJson(data); if (msg != null) await _receivedEvent.InvokeAsync((VoiceOpCode)msg.Operation, msg.Payload).ConfigureAwait(false); } @@ -112,12 +116,21 @@ namespace Discord.Audio public async Task SendAsync(VoiceOpCode opCode, object payload, RequestOptions options = null) { - byte[] bytes = null; - payload = new SocketFrame { Operation = (int)opCode, Payload = payload }; - if (payload != null) - bytes = Encoding.UTF8.GetBytes(SerializeJson(payload)); - await WebSocketClient.SendAsync(bytes, 0, bytes.Length, true).ConfigureAwait(false); - await _sentGatewayMessageEvent.InvokeAsync(opCode).ConfigureAwait(false); + if (_formatters.TryDequeue(out var data1)) + data1 = new ArrayFormatter(128, SymbolTable.InvariantUtf8); + if (_formatters.TryDequeue(out var data2)) + data2 = new ArrayFormatter(128, SymbolTable.InvariantUtf8); + try + { + payload = new SocketFrame { Operation = (int)opCode, Payload = SerializeJson(data1, payload) }; + await WebSocketClient.SendAsync(SerializeJson(data2, payload), true).ConfigureAwait(false); + await _sentGatewayMessageEvent.InvokeAsync(opCode).ConfigureAwait(false); + } + finally + { + _formatters.Enqueue(data1); + _formatters.Enqueue(data2); + } } public async Task SendAsync(byte[] data, int offset, int bytes) { @@ -252,17 +265,15 @@ namespace Discord.Audio //Helpers private static double ToMilliseconds(Stopwatch stopwatch) => Math.Round((double)stopwatch.ElapsedTicks / (double)Stopwatch.Frequency * 1000.0, 2); - private string SerializeJson(object value) + protected ReadOnlyBuffer SerializeJson(ArrayFormatter data, object value) { - var sb = new StringBuilder(256); - using (TextWriter writer = new StringWriter(sb, CultureInfo.InvariantCulture)) - _serializer.ToJson(writer, value); - return sb.ToString(); + _serializer.WriteJson(data, value); + return new ReadOnlyBuffer(data.Formatted.Array, 0, data.Formatted.Count); } - private T DeserializeJson(Stream jsonStream) + protected T DeserializeJson(ReadOnlyBuffer data) + where T : class, new() { - using (TextReader reader = new StreamReader(jsonStream)) - return _serializer.FromJson(reader); + return _serializer.ReadJson(data); } } } diff --git a/src/Discord.Net.WebSocket/Net/DefaultWebSocketClient.cs b/src/Discord.Net.WebSocket/Net/DefaultWebSocketClient.cs index 7d3f51083..7b0686641 100644 --- a/src/Discord.Net.WebSocket/Net/DefaultWebSocketClient.cs +++ b/src/Discord.Net.WebSocket/Net/DefaultWebSocketClient.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Net.WebSockets; -using System.Text; using System.Threading; using System.Threading.Tasks; @@ -16,8 +15,7 @@ namespace Discord.Net.WebSockets public const int SendChunkSize = 4 * 1024; //4KB private const int HR_TIMEOUT = -2147012894; - public event Func BinaryMessage; - public event Func TextMessage; + public event Func, bool, Task> Message; public event Func Closed; private readonly SemaphoreSlim _lock; @@ -146,27 +144,34 @@ namespace Discord.Net.WebSockets _cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_parentToken, _cancelTokenSource.Token).Token; } - public async Task SendAsync(byte[] data, int index, int count, bool isText) + public async Task SendAsync(ReadOnlyBuffer data, bool isText) { await _lock.WaitAsync().ConfigureAwait(false); try { if (_client == null) return; - int frameCount = (int)Math.Ceiling((double)count / SendChunkSize); + int frameCount = (int)Math.Ceiling((double)data.Length / SendChunkSize); + byte[] arr; + if (data.DangerousTryGetArray(out var segment)) + arr = segment.Array; + else + arr = data.ToArray(); + + int index = 0; for (int i = 0; i < frameCount; i++, index += SendChunkSize) { bool isLast = i == (frameCount - 1); int frameSize; if (isLast) - frameSize = count - (i * SendChunkSize); + frameSize = data.Length - (i * SendChunkSize); else frameSize = SendChunkSize; var type = isText ? WebSocketMessageType.Text : WebSocketMessageType.Binary; - await _client.SendAsync(new ArraySegment(data, index, count), type, isLast, _cancelToken).ConfigureAwait(false); + await _client.SendAsync(new ArraySegment(arr, index, frameSize), type, isLast, _cancelToken).ConfigureAwait(false); } } finally @@ -206,14 +211,7 @@ namespace Discord.Net.WebSockets //Use the internal buffer if we can get it resultCount = (int)stream.Length; -#if MSTRYBUFFER - if (stream.TryGetBuffer(out var streamBuffer)) - result = streamBuffer.Array; - else - result = stream.ToArray(); -#else result = stream.GetBuffer(); -#endif } } else @@ -222,14 +220,9 @@ namespace Discord.Net.WebSockets resultCount = socketResult.Count; result = buffer.Array; } - - if (socketResult.MessageType == WebSocketMessageType.Text) - { - string text = Encoding.UTF8.GetString(result, 0, resultCount); - await TextMessage(text).ConfigureAwait(false); - } - else - await BinaryMessage(result, 0, resultCount).ConfigureAwait(false); + + bool isText = socketResult.MessageType == WebSocketMessageType.Text; + await Message(new ReadOnlyBuffer(result, 0, resultCount), isText).ConfigureAwait(false); } } catch (Win32Exception ex) when (ex.HResult == HR_TIMEOUT) diff --git a/src/Discord.Net.Webhook/DiscordWebhookClient.cs b/src/Discord.Net.Webhook/DiscordWebhookClient.cs index 9695099ee..183711a9a 100644 --- a/src/Discord.Net.Webhook/DiscordWebhookClient.cs +++ b/src/Discord.Net.Webhook/DiscordWebhookClient.cs @@ -5,6 +5,7 @@ using System.IO; using System.Threading.Tasks; using System.Linq; using Discord.Logging; +using Discord.Serialization; namespace Discord.Webhook { @@ -13,8 +14,9 @@ namespace Discord.Webhook public event Func Log { add { _logEvent.Add(value); } remove { _logEvent.Remove(value); } } internal readonly AsyncEvent> _logEvent = new AsyncEvent>(); - private readonly ulong _webhookId; internal readonly Logger _restLogger; + private readonly ulong _webhookId; + private readonly ScopedSerializer _serializer; internal API.DiscordRestApiClient ApiClient { get; } internal LogManager LogManager { get; } @@ -27,7 +29,13 @@ namespace Discord.Webhook { _webhookId = webhookId; - ApiClient = CreateApiClient(config); + _serializer = Serializer.CreateScope(); + _serializer.Error += async ex => + { + await _restLogger.WarningAsync("Serializer Error", ex); + }; + + ApiClient = new API.DiscordRestApiClient(config.RestClientProvider, DiscordRestConfig.UserAgent, _serializer); LogManager = new LogManager(config.LogLevel); LogManager.Message += async msg => await _logEvent.InvokeAsync(msg).ConfigureAwait(false); @@ -43,8 +51,6 @@ namespace Discord.Webhook ApiClient.SentRequest += async (method, endpoint, millis) => await _restLogger.VerboseAsync($"{method} {endpoint}: {millis} ms").ConfigureAwait(false); ApiClient.LoginAsync(TokenType.Webhook, webhookToken).GetAwaiter().GetResult(); } - private static API.DiscordRestApiClient CreateApiClient(DiscordRestConfig config) - => new API.DiscordRestApiClient(config.RestClientProvider, DiscordRestConfig.UserAgent); public async Task SendMessageAsync(string text, bool isTTS = false, Embed[] embeds = null, string username = null, string avatarUrl = null, RequestOptions options = null) diff --git a/test/Discord.Net.Tests/Net/CachedRestClient.cs b/test/Discord.Net.Tests/Net/CachedRestClient.cs index 4bc8a386a..ca1922630 100644 --- a/test/Discord.Net.Tests/Net/CachedRestClient.cs +++ b/test/Discord.Net.Tests/Net/CachedRestClient.cs @@ -73,9 +73,9 @@ namespace Discord.Net string uri = Path.Combine(_baseUrl, endpoint); var bytes = await _blobCache.DownloadUrl(uri, _headers); - return new RestResponse(HttpStatusCode.OK, _headers, new MemoryStream(bytes)); + return new RestResponse(HttpStatusCode.OK, _headers, new ReadOnlyBuffer(bytes)); } - public Task SendAsync(string method, string endpoint, string json, CancellationToken cancelToken, bool headerOnly, string reason = null) + public Task SendAsync(string method, string endpoint, ReadOnlyBuffer json, CancellationToken cancelToken, bool headerOnly, string reason = null) { throw new InvalidOperationException("This RestClient does not support payloads."); }