| @@ -2,6 +2,7 @@ | |||
| <PropertyGroup> | |||
| <VersionPrefix>1.1.0-alpha</VersionPrefix> | |||
| <VersionSuffix></VersionSuffix> | |||
| <LangVersion>latest</LangVersion> | |||
| <Authors>RogueException</Authors> | |||
| <PackageTags>discord;discordapp</PackageTags> | |||
| <PackageProjectUrl>https://github.com/RogueException/Discord.Net</PackageProjectUrl> | |||
| @@ -18,7 +19,7 @@ | |||
| <VersionSuffix Condition=" '$(VersionSuffix)' == '' ">build-$(BuildNumber)</VersionSuffix> | |||
| </PropertyGroup> | |||
| <PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard1.3' Or '$(TargetFramework)' == 'net45' "> | |||
| <DefineConstants>$(DefineConstants);FILESYSTEM;DEFAULTUDPCLIENT;DEFAULTWEBSOCKET</DefineConstants> | |||
| <DefineConstants>$(DefineConstants);FILESYSTEM;DEFAULTUDPCLIENT;DEFAULTWEBSOCKET;MSBUFFER</DefineConstants> | |||
| </PropertyGroup> | |||
| <PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard1.3' "> | |||
| <DefineConstants>$(DefineConstants);FORMATSTR;UNIXTIME;MSTRYBUFFER;UDPDISPOSE</DefineConstants> | |||
| @@ -5,10 +5,16 @@ | |||
| <RootNamespace>Discord</RootNamespace> | |||
| <Description>The core components for the Discord.Net library.</Description> | |||
| <TargetFrameworks>net45;netstandard1.1;netstandard1.3</TargetFrameworks> | |||
| <AllowUnsafeBlocks>true</AllowUnsafeBlocks> | |||
| </PropertyGroup> | |||
| <ItemGroup> | |||
| <PackageReference Include="Newtonsoft.Json" Version="10.0.2" /> | |||
| <PackageReference Include="System.Buffers" Version="4.4.0-preview2-25405-01" /> | |||
| <PackageReference Include="System.Collections.Immutable" Version="1.3.1" /> | |||
| <PackageReference Include="System.Interactive.Async" Version="3.1.1" /> | |||
| <PackageReference Include="System.Memory" Version="4.4.0-preview2-25405-01" /> | |||
| <PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.4.0-preview2-25405-01" /> | |||
| </ItemGroup> | |||
| <ItemGroup> | |||
| <Folder Include="Serialization\" /> | |||
| </ItemGroup> | |||
| </Project> | |||
| @@ -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<RestResponse> SendAsync(string method, string endpoint, CancellationToken cancelToken, bool headerOnly = false, string reason = null); | |||
| Task<RestResponse> SendAsync(string method, string endpoint, string json, CancellationToken cancelToken, bool headerOnly = false, string reason = null); | |||
| Task<RestResponse> SendAsync(string method, string endpoint, ReadOnlyBuffer<byte> jsonPayload, CancellationToken cancelToken, bool headerOnly = false, string reason = null); | |||
| Task<RestResponse> SendAsync(string method, string endpoint, IReadOnlyDictionary<string, object> multipartParams, CancellationToken cancelToken, bool headerOnly = false, string reason = null); | |||
| } | |||
| } | |||
| @@ -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<string, string> Headers { get; } | |||
| public Stream Stream { get; } | |||
| public ReadOnlyBuffer<byte> Data { get; } | |||
| public RestResponse(HttpStatusCode statusCode, Dictionary<string, string> headers, Stream stream) | |||
| public RestResponse(HttpStatusCode statusCode, Dictionary<string, string> headers, ReadOnlyBuffer<byte> data) | |||
| { | |||
| StatusCode = statusCode; | |||
| Headers = headers; | |||
| Stream = stream; | |||
| Data = data; | |||
| } | |||
| } | |||
| } | |||
| @@ -6,8 +6,7 @@ namespace Discord.Net.WebSockets | |||
| { | |||
| public interface IWebSocketClient | |||
| { | |||
| event Func<byte[], int, int, Task> BinaryMessage; | |||
| event Func<string, Task> TextMessage; | |||
| event Func<ReadOnlyBuffer<byte>, bool, Task> Message; | |||
| event Func<Exception, Task> 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<byte> data, bool isText); | |||
| } | |||
| } | |||
| @@ -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<byte> ToSpan<T>(this T bufferSequence) where T : ISequence<ReadOnlyBuffer<byte>> | |||
| { | |||
| Position position = Position.First; | |||
| ReadOnlyBuffer<byte> buffer; | |||
| ResizableArray<byte> array = new ResizableArray<byte>(1024); | |||
| while (bufferSequence.TryGet(ref position, out buffer)) | |||
| { | |||
| array.AddAll(buffer.Span); | |||
| } | |||
| array.Resize(array.Count); | |||
| return array.Items.Slice(0, array.Count); | |||
| } | |||
| /// <summary> | |||
| /// Creates a new slice over the portion of the target array segment. | |||
| /// </summary> | |||
| /// <param name="arraySegment">The target array segment.</param> | |||
| /// </exception> | |||
| public static Span<T> Slice<T>(this ArraySegment<T> arraySegment) | |||
| { | |||
| return new Span<T>(arraySegment.Array, arraySegment.Offset, arraySegment.Count); | |||
| } | |||
| /// <summary> | |||
| /// Creates a new slice over the portion of the target array. | |||
| /// </summary> | |||
| /// <param name="array">The target array.</param> | |||
| /// <exception cref="System.ArgumentException"> | |||
| /// Thrown if the 'array' parameter is null. | |||
| /// </exception> | |||
| public static Span<T> Slice<T>(this T[] array) | |||
| { | |||
| return new Span<T>(array); | |||
| } | |||
| /// <summary> | |||
| /// Creates a new slice over the portion of the target array beginning | |||
| /// at 'start' index. | |||
| /// </summary> | |||
| /// <param name="array">The target array.</param> | |||
| /// <param name="start">The index at which to begin the slice.</param> | |||
| /// <exception cref="System.ArgumentException"> | |||
| /// Thrown if the 'array' parameter is null. | |||
| /// </exception> | |||
| /// <exception cref="System.ArgumentOutOfRangeException"> | |||
| /// Thrown when the specified start index is not in range (<0 or >&eq;length). | |||
| /// </exception> | |||
| public static Span<T> Slice<T>(this T[] array, int start) | |||
| { | |||
| return new Span<T>(array, start); | |||
| } | |||
| /// <summary> | |||
| /// Creates a new slice over the portion of the target array beginning | |||
| /// at 'start' index and with 'length' items. | |||
| /// </summary> | |||
| /// <param name="array">The target array.</param> | |||
| /// <param name="start">The index at which to begin the slice.</param> | |||
| /// <param name="length">The number of items in the new slice.</param> | |||
| /// <exception cref="System.ArgumentException"> | |||
| /// Thrown if the 'array' parameter is null. | |||
| /// </exception> | |||
| /// <exception cref="System.ArgumentOutOfRangeException"> | |||
| /// Thrown when the specified start or end index is not in range (<0 or >&eq;length). | |||
| /// </exception> | |||
| public static Span<T> Slice<T>(this T[] array, int start, int length) | |||
| { | |||
| return new Span<T>(array, start, length); | |||
| } | |||
| /// <summary> | |||
| /// Creates a new readonly span over the portion of the target string. | |||
| /// </summary> | |||
| /// <param name="text">The target string.</param> | |||
| /// <exception cref="System.ArgumentNullException">Thrown when <paramref name="text"/> is null.</exception> | |||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
| public static unsafe ReadOnlySpan<char> Slice(this string text) | |||
| { | |||
| if (text == null) | |||
| throw new ArgumentNullException(nameof(text)); | |||
| int textLength = text.Length; | |||
| if (textLength == 0) return ReadOnlySpan<char>.Empty; | |||
| fixed (char* charPointer = text) | |||
| { | |||
| return ReadOnlySpan<char>.DangerousCreate(text, ref *charPointer, textLength); | |||
| } | |||
| } | |||
| /// <summary> | |||
| /// Creates a new readonly span over the portion of the target string, beginning at 'start'. | |||
| /// </summary> | |||
| /// <param name="text">The target string.</param> | |||
| /// <param name="start">The index at which to begin this slice.</param> | |||
| /// <exception cref="System.ArgumentNullException">Thrown when <paramref name="text"/> is null.</exception> | |||
| /// <exception cref="System.ArgumentOutOfRangeException"> | |||
| /// Thrown when the specified <paramref name="start"/> index is not in range (<0 or >Length). | |||
| /// </exception> | |||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
| public static unsafe ReadOnlySpan<char> 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<char>.Empty; | |||
| fixed (char* charPointer = text) | |||
| { | |||
| return ReadOnlySpan<char>.DangerousCreate(text, ref *(charPointer + start), textLength - start); | |||
| } | |||
| } | |||
| /// <summary> | |||
| /// Creates a new readonly span over the portion of the target string, beginning at <paramref name="start"/>, of given <paramref name="length"/>. | |||
| /// </summary> | |||
| /// <param name="text">The target string.</param> | |||
| /// <param name="start">The index at which to begin this slice.</param> | |||
| /// <param name="length">The number of items in the span.</param> | |||
| /// <exception cref="System.ArgumentNullException">Thrown when <paramref name="text"/> is null.</exception> | |||
| /// <exception cref="System.ArgumentOutOfRangeException"> | |||
| /// Thrown when the specified <paramref name="start"/> or end index is not in range (<0 or >=Length). | |||
| /// </exception> | |||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
| public static unsafe ReadOnlySpan<char> 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<char>.Empty; | |||
| fixed (char* charPointer = text) | |||
| { | |||
| return ReadOnlySpan<char>.DangerousCreate(text, ref *(charPointer + start), length); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -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 | |||
| { | |||
| /// <summary> | |||
| /// Reads bytes as primitives with specific endianness | |||
| /// </summary> | |||
| /// <remarks> | |||
| /// For native formats, SpanExtensions.Read<T> should be used. | |||
| /// Use these helpers when you need to read specific endinanness. | |||
| /// </remarks> | |||
| public static class BufferReader | |||
| { | |||
| /// <summary> | |||
| /// Reads a structure of type <typeparamref name="T"/> out of a span of bytes. | |||
| /// </summary> | |||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
| public static T ReadBigEndian<[Primitive]T>(this ReadOnlySpan<byte> span) where T : struct | |||
| => BitConverter.IsLittleEndian ? UnsafeUtilities.Reverse(span.Read<T>()) : span.Read<T>(); | |||
| /// <summary> | |||
| /// Reads a structure of type <typeparamref name="T"/> out of a span of bytes. | |||
| /// </summary> | |||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
| public static T ReadLittleEndian<[Primitive]T>(this ReadOnlySpan<byte> span) where T : struct | |||
| => BitConverter.IsLittleEndian ? span.Read<T>() : UnsafeUtilities.Reverse(span.Read<T>()); | |||
| /// <summary> | |||
| /// Reads a structure of type <typeparamref name="T"/> out of a span of bytes. | |||
| /// </summary> | |||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
| public static T ReadBigEndian<[Primitive]T>(this Span<byte> span) where T : struct | |||
| => BitConverter.IsLittleEndian ? UnsafeUtilities.Reverse(span.Read<T>()) : span.Read<T>(); | |||
| /// <summary> | |||
| /// Reads a structure of type <typeparamref name="T"/> out of a span of bytes. | |||
| /// </summary> | |||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
| public static T ReadLittleEndian<[Primitive]T>(this Span<byte> span) where T : struct | |||
| => BitConverter.IsLittleEndian ? span.Read<T>() : UnsafeUtilities.Reverse(span.Read<T>()); | |||
| /// <summary> | |||
| /// Reads a structure of type T out of a slice of bytes. | |||
| /// </summary> | |||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
| public static T Read<[Primitive]T>(this Span<byte> slice) | |||
| where T : struct | |||
| { | |||
| RequiresInInclusiveRange(Unsafe.SizeOf<T>(), (uint)slice.Length); | |||
| return Unsafe.ReadUnaligned<T>(ref slice.DangerousGetPinnableReference()); | |||
| } | |||
| /// <summary> | |||
| /// Reads a structure of type T out of a slice of bytes. | |||
| /// </summary> | |||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
| public static T Read<[Primitive]T>(this ReadOnlySpan<byte> slice) | |||
| where T : struct | |||
| { | |||
| RequiresInInclusiveRange(Unsafe.SizeOf<T>(), (uint)slice.Length); | |||
| return Unsafe.ReadUnaligned<T>(ref slice.DangerousGetPinnableReference()); | |||
| } | |||
| /// <summary> | |||
| /// Reads a structure of type T out of a slice of bytes. | |||
| /// </summary> | |||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
| public static bool TryRead<[Primitive]T>(this ReadOnlySpan<byte> slice, out T value) | |||
| where T : struct | |||
| { | |||
| if (Unsafe.SizeOf<T>() > (uint)slice.Length) | |||
| { | |||
| value = default; | |||
| return false; | |||
| } | |||
| value = Unsafe.ReadUnaligned<T>(ref slice.DangerousGetPinnableReference()); | |||
| return true; | |||
| } | |||
| /// <summary> | |||
| /// Reads a structure of type T out of a slice of bytes. | |||
| /// </summary> | |||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
| public static bool TryRead<[Primitive]T>(this Span<byte> slice, out T value) | |||
| where T : struct | |||
| { | |||
| if (Unsafe.SizeOf<T>() > (uint)slice.Length) | |||
| { | |||
| value = default; | |||
| return false; | |||
| } | |||
| value = Unsafe.ReadUnaligned<T>(ref slice.DangerousGetPinnableReference()); | |||
| return true; | |||
| } | |||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
| public static void RequiresInInclusiveRange(int start, uint length) | |||
| { | |||
| if ((uint)start > length) | |||
| { | |||
| throw new ArgumentOutOfRangeException(); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -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 | |||
| { | |||
| /// <summary> | |||
| /// Writes endian-specific primitives into spans. | |||
| /// </summary> | |||
| /// <remarks> | |||
| /// Use these helpers when you need to write specific endinaness. | |||
| /// </remarks> | |||
| public static class BufferWriter | |||
| { | |||
| /// <summary> | |||
| /// Writes a structure of type T to a span of bytes. | |||
| /// </summary> | |||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
| public static void WriteBigEndian<[Primitive]T>(this Span<byte> span, T value) where T : struct | |||
| => span.Write(BitConverter.IsLittleEndian ? UnsafeUtilities.Reverse(value) : value); | |||
| /// <summary> | |||
| /// Writes a structure of type T to a span of bytes. | |||
| /// </summary> | |||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
| public static void WriteLittleEndian<[Primitive]T>(this Span<byte> span, T value) where T : struct | |||
| => span.Write(BitConverter.IsLittleEndian ? value : UnsafeUtilities.Reverse(value)); | |||
| /// <summary> | |||
| /// Writes a structure of type T into a slice of bytes. | |||
| /// </summary> | |||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
| public static void Write<[Primitive]T>(this Span<byte> slice, T value) | |||
| where T : struct | |||
| { | |||
| if ((uint)Unsafe.SizeOf<T>() > (uint)slice.Length) | |||
| { | |||
| throw new ArgumentOutOfRangeException(); | |||
| } | |||
| Unsafe.WriteUnaligned<T>(ref slice.DangerousGetPinnableReference(), value); | |||
| } | |||
| /// <summary> | |||
| /// Writes a structure of type T into a slice of bytes. | |||
| /// </summary> | |||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
| public static bool TryWrite<[Primitive]T>(this Span<byte> slice, T value) | |||
| where T : struct | |||
| { | |||
| if (Unsafe.SizeOf<T>() > (uint)slice.Length) | |||
| { | |||
| return false; | |||
| } | |||
| Unsafe.WriteUnaligned<T>(ref slice.DangerousGetPinnableReference(), value); | |||
| return true; | |||
| } | |||
| } | |||
| } | |||
| @@ -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 | |||
| { | |||
| /// <summary> | |||
| /// A collection of unsafe helper methods that we cannot implement in C#. | |||
| /// NOTE: these can be used for VeryBadThings(tm), so tread with care... | |||
| /// </summary> | |||
| internal static class UnsafeUtilities | |||
| { | |||
| /// <summary> | |||
| /// Reverses a primitive value - performs an endianness swap | |||
| /// </summary> | |||
| 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<T>(&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<T>(&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<T>(&val); | |||
| } | |||
| else { | |||
| // default implementation | |||
| int len = Unsafe.SizeOf<T>(); | |||
| 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<T>(val); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -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<T> | |||
| { | |||
| // 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<T> | |||
| // else, object _arrayOrOwnedBuffer is a T[] | |||
| readonly object _arrayOrOwnedBuffer; | |||
| readonly int _index; | |||
| readonly int _length; | |||
| private const int bitMask = 0x7FFFFFFF; | |||
| internal Buffer(OwnedBuffer<T> 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<T>(Buffer<T> 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<T>(Unsafe.As<OwnedBuffer<T>>(buffer._arrayOrOwnedBuffer), buffer._index, buffer._length); | |||
| return new ReadOnlyBuffer<T>(Unsafe.As<T[]>(buffer._arrayOrOwnedBuffer), buffer._index, buffer._length); | |||
| } | |||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
| public static implicit operator Buffer<T>(T[] array) | |||
| { | |||
| return new Buffer<T>(array, 0, array.Length); | |||
| } | |||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
| public static implicit operator Buffer<T>(ArraySegment<T> arraySegment) | |||
| { | |||
| return new Buffer<T>(arraySegment.Array, arraySegment.Offset, arraySegment.Count); | |||
| } | |||
| public static Buffer<T> Empty { get; } = OwnedBuffer<T>.EmptyArray; | |||
| public int Length => _length; | |||
| public bool IsEmpty => Length == 0; | |||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
| public Buffer<T> 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<T>(Unsafe.As<OwnedBuffer<T>>(_arrayOrOwnedBuffer), _index + start, _length - start); | |||
| return new Buffer<T>(Unsafe.As<T[]>(_arrayOrOwnedBuffer), _index + start, _length - start); | |||
| } | |||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
| public Buffer<T> 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<T>(Unsafe.As<OwnedBuffer<T>>(_arrayOrOwnedBuffer), _index + start, length); | |||
| return new Buffer<T>(Unsafe.As<T[]>(_arrayOrOwnedBuffer), _index + start, length); | |||
| } | |||
| public Span<T> Span | |||
| { | |||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
| get | |||
| { | |||
| if (_index < 0) | |||
| return Unsafe.As<OwnedBuffer<T>>(_arrayOrOwnedBuffer).AsSpan(_index & bitMask, _length); | |||
| return new Span<T>(Unsafe.As<T[]>(_arrayOrOwnedBuffer), _index, _length); | |||
| } | |||
| } | |||
| public BufferHandle Retain(bool pin = false) | |||
| { | |||
| BufferHandle bufferHandle; | |||
| if (pin) | |||
| { | |||
| if (_index < 0) | |||
| { | |||
| bufferHandle = Unsafe.As<OwnedBuffer<T>>(_arrayOrOwnedBuffer).Pin(_index & bitMask); | |||
| } | |||
| else | |||
| { | |||
| var handle = GCHandle.Alloc(Unsafe.As<T[]>(_arrayOrOwnedBuffer), GCHandleType.Pinned); | |||
| unsafe | |||
| { | |||
| var pointer = OwnedBuffer<T>.Add((void*)handle.AddrOfPinnedObject(), _index); | |||
| bufferHandle = new BufferHandle(null, pointer, handle); | |||
| } | |||
| } | |||
| } | |||
| else | |||
| { | |||
| if (_index < 0) | |||
| { | |||
| Unsafe.As<OwnedBuffer<T>>(_arrayOrOwnedBuffer).Retain(); | |||
| bufferHandle = new BufferHandle(Unsafe.As<OwnedBuffer<T>>(_arrayOrOwnedBuffer)); | |||
| } | |||
| else | |||
| { | |||
| bufferHandle = new BufferHandle(null); | |||
| } | |||
| } | |||
| return bufferHandle; | |||
| } | |||
| public bool TryGetArray(out ArraySegment<T> arraySegment) | |||
| { | |||
| if (_index < 0) | |||
| { | |||
| if (Unsafe.As<OwnedBuffer<T>>(_arrayOrOwnedBuffer).TryGetArray(out var segment)) | |||
| { | |||
| arraySegment = new ArraySegment<T>(segment.Array, segment.Offset + (_index & bitMask), _length); | |||
| return true; | |||
| } | |||
| } | |||
| else | |||
| { | |||
| arraySegment = new ArraySegment<T>(Unsafe.As<T[]>(_arrayOrOwnedBuffer), _index, _length); | |||
| return true; | |||
| } | |||
| arraySegment = default; | |||
| return false; | |||
| } | |||
| public T[] ToArray() => Span.ToArray(); | |||
| public void CopyTo(Span<T> span) => Span.CopyTo(span); | |||
| public void CopyTo(Buffer<T> buffer) => Span.CopyTo(buffer.Span); | |||
| public bool TryCopyTo(Span<T> span) => Span.TryCopyTo(span); | |||
| public bool TryCopyTo(Buffer<T> buffer) => Span.TryCopyTo(buffer.Span); | |||
| [EditorBrowsable(EditorBrowsableState.Never)] | |||
| public override bool Equals(object obj) | |||
| { | |||
| if (obj is ReadOnlyBuffer<T> readOnlyBuffer) | |||
| { | |||
| return readOnlyBuffer.Equals(this); | |||
| } | |||
| else if (obj is Buffer<T> buffer) | |||
| { | |||
| return Equals(buffer); | |||
| } | |||
| else | |||
| { | |||
| return false; | |||
| } | |||
| } | |||
| public bool Equals(Buffer<T> 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()); | |||
| } | |||
| } | |||
| } | |||
| @@ -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<T>(this Buffer<T> first, Buffer<T> second) where T : struct, IEquatable<T> | |||
| { | |||
| return first.Span.SequenceEqual(second.Span); | |||
| } | |||
| public static bool SequenceEqual<T>(this ReadOnlyBuffer<T> first, ReadOnlyBuffer<T> second) where T : struct, IEquatable<T> | |||
| { | |||
| return first.Span.SequenceEqual(second.Span); | |||
| } | |||
| } | |||
| } | |||
| @@ -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; | |||
| } | |||
| } | |||
| } | |||
| @@ -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<byte> Rent(int minimumBufferSize); | |||
| public void Dispose() | |||
| { | |||
| Dispose(true); | |||
| GC.SuppressFinalize(this); | |||
| } | |||
| ~BufferPool() | |||
| { | |||
| Dispose(false); | |||
| } | |||
| protected abstract void Dispose(bool disposing); | |||
| } | |||
| } | |||
| @@ -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<byte> Buffer { get; } | |||
| void Advance(int bytes); | |||
| /// <summary>desiredBufferLength == 0 means "i don't care"</summary> | |||
| void Enlarge(int desiredBufferLength = 0); | |||
| } | |||
| } | |||
| @@ -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; } | |||
| } | |||
| } | |||
| @@ -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<T> : IDisposable, IRetainable | |||
| { | |||
| protected OwnedBuffer() { } | |||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
| public static implicit operator OwnedBuffer<T>(T[] array) | |||
| { | |||
| return new Internal.OwnedArray<T>(array); | |||
| } | |||
| public abstract int Length { get; } | |||
| public abstract Span<T> AsSpan(int index, int length); | |||
| public virtual Span<T> AsSpan() | |||
| { | |||
| if (IsDisposed) BufferPrimitivesThrowHelper.ThrowObjectDisposedException(nameof(OwnedBuffer<T>)); | |||
| return AsSpan(0, Length); | |||
| } | |||
| public Buffer<T> Buffer | |||
| { | |||
| get { | |||
| if (IsDisposed) BufferPrimitivesThrowHelper.ThrowObjectDisposedException(nameof(OwnedBuffer<T>)); | |||
| return new Buffer<T>(this, 0, Length); | |||
| } | |||
| } | |||
| public ReadOnlyBuffer<T> ReadOnlyBuffer | |||
| { | |||
| get { | |||
| if (IsDisposed) BufferPrimitivesThrowHelper.ThrowObjectDisposedException(nameof(OwnedBuffer<T>)); | |||
| return new ReadOnlyBuffer<T>(this, 0, Length); | |||
| } | |||
| } | |||
| public abstract BufferHandle Pin(int index = 0); | |||
| protected internal abstract bool TryGetArray(out ArraySegment<T> 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<T>() * (ulong)offset); | |||
| } | |||
| internal static readonly T[] EmptyArray = new T[0]; | |||
| } | |||
| } | |||
| @@ -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<T> | |||
| { | |||
| // 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<T> | |||
| // else, object _arrayOrOwnedBuffer is a T[] | |||
| readonly object _arrayOrOwnedBuffer; | |||
| readonly int _index; | |||
| readonly int _length; | |||
| private const int bitMask = 0x7FFFFFFF; | |||
| internal ReadOnlyBuffer(OwnedBuffer<T> 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>(T[] array) | |||
| { | |||
| return new ReadOnlyBuffer<T>(array, 0, array.Length); | |||
| } | |||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
| public static implicit operator ReadOnlyBuffer<T>(ArraySegment<T> arraySegment) | |||
| { | |||
| return new ReadOnlyBuffer<T>(arraySegment.Array, arraySegment.Offset, arraySegment.Count); | |||
| } | |||
| public static ReadOnlyBuffer<T> Empty { get; } = OwnedBuffer<T>.EmptyArray; | |||
| public int Length => _length; | |||
| public bool IsEmpty => Length == 0; | |||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
| public ReadOnlyBuffer<T> 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<T>(Unsafe.As<OwnedBuffer<T>>(_arrayOrOwnedBuffer), _index + start, _length - start); | |||
| return new ReadOnlyBuffer<T>(Unsafe.As<T[]>(_arrayOrOwnedBuffer), _index + start, _length - start); | |||
| } | |||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
| public ReadOnlyBuffer<T> 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<T>(Unsafe.As<OwnedBuffer<T>>(_arrayOrOwnedBuffer), _index + start, length); | |||
| return new ReadOnlyBuffer<T>(Unsafe.As<T[]>(_arrayOrOwnedBuffer), _index + start, length); | |||
| } | |||
| public ReadOnlySpan<T> Span | |||
| { | |||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
| get | |||
| { | |||
| if (_index < 0) | |||
| return Unsafe.As<OwnedBuffer<T>>(_arrayOrOwnedBuffer).AsSpan(_index & bitMask, _length); | |||
| return new ReadOnlySpan<T>(Unsafe.As<T[]>(_arrayOrOwnedBuffer), _index, _length); | |||
| } | |||
| } | |||
| public BufferHandle Retain(bool pin = false) | |||
| { | |||
| BufferHandle bufferHandle; | |||
| if (pin) | |||
| { | |||
| if (_index < 0) | |||
| { | |||
| bufferHandle = Unsafe.As<OwnedBuffer<T>>(_arrayOrOwnedBuffer).Pin(_index & bitMask); | |||
| } | |||
| else | |||
| { | |||
| var handle = GCHandle.Alloc(Unsafe.As<T[]>(_arrayOrOwnedBuffer), GCHandleType.Pinned); | |||
| unsafe | |||
| { | |||
| var pointer = OwnedBuffer<T>.Add((void*)handle.AddrOfPinnedObject(), _index); | |||
| bufferHandle = new BufferHandle(null, pointer, handle); | |||
| } | |||
| } | |||
| } | |||
| else | |||
| { | |||
| if (_index < 0) | |||
| { | |||
| Unsafe.As<OwnedBuffer<T>>(_arrayOrOwnedBuffer).Retain(); | |||
| bufferHandle = new BufferHandle(Unsafe.As<OwnedBuffer<T>>(_arrayOrOwnedBuffer)); | |||
| } | |||
| else | |||
| { | |||
| bufferHandle = new BufferHandle(null); | |||
| } | |||
| } | |||
| return bufferHandle; | |||
| } | |||
| public T[] ToArray() => Span.ToArray(); | |||
| [EditorBrowsable(EditorBrowsableState.Never)] | |||
| public bool DangerousTryGetArray(out ArraySegment<T> arraySegment) | |||
| { | |||
| if (_index < 0) | |||
| { | |||
| if (Unsafe.As<OwnedBuffer<T>>(_arrayOrOwnedBuffer).TryGetArray(out var segment)) | |||
| { | |||
| arraySegment = new ArraySegment<T>(segment.Array, segment.Offset + (_index & bitMask), _length); | |||
| return true; | |||
| } | |||
| } | |||
| else | |||
| { | |||
| arraySegment = new ArraySegment<T>(Unsafe.As<T[]>(_arrayOrOwnedBuffer), _index, _length); | |||
| return true; | |||
| } | |||
| arraySegment = default; | |||
| return false; | |||
| } | |||
| public void CopyTo(Span<T> span) => Span.CopyTo(span); | |||
| public void CopyTo(Buffer<T> buffer) => Span.CopyTo(buffer.Span); | |||
| public bool TryCopyTo(Span<T> span) => Span.TryCopyTo(span); | |||
| public bool TryCopyTo(Buffer<T> buffer) => Span.TryCopyTo(buffer.Span); | |||
| [EditorBrowsable(EditorBrowsableState.Never)] | |||
| public override bool Equals(object obj) | |||
| { | |||
| if (obj is ReadOnlyBuffer<T> readOnlyBuffer) | |||
| { | |||
| return Equals(readOnlyBuffer); | |||
| } | |||
| else if (obj is Buffer<T> buffer) | |||
| { | |||
| return Equals(buffer); | |||
| } | |||
| else | |||
| { | |||
| return false; | |||
| } | |||
| } | |||
| public bool Equals(ReadOnlyBuffer<T> 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()); | |||
| } | |||
| } | |||
| } | |||
| @@ -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<byte> source, Span<byte> destination, out int bytesConsumed, out int bytesWritten); | |||
| } | |||
| public enum TransformationStatus | |||
| { | |||
| Done, | |||
| DestinationTooSmall, | |||
| NeedMoreSourceData, | |||
| InvalidData // TODO: how do we communicate details of the error | |||
| } | |||
| } | |||
| @@ -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<byte> Rent(int minimumBufferSize) | |||
| { | |||
| var buffer = new ArrayPoolBuffer(minimumBufferSize); | |||
| return buffer; | |||
| } | |||
| protected override void Dispose(bool disposing) | |||
| { | |||
| } | |||
| private sealed class ArrayPoolBuffer : OwnedBuffer<byte> | |||
| { | |||
| byte[] _array; | |||
| bool _disposed; | |||
| int _referenceCount; | |||
| public ArrayPoolBuffer(int size) | |||
| { | |||
| _array = ArrayPool<byte>.Shared.Rent(size); | |||
| } | |||
| public override int Length => _array.Length; | |||
| public override bool IsDisposed => _disposed; | |||
| public override bool IsRetained => _referenceCount > 0; | |||
| public override Span<byte> AsSpan(int index, int length) | |||
| { | |||
| if (IsDisposed) BufferPrimitivesThrowHelper.ThrowObjectDisposedException(nameof(ArrayPoolBuffer)); | |||
| return new Span<byte>(_array, index, length); | |||
| } | |||
| protected override void Dispose(bool disposing) | |||
| { | |||
| var array = Interlocked.Exchange(ref _array, null); | |||
| if (array != null) { | |||
| _disposed = true; | |||
| ArrayPool<byte>.Shared.Return(array); | |||
| } | |||
| } | |||
| protected internal override bool TryGetArray(out ArraySegment<byte> arraySegment) | |||
| { | |||
| if (IsDisposed) BufferPrimitivesThrowHelper.ThrowObjectDisposedException(nameof(ManagedBufferPool)); | |||
| arraySegment = new ArraySegment<byte>(_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(); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -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<T> : OwnedBuffer<T> | |||
| { | |||
| 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>(T[] array) => new OwnedArray<T>(array); | |||
| public override int Length => _array.Length; | |||
| public override Span<T> AsSpan(int index, int length) | |||
| { | |||
| if (IsDisposed) BufferPrimitivesThrowHelper.ThrowObjectDisposedException(nameof(OwnedArray<T>)); | |||
| return new Span<T>(_array, index, length); | |||
| } | |||
| public override Span<T> AsSpan() | |||
| { | |||
| if (IsDisposed) BufferPrimitivesThrowHelper.ThrowObjectDisposedException(nameof(OwnedArray<T>)); | |||
| return new Span<T>(_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<T> arraySegment) | |||
| { | |||
| if (IsDisposed) BufferPrimitivesThrowHelper.ThrowObjectDisposedException(nameof(OwnedArray<T>)); | |||
| arraySegment = new ArraySegment<T>(_array); | |||
| return true; | |||
| } | |||
| protected override void Dispose(bool disposing) | |||
| { | |||
| _array = null; | |||
| } | |||
| public override void Retain() | |||
| { | |||
| if (IsDisposed) BufferPrimitivesThrowHelper.ThrowObjectDisposedException(nameof(OwnedArray<T>)); | |||
| 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; | |||
| } | |||
| } | |||
| @@ -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<T> | |||
| { | |||
| private ReadOnlyBuffer<T> _buffer; | |||
| public BufferDebuggerView(Buffer<T> buffer) | |||
| { | |||
| _buffer = buffer; | |||
| } | |||
| [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] | |||
| public T[] Items | |||
| { | |||
| get { | |||
| return _buffer.ToArray(); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -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 | |||
| } | |||
| } | |||
| @@ -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<T>(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>(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(); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -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)); | |||
| } | |||
| } | |||
| } | |||
| @@ -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 | |||
| { | |||
| } | |||
| } | |||
| @@ -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<T> | |||
| { | |||
| private ReadOnlyBuffer<T> _buffer; | |||
| public ReadOnlyBufferDebuggerView(ReadOnlyBuffer<T> buffer) | |||
| { | |||
| _buffer = buffer; | |||
| } | |||
| [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] | |||
| public T[] Items | |||
| { | |||
| get { | |||
| return _buffer.ToArray(); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -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<T> : ISequence<T> | |||
| { | |||
| ResizableArray<T> _items; | |||
| public ArrayList() | |||
| { | |||
| _items = new ResizableArray<T>(0); | |||
| } | |||
| public ArrayList(int capacity) | |||
| { | |||
| _items = new ResizableArray<T>(capacity); | |||
| } | |||
| public int Length => _items.Count; | |||
| public T this[int index] => _items[index]; | |||
| public void Add(T item) | |||
| { | |||
| _items.Add(item); | |||
| } | |||
| public SequenceEnumerator<T> GetEnumerator() | |||
| { | |||
| return new SequenceEnumerator<T>(this); | |||
| } | |||
| public bool TryGet(ref Position position, out T item, bool advance = true) | |||
| { | |||
| return _items.TryGet(ref position, out item, advance); | |||
| } | |||
| } | |||
| } | |||
| @@ -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<T> | |||
| { | |||
| /// <summary> | |||
| /// | |||
| /// </summary> | |||
| /// <param name="position"></param> | |||
| /// <param name="advance"></param> | |||
| /// <returns></returns> | |||
| /// <remarks></remarks> | |||
| bool TryGet(ref Position position, out T item, bool advance = true); | |||
| } | |||
| } | |||
| @@ -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<Position> | |||
| { | |||
| 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; | |||
| } | |||
| } | |||
| } | |||
| @@ -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<T> like type designed to be embeded in other types | |||
| public struct ResizableArray<T> | |||
| { | |||
| 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<T> items) | |||
| { | |||
| if (items.Length > _array.Length - _count) { | |||
| Resize(items.Length + _count); | |||
| } | |||
| items.CopyTo(new Span<T>(_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<T>(_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<T> Full => new ArraySegment<T>(_array, 0, _count); | |||
| public ArraySegment<T> Free => new ArraySegment<T>(_array, _count, _array.Length - _count); | |||
| } | |||
| } | |||
| @@ -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<T> | |||
| { | |||
| Position _position; | |||
| ISequence<T> _sequence; | |||
| T _current; | |||
| bool first; // this is needed so that MoveNext does not advance the first time it's called | |||
| public SequenceEnumerator(ISequence<T> 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; | |||
| } | |||
| } | |||
| @@ -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<TFormatter, T0>(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<TFormatter, T0, T1>(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<TFormatter, T0, T1, T2>(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<TFormatter, T0, T1, T2, T3>(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<TFormatter, T0, T1, T2, T3, T4>(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<TFormatter>(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<TFormatter, T>(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 }; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -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<byte> _buffer; | |||
| SymbolTable _symbolTable; | |||
| ArrayPool<byte> _pool; | |||
| public ArrayFormatter(int capacity, SymbolTable symbolTable, ArrayPool<byte> pool = null) | |||
| { | |||
| _pool = pool ?? ArrayPool<byte>.Shared; | |||
| _symbolTable = symbolTable; | |||
| _buffer = new ResizableArray<byte>(_pool.Rent(capacity)); | |||
| } | |||
| public int CommitedByteCount => _buffer.Count; | |||
| public void Clear() { | |||
| _buffer.Count = 0; | |||
| } | |||
| public ArraySegment<byte> Free => _buffer.Free; | |||
| public ArraySegment<byte> Formatted => _buffer.Full; | |||
| public SymbolTable SymbolTable => _symbolTable; | |||
| public Span<byte> 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"); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -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<TOutput> : 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<byte> Buffer => _output.Buffer; | |||
| public SymbolTable SymbolTable => _symbolTable; | |||
| public void Advance(int bytes) => _output.Advance(bytes); | |||
| public void Enlarge(int desiredBufferLength = 0) => _output.Enlarge(desiredBufferLength); | |||
| } | |||
| } | |||
| @@ -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<TSequence> CreateFormatter<TSequence>(this TSequence sequence, SymbolTable symbolTable = null) where TSequence : ISequence<Buffer<byte>> | |||
| { | |||
| return new SequenceFormatter<TSequence>(sequence, symbolTable); | |||
| } | |||
| } | |||
| public class SequenceFormatter<TSequence> : ITextOutput where TSequence : ISequence<Buffer<byte>> | |||
| { | |||
| ISequence<Buffer<byte>> _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<byte> IOutput.Buffer | |||
| { | |||
| get { | |||
| return Current.Span.Slice(_currentWrittenBytes); | |||
| } | |||
| } | |||
| private Buffer<byte> Current { | |||
| get { | |||
| Buffer<byte> result; | |||
| if (!_buffers.TryGet(ref _currentPosition, out result, advance: false)) { throw new InvalidOperationException(); } | |||
| return result; | |||
| } | |||
| } | |||
| private Buffer<byte> Previous | |||
| { | |||
| get { | |||
| Buffer<byte> 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<byte> 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; | |||
| } | |||
| } | |||
| } | |||
| @@ -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<byte> _pool; | |||
| public StreamFormatter(Stream stream, ArrayPool<byte> pool) : this(stream, SymbolTable.InvariantUtf16, pool) | |||
| { | |||
| } | |||
| public StreamFormatter(Stream stream, SymbolTable symbolTable, ArrayPool<byte> pool, int bufferSize = 256) | |||
| { | |||
| _pool = pool; | |||
| _buffer = null; | |||
| if (bufferSize > 0) | |||
| { | |||
| _buffer = _pool.Rent(bufferSize); | |||
| } | |||
| _symbolTable = symbolTable; | |||
| _stream = stream; | |||
| } | |||
| Span<byte> IOutput.Buffer | |||
| { | |||
| get | |||
| { | |||
| if (_buffer == null) | |||
| { | |||
| _buffer = _pool.Rent(256); | |||
| } | |||
| return new Span<byte>(_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; | |||
| } | |||
| } | |||
| /// <summary> | |||
| /// Returns buffers to the pool | |||
| /// </summary> | |||
| public void Dispose() | |||
| { | |||
| _pool.Return(_buffer); | |||
| _buffer = null; | |||
| _stream = null; | |||
| } | |||
| } | |||
| } | |||
| @@ -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<byte> _buffer; | |||
| ArrayPool<byte> _pool; | |||
| public SymbolTable SymbolTable { get; set; } = SymbolTable.InvariantUtf16; | |||
| public StringFormatter(int characterCapacity = 32, ArrayPool<byte> pool = null) | |||
| { | |||
| if (pool == null) _pool = ArrayPool<byte>.Shared; | |||
| else _pool = pool; | |||
| _buffer = new ResizableArray<byte>(_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<byte> | |||
| public void Append(string text) | |||
| { | |||
| foreach (char character in text) | |||
| { | |||
| Append(character); | |||
| } | |||
| } | |||
| //TODO: this should use Span<byte> | |||
| public void Append(ReadOnlySpan<char> 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<byte> 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; | |||
| } | |||
| } | |||
| } | |||
| @@ -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<TFormatter, T>(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<TFormatter, T>(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<TFormatter>(this TFormatter formatter, string value, SymbolTable symbolTable) where TFormatter : IOutput | |||
| { | |||
| formatter.Append(value.AsSpan(), symbolTable); | |||
| } | |||
| public static bool TryAppend<TFormatter>(this TFormatter formatter, string value, SymbolTable symbolTable) where TFormatter : IOutput | |||
| { | |||
| return formatter.TryAppend(value.AsSpan(), symbolTable); | |||
| } | |||
| public static void Append<TFormatter>(this TFormatter formatter, ReadOnlySpan<char> 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<TFormatter>(this TFormatter formatter, ReadOnlySpan<char> 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<TFormatter>(this TFormatter formatter, char value, SymbolTable symbolTable) where TFormatter : IOutput | |||
| { | |||
| while (!formatter.TryAppend(value, symbolTable)) { | |||
| formatter.Enlarge(); | |||
| } | |||
| } | |||
| public static bool TryAppend<TFormatter>(this TFormatter formatter, char value, SymbolTable symbolTable) where TFormatter : IOutput | |||
| { | |||
| unsafe | |||
| { | |||
| ReadOnlySpan<char> input = new ReadOnlySpan<char>(&value, 1); | |||
| return formatter.TryAppend(input, symbolTable); | |||
| } | |||
| } | |||
| public static void Append<TFormatter>(this TFormatter formatter, Utf8String value, SymbolTable encoder) where TFormatter : IOutput | |||
| { | |||
| while (!formatter.TryAppend(value, encoder)) { | |||
| formatter.Enlarge(); | |||
| } | |||
| } | |||
| public static bool TryAppend<TFormatter>(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<TFormatter>(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<TFormatter>(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<TFormatter>(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<TFormatter>(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<TFormatter>(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<TFormatter>(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<TFormatter>(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<TFormatter>(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<TFormatter>(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<TFormatter>(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<TFormatter>(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<TFormatter>(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<TFormatter>(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<TFormatter>(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<TFormatter>(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<TFormatter>(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<TFormatter>(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<TFormatter>(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<TFormatter>(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<TFormatter>(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<TFormatter>(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<TFormatter>(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<TFormatter>(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<TFormatter>(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<TFormatter>(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<TFormatter>(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<TFormatter>(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<TFormatter>(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; | |||
| } | |||
| } | |||
| } | |||
| @@ -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; } | |||
| } | |||
| } | |||
| @@ -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<TFormatter, T>(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<TFormatter, T>(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<TFormatter>(this TFormatter formatter, byte value, ParsedFormat format = default) where TFormatter : ITextOutput | |||
| { | |||
| while (!formatter.TryAppend(value, format)) { | |||
| formatter.Enlarge(); | |||
| } | |||
| } | |||
| public static bool TryAppend<TFormatter>(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<TFormatter>(this TFormatter formatter, sbyte value, ParsedFormat format = default) where TFormatter : ITextOutput | |||
| { | |||
| while (!formatter.TryAppend(value, format)) { | |||
| formatter.Enlarge(); | |||
| } | |||
| } | |||
| public static bool TryAppend<TFormatter>(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<TFormatter>(this TFormatter formatter, ushort value, ParsedFormat format = default) where TFormatter : ITextOutput | |||
| { | |||
| while (!formatter.TryAppend(value, format)) { | |||
| formatter.Enlarge(); | |||
| } | |||
| } | |||
| public static bool TryAppend<TFormatter>(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<TFormatter>(this TFormatter formatter, short value, ParsedFormat format = default) where TFormatter : ITextOutput | |||
| { | |||
| while (!formatter.TryAppend(value, format)) { | |||
| formatter.Enlarge(); | |||
| } | |||
| } | |||
| public static bool TryAppend<TFormatter>(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<TFormatter>(this TFormatter formatter, uint value, ParsedFormat format = default) where TFormatter : ITextOutput | |||
| { | |||
| while (!formatter.TryAppend(value, format)) { | |||
| formatter.Enlarge(); | |||
| } | |||
| } | |||
| public static bool TryAppend<TFormatter>(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<TFormatter>(this TFormatter formatter, int value, ParsedFormat format = default) where TFormatter : ITextOutput | |||
| { | |||
| while (!formatter.TryAppend(value, format)) { | |||
| formatter.Enlarge(); | |||
| } | |||
| } | |||
| public static bool TryAppend<TFormatter>(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<TFormatter>(this TFormatter formatter, ulong value, ParsedFormat format = default) where TFormatter : ITextOutput | |||
| { | |||
| while (!formatter.TryAppend(value, format)) { | |||
| formatter.Enlarge(); | |||
| } | |||
| } | |||
| public static bool TryAppend<TFormatter>(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<TFormatter>(this TFormatter formatter, long value, ParsedFormat format = default) where TFormatter : ITextOutput | |||
| { | |||
| while (!formatter.TryAppend(value, format)) { | |||
| formatter.Enlarge(); | |||
| } | |||
| } | |||
| public static bool TryAppend<TFormatter>(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<TFormatter>(this TFormatter formatter, char value) where TFormatter : ITextOutput | |||
| { | |||
| while (!formatter.TryAppend(value)) { | |||
| formatter.Enlarge(); | |||
| } | |||
| } | |||
| public static bool TryAppend<TFormatter>(this TFormatter formatter, char value) where TFormatter : ITextOutput | |||
| { | |||
| return formatter.TryAppend(value, formatter.SymbolTable); | |||
| } | |||
| public static void Append<TFormatter>(this TFormatter formatter, ReadOnlySpan<char> value) where TFormatter : ITextOutput | |||
| { | |||
| while (!formatter.TryAppend(value)) { | |||
| formatter.Enlarge(); | |||
| } | |||
| } | |||
| public static bool TryAppend<TFormatter>(this TFormatter formatter, ReadOnlySpan<char> value) where TFormatter : ITextOutput | |||
| { | |||
| return formatter.TryAppend(value, formatter.SymbolTable); | |||
| } | |||
| public static void Append<TFormatter>(this TFormatter formatter, string value) where TFormatter : ITextOutput | |||
| { | |||
| while (!formatter.TryAppend(value)) { | |||
| formatter.Enlarge(); | |||
| } | |||
| } | |||
| public static bool TryAppend<TFormatter>(this TFormatter formatter, string value) where TFormatter : ITextOutput | |||
| { | |||
| return formatter.TryAppend(value, formatter.SymbolTable); | |||
| } | |||
| public static void Append<TFormatter>(this TFormatter formatter, Utf8String value) where TFormatter : ITextOutput | |||
| { | |||
| while (!formatter.TryAppend(value)) { | |||
| formatter.Enlarge(); | |||
| } | |||
| } | |||
| public static bool TryAppend<TFormatter>(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<TFormatter>(this TFormatter formatter, Guid value, ParsedFormat format = default) where TFormatter : ITextOutput | |||
| { | |||
| while (!formatter.TryAppend(value, format)) { | |||
| formatter.Enlarge(); | |||
| } | |||
| } | |||
| public static bool TryAppend<TFormatter>(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<TFormatter>(this TFormatter formatter, DateTime value, ParsedFormat format = default) where TFormatter : ITextOutput | |||
| { | |||
| while (!formatter.TryAppend(value, format)) { | |||
| formatter.Enlarge(); | |||
| } | |||
| } | |||
| public static bool TryAppend<TFormatter>(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<TFormatter>(this TFormatter formatter, DateTimeOffset value, ParsedFormat format = default) where TFormatter : ITextOutput | |||
| { | |||
| while (!formatter.TryAppend(value, format)) { | |||
| formatter.Enlarge(); | |||
| } | |||
| } | |||
| public static bool TryAppend<TFormatter>(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<TFormatter>(this TFormatter formatter, TimeSpan value, ParsedFormat format = default) where TFormatter : ITextOutput | |||
| { | |||
| while (!formatter.TryAppend(value, format)) { | |||
| formatter.Enlarge(); | |||
| } | |||
| } | |||
| public static bool TryAppend<TFormatter>(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<TFormatter>(this TFormatter formatter, float value, ParsedFormat format = default) where TFormatter : ITextOutput | |||
| { | |||
| while (!formatter.TryAppend(value, format)) { | |||
| formatter.Enlarge(); | |||
| } | |||
| } | |||
| public static bool TryAppend<TFormatter>(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<TFormatter>(this TFormatter formatter, double value, ParsedFormat format = default) where TFormatter : ITextOutput | |||
| { | |||
| while (!formatter.TryAppend(value, format)) { | |||
| formatter.Enlarge(); | |||
| } | |||
| } | |||
| public static bool TryAppend<TFormatter>(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; | |||
| } | |||
| } | |||
| } | |||
| @@ -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<T>(this T bufferSequence, out ulong value, out int consumed) where T : ISequence<ReadOnlyBuffer<byte>> | |||
| { | |||
| value = default; | |||
| consumed = default; | |||
| Position position = Position.First; | |||
| // Fetch the first segment | |||
| ReadOnlyBuffer<byte> 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<byte> 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<byte> combinedSpan; | |||
| unsafe | |||
| { | |||
| if (first.Length < StackBufferSize) { | |||
| var data = stackalloc byte[StackBufferSize]; | |||
| var destination = new Span<byte>(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<byte> 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<T>(this T bufferSequence, out uint value, out int consumed) where T : ISequence<ReadOnlyBuffer<byte>> | |||
| { | |||
| value = default; | |||
| consumed = default; | |||
| Position position = Position.First; | |||
| // Fetch the first segment | |||
| ReadOnlyBuffer<byte> 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<byte> 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<byte> combinedSpan; | |||
| unsafe | |||
| { | |||
| if (first.Length < StackBufferSize) { | |||
| var data = stackalloc byte[StackBufferSize]; | |||
| var destination = new Span<byte>(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<byte> 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; | |||
| } | |||
| } | |||
| } | |||
| @@ -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) { } | |||
| } | |||
| } | |||
| } | |||
| @@ -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<byte> 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<byte> input, Span<byte> 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<byte> 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<byte> input, Span<byte> 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; | |||
| } | |||
| } | |||
| } | |||
| @@ -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<byte> 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<byte> 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; | |||
| } | |||
| } | |||
| } | |||
| @@ -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 | |||
| /// <summary> | |||
| /// 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. | |||
| /// </summary> | |||
| /// <param name="source">A span containing a sequence of UTF-8 bytes.</param> | |||
| /// <param name="bytesNeeded">On exit, contains the number of bytes required for encoding from the <paramref name="source"/>.</param> | |||
| /// <returns>A <see cref="TransformationStatus"/> value representing the expected state of the conversion.</returns> | |||
| public static TransformationStatus FromUtf8Length(ReadOnlySpan<byte> source, out int bytesNeeded) | |||
| => Utf8.ToUtf16Length(source, out bytesNeeded); | |||
| /// <summary> | |||
| /// 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, <paramref name="bytesConsumed"/> will be | |||
| /// equal to the length of the <paramref name="source"/> and <paramref name="bytesWritten"/> will equal the total number of bytes written to | |||
| /// the <paramref name="destination"/>. | |||
| /// </summary> | |||
| /// <param name="source">A span containing a sequence of UTF-8 bytes.</param> | |||
| /// <param name="destination">A span to write the UTF-16 bytes into.</param> | |||
| /// <param name="bytesConsumed">On exit, contains the number of bytes that were consumed from the <paramref name="source"/>.</param> | |||
| /// <param name="bytesWritten">On exit, contains the number of bytes written to <paramref name="destination"/></param> | |||
| /// <returns>A <see cref="TransformationStatus"/> value representing the state of the conversion.</returns> | |||
| public static TransformationStatus FromUtf8(ReadOnlySpan<byte> source, Span<byte> destination, out int bytesConsumed, out int bytesWritten) | |||
| => Utf8.ToUtf16(source, destination, out bytesConsumed, out bytesWritten); | |||
| /// <summary> | |||
| /// 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. | |||
| /// </summary> | |||
| /// <param name="source">A span containing a sequence of UTF-16 bytes.</param> | |||
| /// <param name="bytesNeeded">On exit, contains the number of bytes required for encoding from the <paramref name="source"/>.</param> | |||
| /// <returns>A <see cref="TransformationStatus"/> value representing the expected state of the conversion.</returns> | |||
| public static TransformationStatus ToUtf8Length(ReadOnlySpan<byte> 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<byte, char>(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; | |||
| } | |||
| } | |||
| /// <summary> | |||
| /// 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, <paramref name="bytesConsumed"/> will be | |||
| /// equal to the length of the <paramref name="source"/> and <paramref name="bytesWritten"/> will equal the total number of bytes written to | |||
| /// the <paramref name="destination"/>. | |||
| /// </summary> | |||
| /// <param name="source">A span containing a sequence of UTF-16 bytes.</param> | |||
| /// <param name="destination">A span to write the UTF-8 bytes into.</param> | |||
| /// <param name="bytesConsumed">On exit, contains the number of bytes that were consumed from the <paramref name="source"/>.</param> | |||
| /// <param name="bytesWritten">On exit, contains the number of bytes written to <paramref name="destination"/></param> | |||
| /// <returns>A <see cref="TransformationStatus"/> value representing the state of the conversion.</returns> | |||
| public unsafe static TransformationStatus ToUtf8(ReadOnlySpan<byte> source, Span<byte> 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 | |||
| /// <summary> | |||
| /// 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. | |||
| /// </summary> | |||
| /// <param name="source">A span containing a sequence of UTF-32 bytes.</param> | |||
| /// <param name="bytesNeeded">On exit, contains the number of bytes required for encoding from the <paramref name="source"/>.</param> | |||
| /// <returns>A <see cref="TransformationStatus"/> value representing the expected state of the conversion.</returns> | |||
| public static TransformationStatus FromUtf32Length(ReadOnlySpan<byte> source, out int bytesNeeded) | |||
| => Utf32.ToUtf16Length(source, out bytesNeeded); | |||
| /// <summary> | |||
| /// 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, <paramref name="bytesConsumed"/> will be | |||
| /// equal to the length of the <paramref name="source"/> and <paramref name="bytesWritten"/> will equal the total number of bytes written to | |||
| /// the <paramref name="destination"/>. | |||
| /// </summary> | |||
| /// <param name="source">A span containing a sequence of UTF-32 bytes.</param> | |||
| /// <param name="destination">A span to write the UTF-16 bytes into.</param> | |||
| /// <param name="bytesConsumed">On exit, contains the number of bytes that were consumed from the <paramref name="source"/>.</param> | |||
| /// <param name="bytesWritten">On exit, contains the number of bytes written to <paramref name="destination"/></param> | |||
| /// <returns>A <see cref="TransformationStatus"/> value representing the state of the conversion.</returns> | |||
| public static TransformationStatus FromUtf32(ReadOnlySpan<byte> source, Span<byte> destination, out int bytesConsumed, out int bytesWritten) | |||
| => Utf32.ToUtf16(source, destination, out bytesConsumed, out bytesWritten); | |||
| /// <summary> | |||
| /// 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. | |||
| /// </summary> | |||
| /// <param name="source">A span containing a sequence of UTF-16 bytes.</param> | |||
| /// <param name="bytesNeeded">On exit, contains the number of bytes required for encoding from the <paramref name="source"/>.</param> | |||
| /// <returns>A <see cref="TransformationStatus"/> value representing the expected state of the conversion.</returns> | |||
| public static TransformationStatus ToUtf32Length(ReadOnlySpan<byte> 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<byte, char>(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<byte, char>(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; | |||
| } | |||
| /// <summary> | |||
| /// 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, <paramref name="bytesConsumed"/> will be | |||
| /// equal to the length of the <paramref name="source"/> and <paramref name="bytesWritten"/> will equal the total number of bytes written to | |||
| /// the <paramref name="destination"/>. | |||
| /// </summary> | |||
| /// <param name="source">A span containing a sequence of UTF-16 bytes.</param> | |||
| /// <param name="destination">A span to write the UTF-32 bytes into.</param> | |||
| /// <param name="bytesConsumed">On exit, contains the number of bytes that were consumed from the <paramref name="source"/>.</param> | |||
| /// <param name="bytesWritten">On exit, contains the number of bytes written to <paramref name="destination"/></param> | |||
| /// <returns>A <see cref="TransformationStatus"/> value representing the state of the conversion.</returns> | |||
| public static TransformationStatus ToUtf32(ReadOnlySpan<byte> source, Span<byte> 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<byte, char>(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<byte, char>(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<byte, uint>(ref Unsafe.Add(ref dst, bytesWritten)) = codePoint; | |||
| bytesConsumed += 2; | |||
| bytesWritten += 4; | |||
| } | |||
| return bytesConsumed < srcLength ? TransformationStatus.NeedMoreSourceData : TransformationStatus.Done; | |||
| } | |||
| #endregion UTF-32 Conversions | |||
| } | |||
| } | |||
| @@ -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 | |||
| /// <summary> | |||
| /// 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. | |||
| /// </summary> | |||
| /// <param name="source">A span containing a sequence of UTF-8 bytes.</param> | |||
| /// <param name="bytesNeeded">On exit, contains the number of bytes required for encoding from the <paramref name="source"/>.</param> | |||
| /// <returns>A <see cref="TransformationStatus"/> value representing the expected state of the conversion.</returns> | |||
| public static TransformationStatus FromUtf8Length(ReadOnlySpan<byte> source, out int bytesNeeded) | |||
| => Utf8.ToUtf32Length(source, out bytesNeeded); | |||
| /// <summary> | |||
| /// 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, <paramref name="bytesConsumed"/> will be | |||
| /// equal to the length of the <paramref name="source"/> and <paramref name="bytesWritten"/> will equal the total number of bytes written to | |||
| /// the <paramref name="destination"/>. | |||
| /// </summary> | |||
| /// <param name="source">A span containing a sequence of UTF-8 bytes.</param> | |||
| /// <param name="destination">A span to write the UTF-32 bytes into.</param> | |||
| /// <param name="bytesConsumed">On exit, contains the number of bytes that were consumed from the <paramref name="source"/>.</param> | |||
| /// <param name="bytesWritten">On exit, contains the number of bytes written to <paramref name="destination"/></param> | |||
| /// <returns>A <see cref="TransformationStatus"/> value representing the state of the conversion.</returns> | |||
| public static TransformationStatus FromUtf8(ReadOnlySpan<byte> source, Span<byte> destination, out int bytesConsumed, out int bytesWritten) | |||
| => Utf8.ToUtf32(source, destination, out bytesConsumed, out bytesWritten); | |||
| /// <summary> | |||
| /// 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. | |||
| /// </summary> | |||
| /// <param name="source">A span containing a sequence of UTF-32 bytes.</param> | |||
| /// <param name="bytesNeeded">On exit, contains the number of bytes required for encoding from the <paramref name="source"/>.</param> | |||
| /// <returns>A <see cref="TransformationStatus"/> value representing the expected state of the conversion.</returns> | |||
| public static TransformationStatus ToUtf8Length(ReadOnlySpan<byte> source, out int bytesNeeded) | |||
| { | |||
| bytesNeeded = 0; | |||
| ref uint utf32 = ref Unsafe.As<byte, uint>(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; | |||
| } | |||
| /// <summary> | |||
| /// 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, <paramref name="bytesConsumed"/> will be | |||
| /// equal to the length of the <paramref name="source"/> and <paramref name="bytesWritten"/> will equal the total number of bytes written to | |||
| /// the <paramref name="destination"/>. | |||
| /// </summary> | |||
| /// <param name="source">A span containing a sequence of UTF-32 bytes.</param> | |||
| /// <param name="destination">A span to write the UTF-8 bytes into.</param> | |||
| /// <param name="bytesConsumed">On exit, contains the number of bytes that were consumed from the <paramref name="source"/>.</param> | |||
| /// <param name="bytesWritten">On exit, contains the number of bytes written to <paramref name="destination"/></param> | |||
| /// <returns>A <see cref="TransformationStatus"/> value representing the state of the conversion.</returns> | |||
| public static TransformationStatus ToUtf8(ReadOnlySpan<byte> source, Span<byte> 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<byte, uint>(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 | |||
| /// <summary> | |||
| /// 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. | |||
| /// </summary> | |||
| /// <param name="source">A span containing a sequence of UTF-16 bytes.</param> | |||
| /// <param name="bytesNeeded">On exit, contains the number of bytes required for encoding from the <paramref name="source"/>.</param> | |||
| /// <returns>A <see cref="TransformationStatus"/> value representing the expected state of the conversion.</returns> | |||
| public static TransformationStatus FromUtf16Length(ReadOnlySpan<byte> source, out int bytesNeeded) | |||
| => Utf16.ToUtf32Length(source, out bytesNeeded); | |||
| /// <summary> | |||
| /// 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, <paramref name="bytesConsumed"/> will be | |||
| /// equal to the length of the <paramref name="source"/> and <paramref name="bytesWritten"/> will equal the total number of bytes written to | |||
| /// the <paramref name="destination"/>. | |||
| /// </summary> | |||
| /// <param name="source">A span containing a sequence of UTF-16 bytes.</param> | |||
| /// <param name="destination">A span to write the UTF-32 bytes into.</param> | |||
| /// <param name="bytesConsumed">On exit, contains the number of bytes that were consumed from the <paramref name="source"/>.</param> | |||
| /// <param name="bytesWritten">On exit, contains the number of bytes written to <paramref name="destination"/></param> | |||
| /// <returns>A <see cref="TransformationStatus"/> value representing the state of the conversion.</returns> | |||
| public static TransformationStatus FromUtf16(ReadOnlySpan<byte> source, Span<byte> destination, out int bytesConsumed, out int bytesWritten) | |||
| => Utf16.ToUtf32(source, destination, out bytesConsumed, out bytesWritten); | |||
| /// <summary> | |||
| /// 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. | |||
| /// </summary> | |||
| /// <param name="source">A span containing a sequence of UTF-32 bytes.</param> | |||
| /// <param name="bytesNeeded">On exit, contains the number of bytes required for encoding from the <paramref name="source"/>.</param> | |||
| /// <returns>A <see cref="TransformationStatus"/> value representing the expected state of the conversion.</returns> | |||
| public static TransformationStatus ToUtf16Length(ReadOnlySpan<byte> 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<byte, uint>(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; | |||
| } | |||
| /// <summary> | |||
| /// 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, <paramref name="bytesConsumed"/> will be | |||
| /// equal to the length of the <paramref name="source"/> and <paramref name="bytesWritten"/> will equal the total number of bytes written to | |||
| /// the <paramref name="destination"/>. | |||
| /// </summary> | |||
| /// <param name="source">A span containing a sequence of UTF-32 bytes.</param> | |||
| /// <param name="destination">A span to write the UTF-16 bytes into.</param> | |||
| /// <param name="bytesConsumed">On exit, contains the number of bytes that were consumed from the <paramref name="source"/>.</param> | |||
| /// <param name="bytesWritten">On exit, contains the number of bytes written to <paramref name="destination"/></param> | |||
| /// <returns>A <see cref="TransformationStatus"/> value representing the state of the conversion.</returns> | |||
| public static TransformationStatus ToUtf16(ReadOnlySpan<byte> source, Span<byte> 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<byte, uint>(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<byte, char>(ref Unsafe.Add(ref dst, bytesWritten)) = (char)codePoint; | |||
| else | |||
| { | |||
| Unsafe.As<byte, char>(ref Unsafe.Add(ref dst, bytesWritten)) = (char)(((codePoint - 0x010000u) >> 10) + EncodingHelper.HighSurrogateStart); | |||
| Unsafe.As<byte, char>(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 | |||
| } | |||
| } | |||
| @@ -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 | |||
| /// <summary> | |||
| /// 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. | |||
| /// </summary> | |||
| /// <param name="source">A span containing a sequence of UTF-16 bytes.</param> | |||
| /// <param name="bytesNeeded">On exit, contains the number of bytes required for encoding from the <paramref name="source"/>.</param> | |||
| /// <returns>A <see cref="TransformationStatus"/> value representing the expected state of the conversion.</returns> | |||
| public static TransformationStatus FromUtf16Length(ReadOnlySpan<byte> source, out int bytesNeeded) | |||
| => Utf16.ToUtf8Length(source, out bytesNeeded); | |||
| /// <summary> | |||
| /// 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, <paramref name="bytesConsumed"/> will be | |||
| /// equal to the length of the <paramref name="source"/> and <paramref name="bytesWritten"/> will equal the total number of bytes written to | |||
| /// the <paramref name="destination"/>. | |||
| /// </summary> | |||
| /// <param name="source">A span containing a sequence of UTF-16 bytes.</param> | |||
| /// <param name="destination">A span to write the UTF-8 bytes into.</param> | |||
| /// <param name="bytesConsumed">On exit, contains the number of bytes that were consumed from the <paramref name="source"/>.</param> | |||
| /// <param name="bytesWritten">On exit, contains the number of bytes written to <paramref name="destination"/></param> | |||
| /// <returns>A <see cref="TransformationStatus"/> value representing the state of the conversion.</returns> | |||
| public static TransformationStatus FromUtf16(ReadOnlySpan<byte> source, Span<byte> destination, out int bytesConsumed, out int bytesWritten) | |||
| => Utf16.ToUtf8(source, destination, out bytesConsumed, out bytesWritten); | |||
| /// <summary> | |||
| /// 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. | |||
| /// </summary> | |||
| /// <param name="source">A span containing a sequence of UTF-8 bytes.</param> | |||
| /// <param name="bytesNeeded">On exit, contains the number of bytes required for encoding from the <paramref name="source"/>.</param> | |||
| /// <returns>A <see cref="TransformationStatus"/> value representing the expected state of the conversion.</returns> | |||
| public unsafe static TransformationStatus ToUtf16Length(ReadOnlySpan<byte> 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; | |||
| } | |||
| } | |||
| /// <summary> | |||
| /// 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, <paramref name="bytesConsumed"/> will be | |||
| /// equal to the length of the <paramref name="source"/> and <paramref name="bytesWritten"/> will equal the total number of bytes written to | |||
| /// the <paramref name="destination"/>. | |||
| /// </summary> | |||
| /// <param name="source">A span containing a sequence of UTF-8 bytes.</param> | |||
| /// <param name="destination">A span to write the UTF-16 bytes into.</param> | |||
| /// <param name="bytesConsumed">On exit, contains the number of bytes that were consumed from the <paramref name="source"/>.</param> | |||
| /// <param name="bytesWritten">On exit, contains the number of bytes written to <paramref name="destination"/></param> | |||
| /// <returns>A <see cref="TransformationStatus"/> value representing the state of the conversion.</returns> | |||
| public unsafe static TransformationStatus ToUtf16(ReadOnlySpan<byte> source, Span<byte> 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 | |||
| /// <summary> | |||
| /// 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. | |||
| /// </summary> | |||
| /// <param name="source">A span containing a sequence of UTF-32 bytes.</param> | |||
| /// <param name="bytesNeeded">On exit, contains the number of bytes required for encoding from the <paramref name="source"/>.</param> | |||
| /// <returns>A <see cref="TransformationStatus"/> value representing the expected state of the conversion.</returns> | |||
| public static TransformationStatus FromUtf32Length(ReadOnlySpan<byte> source, out int bytesNeeded) | |||
| => Utf32.ToUtf8Length(source, out bytesNeeded); | |||
| /// <summary> | |||
| /// 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, <paramref name="bytesConsumed"/> will be | |||
| /// equal to the length of the <paramref name="source"/> and <paramref name="bytesWritten"/> will equal the total number of bytes written to | |||
| /// the <paramref name="destination"/>. | |||
| /// </summary> | |||
| /// <param name="source">A span containing a sequence of UTF-32 bytes.</param> | |||
| /// <param name="destination">A span to write the UTF-8 bytes into.</param> | |||
| /// <param name="bytesConsumed">On exit, contains the number of bytes that were consumed from the <paramref name="source"/>.</param> | |||
| /// <param name="bytesWritten">On exit, contains the number of bytes written to <paramref name="destination"/></param> | |||
| /// <returns>A <see cref="TransformationStatus"/> value representing the state of the conversion.</returns> | |||
| public static TransformationStatus FromUtf32(ReadOnlySpan<byte> source, Span<byte> destination, out int bytesConsumed, out int bytesWritten) | |||
| => Utf32.ToUtf8(source, destination, out bytesConsumed, out bytesWritten); | |||
| /// <summary> | |||
| /// 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. | |||
| /// </summary> | |||
| /// <param name="source">A span containing a sequence of UTF-8 bytes.</param> | |||
| /// <param name="bytesNeeded">On exit, contains the number of bytes required for encoding from the <paramref name="source"/>.</param> | |||
| /// <returns>A <see cref="TransformationStatus"/> value representing the expected state of the conversion.</returns> | |||
| public static TransformationStatus ToUtf32Length(ReadOnlySpan<byte> 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; | |||
| } | |||
| /// <summary> | |||
| /// 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, <paramref name="bytesConsumed"/> will be | |||
| /// equal to the length of the <paramref name="source"/> and <paramref name="bytesWritten"/> will equal the total number of bytes written to | |||
| /// the <paramref name="destination"/>. | |||
| /// </summary> | |||
| /// <param name="source">A span containing a sequence of UTF-8 bytes.</param> | |||
| /// <param name="destination">A span to write the UTF-32 bytes into.</param> | |||
| /// <param name="bytesConsumed">On exit, contains the number of bytes that were consumed from the <paramref name="source"/>.</param> | |||
| /// <param name="bytesWritten">On exit, contains the number of bytes written to <paramref name="destination"/></param> | |||
| /// <returns>A <see cref="TransformationStatus"/> value representing the state of the conversion.</returns> | |||
| public static TransformationStatus ToUtf32(ReadOnlySpan<byte> source, Span<byte> 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<byte, uint>(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 | |||
| } | |||
| } | |||
| @@ -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<uint>(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; | |||
| } | |||
| } | |||
| } | |||
| @@ -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 | |||
| /// <summary> | |||
| /// 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. | |||
| /// </summary> | |||
| private struct Suffix : IComparable<Suffix> | |||
| { | |||
| public int SymbolIndex; | |||
| public byte[] Bytes; | |||
| public Suffix(int symbolIndex, byte[] bytes) | |||
| { | |||
| SymbolIndex = symbolIndex; | |||
| Bytes = bytes; | |||
| } | |||
| public Suffix(int symbolIndex, ReadOnlySpan<byte> 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<Suffix> 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<Suffix>(20); | |||
| } | |||
| } | |||
| private struct Sequence : IComparable<Sequence> | |||
| { | |||
| 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<Node> parsingTrieList, List<Suffix> 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<SuffixClump> clumps = new List<SuffixClump>(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<byte>(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<Node> childNodes = new List<Node>(); | |||
| 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<Suffix> symbolList = new List<Suffix>(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<Node> parsingTrieList = new List<Node>(100); | |||
| CreateParsingTrieNodeAndChildren(ref parsingTrieList, symbolList); | |||
| return parsingTrieList.ToArray(); | |||
| } | |||
| } | |||
| } | |||
| @@ -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, | |||
| } | |||
| } | |||
| } | |||
| @@ -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<byte> destination, out int bytesWritten) | |||
| { | |||
| if (destination.Length < 2) | |||
| goto ExitFailed; | |||
| if (utf8 > 0x7F) | |||
| goto ExitFailed; | |||
| Unsafe.As<byte, char>(ref destination.DangerousGetPinnableReference()) = (char)utf8; | |||
| bytesWritten = 2; | |||
| return true; | |||
| ExitFailed: | |||
| bytesWritten = 0; | |||
| return false; | |||
| } | |||
| public override bool TryEncode(ReadOnlySpan<byte> utf8, Span<byte> 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<byte> source, out byte utf8, out int bytesConsumed) | |||
| { | |||
| if (source.Length < 2) | |||
| goto ExitFailed; | |||
| ref char value = ref Unsafe.As<byte, char>(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<byte> source, Span<byte> 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; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -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<byte> 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<byte> utf8, Span<byte> 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<byte> 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<byte> source, Span<byte> 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; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -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<byte> 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<byte>(bytes).CopyTo(destination); | |||
| return true; | |||
| } | |||
| public abstract bool TryEncode(byte utf8, Span<byte> destination, out int bytesWritten); | |||
| public abstract bool TryEncode(ReadOnlySpan<byte> utf8, Span<byte> destination, out int bytesConsumed, out int bytesWritten); | |||
| public bool TryParse(ReadOnlySpan<byte> 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<byte> source, out byte utf8, out int bytesConsumed); | |||
| public abstract bool TryParse(ReadOnlySpan<byte> source, Span<byte> utf8, out int bytesConsumed, out int bytesWritten); | |||
| #region Public UTF-16 to UTF-8 helpers | |||
| public bool TryEncode(ReadOnlySpan<char> source, Span<byte> destination, out int bytesConsumed, out int bytesWritten) | |||
| { | |||
| ReadOnlySpan<byte> 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<byte> temp; | |||
| unsafe | |||
| { | |||
| byte* pTemp = stackalloc byte[BufferSize]; | |||
| temp = new Span<byte>(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<byte> source, Span<byte> 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<byte> 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 | |||
| } | |||
| } | |||
| @@ -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<byte> 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<byte> temp; | |||
| unsafe | |||
| { | |||
| var buf = stackalloc byte[needed]; | |||
| temp = new Span<byte>(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); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -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. | |||
| /// <summary> | |||
| /// This interface should be implemented by types that want to support allocation-free formatting. | |||
| /// </summary> | |||
| /// <param name="buffer">The buffer to format the value into</param> | |||
| /// <param name="written">This parameter is used to return the number of bytes that were written to the buffer</param> | |||
| /// <param name="format">This is a pre-parsed representation of the formatting string. It's preparsed for efficiency.</param> | |||
| /// <param name="symbolTable">This object implements the character and symbol encoder.</param> | |||
| /// <returns>False if the buffer was to small, otherwise true.</returns> | |||
| bool TryFormat(Span<byte> buffer, out int written, ParsedFormat format = default, SymbolTable symbolTable = null); | |||
| } | |||
| } | |||
| @@ -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; | |||
| } | |||
| /// <summary> | |||
| /// 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. | |||
| /// </summary> | |||
| [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; | |||
| } | |||
| /// <summary> | |||
| /// 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. | |||
| /// </summary> | |||
| [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 | |||
| /// <summary> | |||
| /// We don't have access to Math.DivRem, so this is a copy of the implementation. | |||
| /// </summary> | |||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
| public static long DivMod(long numerator, long denominator, out long modulo) | |||
| { | |||
| long div = numerator / denominator; | |||
| modulo = numerator - (div * denominator); | |||
| return div; | |||
| } | |||
| /// <summary> | |||
| /// We don't have access to Math.DivRem, so this is a copy of the implementation. | |||
| /// </summary> | |||
| [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 | |||
| } | |||
| } | |||
| @@ -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<byte> 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<char> dst = buffer.NonPortableCast<byte, char>(); | |||
| 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; | |||
| } | |||
| } | |||
| } | |||
| @@ -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<byte> 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; | |||
| } | |||
| } | |||
| } | |||
| @@ -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<byte> buffer, out int bytesWritten) | |||
| { | |||
| int digitCount = FormattingHelpers.CountDigits(value); | |||
| int charsNeeded = digitCount + (int)((value >> 63) & 1); | |||
| Span<char> span = buffer.NonPortableCast<byte, char>(); | |||
| 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<byte> 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<char> span = buffer.Slice(bytesWritten).NonPortableCast<byte, char>(); | |||
| 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<byte> 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<char> span = buffer.NonPortableCast<byte, char>(); | |||
| 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<byte> 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<char> span = buffer.Slice(bytesWritten).NonPortableCast<byte, char>(); | |||
| 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<byte> 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<char> span = buffer.NonPortableCast<byte, char>(); | |||
| 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; | |||
| } | |||
| } | |||
| } | |||
| @@ -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<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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; | |||
| } | |||
| } | |||
| } | |||
| @@ -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<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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; | |||
| } | |||
| } | |||
| } | |||
| @@ -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<byte> 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<char> dst = buffer.NonPortableCast<byte, char>(); | |||
| 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<byte> 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<char> dst = buffer.NonPortableCast<byte, char>(); | |||
| 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<byte> buffer, out int bytesWritten) | |||
| { | |||
| const int CharsNeeded = 29; | |||
| bytesWritten = CharsNeeded * sizeof(char); | |||
| if (buffer.Length < bytesWritten) | |||
| { | |||
| bytesWritten = 0; | |||
| return false; | |||
| } | |||
| Span<char> dst = buffer.NonPortableCast<byte, char>(); | |||
| 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<byte> buffer, out int bytesWritten) | |||
| { | |||
| const int CharsNeeded = 29; | |||
| bytesWritten = CharsNeeded * sizeof(char); | |||
| if (buffer.Length < bytesWritten) | |||
| { | |||
| bytesWritten = 0; | |||
| return false; | |||
| } | |||
| Span<char> dst = buffer.NonPortableCast<byte, char>(); | |||
| 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<byte> 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<char> dst = buffer.NonPortableCast<byte, char>(); | |||
| 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; | |||
| } | |||
| } | |||
| } | |||
| @@ -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<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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; | |||
| } | |||
| } | |||
| } | |||
| @@ -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<byte> 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(); | |||
| } | |||
| } | |||
| } | |||
| @@ -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 | |||
| { | |||
| /// <summary> | |||
| /// Pseudo-implementations of IBufferFormattable interface for primitive types | |||
| /// </summary> | |||
| /// <remarks> | |||
| /// Holds extension methods for formatting types that cannot implement IBufferFormattable for layering reasons. | |||
| /// </remarks> | |||
| public static partial class PrimitiveFormatter | |||
| { | |||
| public static bool TryFormat(this byte value, Span<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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(); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -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<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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(); | |||
| } | |||
| } | |||
| } | |||
| @@ -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<byte> 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<byte> 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); | |||
| } | |||
| } | |||
| } | |||
| @@ -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<char> 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()); | |||
| } | |||
| } | |||
| } | |||
| @@ -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<byte> 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<byte> 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<char> 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<char> 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; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -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<char>(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<char>(text, length); | |||
| return TryParseDecimal(span, out value, out charactersConsumed); | |||
| } | |||
| public static bool TryParseDecimal(ReadOnlySpan<char> text, out decimal value) | |||
| { | |||
| int consumed; | |||
| return TryParseDecimal(text, out value, out consumed); | |||
| } | |||
| public static bool TryParseDecimal(ReadOnlySpan<char> 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; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -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<byte>(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<byte>(text, length); | |||
| return TryParseDecimal(span, out value, out bytesConsumed); | |||
| } | |||
| public static bool TryParseDecimal(ReadOnlySpan<byte> text, out decimal value) | |||
| { | |||
| int consumed; | |||
| return TryParseDecimal(text, out value, out consumed); | |||
| } | |||
| public static bool TryParseDecimal(ReadOnlySpan<byte> 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; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -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'; | |||
| } | |||
| } | |||
| } | |||
| @@ -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<byte> 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<char> textChars = text.NonPortableCast<byte, char>(); | |||
| int charactersConsumed; | |||
| bool result = InvariantUtf16.TryParseBoolean(textChars, out value, out charactersConsumed); | |||
| bytesConsumed = charactersConsumed * sizeof(char); | |||
| return result; | |||
| } | |||
| return false; | |||
| } | |||
| } | |||
| } | |||
| @@ -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<byte> 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<char> textChars = text.NonPortableCast<byte, char>(); | |||
| int charactersConsumed; | |||
| bool result = InvariantUtf16.TryParseDecimal(textChars, out value, out charactersConsumed); | |||
| bytesConsumed = charactersConsumed * sizeof(char); | |||
| return result; | |||
| } | |||
| return false; | |||
| } | |||
| } | |||
| } | |||
| @@ -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<byte> 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<char> utf16Text = text.NonPortableCast<byte, char>(); | |||
| 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<byte> 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<char> utf16Text = text.NonPortableCast<byte, char>(); | |||
| 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<byte> 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<char> utf16Text = text.NonPortableCast<byte, char>(); | |||
| 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<byte> 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<char> utf16Text = text.NonPortableCast<byte, char>(); | |||
| 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; | |||
| } | |||
| } | |||
| } | |||
| @@ -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<byte> 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<char> utf16Text = text.NonPortableCast<byte, char>(); | |||
| 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<byte> 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<char> utf16Text = text.NonPortableCast<byte, char>(); | |||
| 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<byte> 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<char> utf16Text = text.NonPortableCast<byte, char>(); | |||
| 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<byte> 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<char> utf16Text = text.NonPortableCast<byte, char>(); | |||
| 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; | |||
| } | |||
| } | |||
| } | |||
| @@ -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<uint>, IEnumerable | |||
| { | |||
| private string _s; | |||
| public Utf16LittleEndianCodePointEnumerable(string s) | |||
| { | |||
| _s = s; | |||
| } | |||
| public Utf16LittleEndianCodePointEnumerator GetEnumerator() | |||
| { | |||
| return new Utf16LittleEndianCodePointEnumerator(_s); | |||
| } | |||
| IEnumerator<uint> IEnumerable<uint>.GetEnumerator() | |||
| { | |||
| return GetEnumerator(); | |||
| } | |||
| IEnumerator IEnumerable.GetEnumerator() | |||
| { | |||
| return GetEnumerator(); | |||
| } | |||
| } | |||
| } | |||
| @@ -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<uint>, 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() { } | |||
| } | |||
| } | |||
| @@ -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<byte> 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<byte> buffer, out int encodedBytes) | |||
| { | |||
| encodedBytes = 1; | |||
| ReadOnlySpan<byte> it = buffer; | |||
| // TODO: Should we have something like: Span<byte>.(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<byte>.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<byte> 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<uint>(SortedWhitespaceCodePoints, codePoint) >= 0; | |||
| } | |||
| } | |||
| } | |||
| @@ -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<byte> _buffer; | |||
| public CodePointEnumerable(byte[] bytes, int index, int length) | |||
| { | |||
| _buffer = new ReadOnlySpan<byte>(bytes, index, length); | |||
| } | |||
| public unsafe CodePointEnumerable(ReadOnlySpan<byte> 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; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -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<byte> _buffer; | |||
| private int _index; | |||
| private int _currentLenCache; | |||
| private const int ResetIndex = -Utf8Helper.MaxCodeUnitsPerCodePoint - 1; | |||
| public unsafe CodePointEnumerator(ReadOnlySpan<byte> 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; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -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<byte> _buffer; | |||
| private int _index; | |||
| private int _currentLenCache; | |||
| private const int ResetIndex = -Utf8Helper.MaxCodeUnitsPerCodePoint - 1; | |||
| public unsafe CodePointReverseEnumerator(ReadOnlySpan<byte> 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<byte> 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; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -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<byte> _buffer; | |||
| private readonly int _length; | |||
| private int _index; | |||
| internal Enumerator(ReadOnlySpan<byte> 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; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -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<byte> _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<byte> buffer) | |||
| { | |||
| _buffer = buffer; | |||
| } | |||
| public Utf8String(byte[] utf8bytes) | |||
| { | |||
| _buffer = new ReadOnlySpan<byte>(utf8bytes); | |||
| } | |||
| public Utf8String(byte[] utf8bytes, int index, int length) | |||
| { | |||
| _buffer = new ReadOnlySpan<byte>(utf8bytes, index, length); | |||
| } | |||
| public Utf8String(string s) | |||
| { | |||
| if (s == null) | |||
| { | |||
| throw new ArgumentNullException("s", "String cannot be null"); | |||
| } | |||
| if (s == string.Empty) | |||
| { | |||
| _buffer = ReadOnlySpan<byte>.Empty; | |||
| } | |||
| else | |||
| { | |||
| _buffer = new ReadOnlySpan<byte>(GetUtf8BytesFromString(s)); | |||
| } | |||
| } | |||
| /// <summary> | |||
| /// This constructor is for use by the compiler. | |||
| /// </summary> | |||
| [EditorBrowsable(EditorBrowsableState.Never)] | |||
| public Utf8String(RuntimeFieldHandle utf8Data, int length) : this(CreateArrayFromFieldHandle(utf8Data, length)) | |||
| { | |||
| } | |||
| public static explicit operator Utf8String(ArraySegment<byte> 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; } } | |||
| /// <summary> | |||
| /// Returns length of the string in UTF-8 code units (bytes) | |||
| /// </summary> | |||
| 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<byte>(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<byte> 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<char>(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(); | |||
| } | |||
| /// <summary> | |||
| /// | |||
| /// </summary> | |||
| /// <param name="index">Index in UTF-8 code units (bytes)</param> | |||
| /// <returns>Length in UTF-8 code units (bytes)</returns> | |||
| public Utf8String Substring(int index) | |||
| { | |||
| return Substring(index, Length - index); | |||
| } | |||
| /// <summary> | |||
| /// | |||
| /// </summary> | |||
| /// <param name="index">Index in UTF-8 code units (bytes)</param> | |||
| /// <returns>Length in UTF-8 code units (bytes)</returns> | |||
| 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<byte> 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<uint> 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<byte>, 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'; | |||
| } | |||
| } | |||
| } | |||
| @@ -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<byte[], int, int, Task> BinaryMessage; | |||
| public event Func<string, Task> TextMessage; | |||
| public event Func<ReadOnlyBuffer<byte>, bool, Task> Message; | |||
| public event Func<Exception, Task> 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<byte> 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<byte>(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<byte>(e.Data, 0, e.Data.Count()), false).GetAwaiter().GetResult(); | |||
| } | |||
| private void OnConnected(object sender, object e) | |||
| { | |||
| @@ -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<ulong> Flags { get; set; } | |||
| [JsonProperty("owner")] | |||
| [ModelProperty("owner")] | |||
| public Optional<User> Owner { get; set; } | |||
| } | |||
| } | |||
| @@ -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<int> Height { get; set; } | |||
| [JsonProperty("width")] | |||
| [ModelProperty("width")] | |||
| public Optional<int> Width { get; set; } | |||
| } | |||
| } | |||
| @@ -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; } | |||
| } | |||
| } | |||
| @@ -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<ulong> GuildId { get; set; } | |||
| [JsonProperty("name")] | |||
| [ModelProperty("name")] | |||
| public Optional<string> Name { get; set; } | |||
| [JsonProperty("position")] | |||
| [ModelProperty("position")] | |||
| public Optional<int> Position { get; set; } | |||
| [JsonProperty("permission_overwrites")] | |||
| [ModelProperty("permission_overwrites")] | |||
| public Optional<Overwrite[]> PermissionOverwrites { get; set; } | |||
| //TextChannel | |||
| [JsonProperty("topic")] | |||
| [ModelProperty("topic")] | |||
| public Optional<string> Topic { get; set; } | |||
| [JsonProperty("last_pin_timestamp")] | |||
| [ModelProperty("last_pin_timestamp")] | |||
| public Optional<DateTimeOffset?> LastPinTimestamp { get; set; } | |||
| //VoiceChannel | |||
| [JsonProperty("bitrate")] | |||
| [ModelProperty("bitrate")] | |||
| public Optional<int> Bitrate { get; set; } | |||
| [JsonProperty("user_limit")] | |||
| [ModelProperty("user_limit")] | |||
| public Optional<int> UserLimit { get; set; } | |||
| //PrivateChannel | |||
| [JsonProperty("recipients")] | |||
| [ModelProperty("recipients")] | |||
| public Optional<User[]> Recipients { get; set; } | |||
| //GroupChannel | |||
| [JsonProperty("icon")] | |||
| [ModelProperty("icon")] | |||
| public Optional<string> Icon { get; set; } | |||
| } | |||
| } | |||
| @@ -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<ulong> Integrations { get; set; } | |||
| } | |||
| } | |||
| @@ -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<EmbedAuthor> Author { get; set; } | |||
| [JsonProperty("footer")] | |||
| [ModelProperty("footer")] | |||
| public Optional<EmbedFooter> Footer { get; set; } | |||
| [JsonProperty("video")] | |||
| [ModelProperty("video")] | |||
| public Optional<EmbedVideo> Video { get; set; } | |||
| [JsonProperty("thumbnail")] | |||
| [ModelProperty("thumbnail")] | |||
| public Optional<EmbedThumbnail> Thumbnail { get; set; } | |||
| [JsonProperty("image")] | |||
| [ModelProperty("image")] | |||
| public Optional<EmbedImage> Image { get; set; } | |||
| [JsonProperty("provider")] | |||
| [ModelProperty("provider")] | |||
| public Optional<EmbedProvider> Provider { get; set; } | |||
| [JsonProperty("fields")] | |||
| [ModelProperty("fields")] | |||
| public Optional<EmbedField[]> Fields { get; set; } | |||
| } | |||
| } | |||
| @@ -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; } | |||
| } | |||
| } | |||
| @@ -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; } | |||
| } | |||
| } | |||
| @@ -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; } | |||
| } | |||
| } | |||
| @@ -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<int> Height { get; set; } | |||
| [JsonProperty("width")] | |||
| [ModelProperty("width")] | |||
| public Optional<int> Width { get; set; } | |||
| } | |||
| } | |||
| @@ -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; } | |||
| } | |||
| } | |||