Skip to content

Commit

Permalink
Added Encode extension to BufferWriterSlim type
Browse files Browse the repository at this point in the history
  • Loading branch information
sakno committed Dec 4, 2024
1 parent 0aa4c56 commit b96205d
Show file tree
Hide file tree
Showing 3 changed files with 190 additions and 13 deletions.
91 changes: 79 additions & 12 deletions src/DotNext.IO/Buffers/BufferWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@ static int Write7BitEncodedInteger(ref SpanWriter<byte> writer, int value)

internal static int WriteLength(this IBufferWriter<byte> buffer, int length, LengthFormat lengthFormat)
{
var writer = new SpanWriter<byte>(buffer.GetSpan(SevenBitEncodedInt.MaxSizeInBytes));
buffer.Advance(writer.WriteLength(length, lengthFormat));
return writer.WrittenCount;
var bytesWritten = WriteLength(buffer.GetSpan(SevenBitEncodedInt.MaxSizeInBytes), length, lengthFormat);
buffer.Advance(bytesWritten);
return bytesWritten;
}

internal static int WriteLength(Span<byte> buffer, int length, LengthFormat lengthFormat)
Expand All @@ -71,21 +71,28 @@ internal static int WriteLength(Span<byte> buffer, int length, LengthFormat leng
return writer.WriteLength(length, lengthFormat);
}

private static int WriteLength(this ref BufferWriterSlim<byte> buffer, int length, LengthFormat lengthFormat)
{
var bytesWritten = WriteLength(buffer.GetSpan(SevenBitEncodedInt.MaxSizeInBytes), length, lengthFormat);
buffer.Advance(bytesWritten);
return bytesWritten;
}

/// <summary>
/// Encodes string using the specified encoding.
/// </summary>
/// <param name="writer">The buffer writer.</param>
/// <param name="value">The sequence of characters.</param>
/// <param name="chars">The sequence of characters.</param>
/// <param name="context">The encoding context.</param>
/// <param name="lengthFormat">String length encoding format; or <see langword="null"/> to prevent encoding of string length.</param>
/// <returns>The number of written bytes.</returns>
public static long Encode(this IBufferWriter<byte> writer, ReadOnlySpan<char> value, in EncodingContext context, LengthFormat? lengthFormat = null)
public static long Encode(this IBufferWriter<byte> writer, ReadOnlySpan<char> chars, in EncodingContext context, LengthFormat? lengthFormat = null)
{
var result = lengthFormat.HasValue
? writer.WriteLength(context.Encoding.GetByteCount(value), lengthFormat.GetValueOrDefault())
? writer.WriteLength(context.Encoding.GetByteCount(chars), lengthFormat.GetValueOrDefault())
: 0L;

context.GetEncoder().Convert(value, writer, true, out var bytesWritten, out _);
context.GetEncoder().Convert(chars, writer, true, out var bytesWritten, out _);
result += bytesWritten;

return result;
Expand All @@ -95,19 +102,19 @@ public static long Encode(this IBufferWriter<byte> writer, ReadOnlySpan<char> va
/// Encodes string using the specified encoding.
/// </summary>
/// <param name="writer">The buffer writer.</param>
/// <param name="value">The sequence of characters.</param>
/// <param name="chars">The sequence of characters.</param>
/// <param name="context">The encoding context.</param>
/// <param name="lengthFormat">String length encoding format; or <see langword="null"/> to prevent encoding of string length.</param>
/// <returns>The number of written bytes.</returns>
public static int Encode(this ref SpanWriter<byte> writer, ReadOnlySpan<char> value, in EncodingContext context, LengthFormat? lengthFormat = null)
public static int Encode(this ref SpanWriter<byte> writer, scoped ReadOnlySpan<char> chars, in EncodingContext context, LengthFormat? lengthFormat = null)
{
var result = lengthFormat.HasValue
? writer.WriteLength(context.Encoding.GetByteCount(value), lengthFormat.GetValueOrDefault())
? writer.WriteLength(context.Encoding.GetByteCount(chars), lengthFormat.GetValueOrDefault())
: 0;

var bytesWritten = context.TryGetEncoder() is { } encoder
? encoder.GetBytes(value, writer.RemainingSpan, flush: true)
: context.Encoding.GetBytes(value, writer.RemainingSpan);
? encoder.GetBytes(chars, writer.RemainingSpan, flush: true)
: context.Encoding.GetBytes(chars, writer.RemainingSpan);
result += bytesWritten;
writer.Advance(bytesWritten);

Expand All @@ -128,6 +135,66 @@ public static int Write(this ref SpanWriter<byte> writer, scoped ReadOnlySpan<by

return result;
}

/// <summary>
/// Encodes string using the specified encoding.
/// </summary>
/// <param name="writer">The buffer writer.</param>
/// <param name="chars">The sequence of characters.</param>
/// <param name="context">The encoding context.</param>
/// <param name="lengthFormat">String length encoding format; or <see langword="null"/> to prevent encoding of string length.</param>
/// <returns>The number of written bytes.</returns>
public static int Encode(this ref BufferWriterSlim<byte> writer, scoped ReadOnlySpan<char> chars, in EncodingContext context,
LengthFormat? lengthFormat = null)
{
Span<byte> buffer;
int byteCount, result;
if (lengthFormat.HasValue)
{
byteCount = context.Encoding.GetByteCount(chars);
result = writer.WriteLength(byteCount, lengthFormat.GetValueOrDefault());

buffer = writer.GetSpan(byteCount);
byteCount = context.TryGetEncoder() is { } encoder
? encoder.GetBytes(chars, buffer, flush: true)
: context.Encoding.GetBytes(chars, buffer);

result += byteCount;
writer.Advance(byteCount);
}
else
{
result = 0;
var encoder = context.GetEncoder();
byteCount = context.Encoding.GetMaxByteCount(1);
for (int charsUsed, bytesWritten; !chars.IsEmpty; chars = chars.Slice(charsUsed), result += bytesWritten)
{
buffer = writer.GetSpan(byteCount);
var maxChars = buffer.Length / byteCount;

encoder.Convert(chars, buffer, chars.Length <= maxChars, out charsUsed, out bytesWritten, out _);
writer.Advance(bytesWritten);
}
}

return result;
}

/// <summary>
/// Writes a sequence of bytes prefixed with the length.
/// </summary>
/// <param name="writer">The buffer writer.</param>
/// <param name="value">A sequence of bytes to be written.</param>
/// <param name="lengthFormat">A format of the buffer length to be written.</param>
/// <returns>A number of bytes written.</returns>
public static int Write(this ref BufferWriterSlim<byte> writer, scoped ReadOnlySpan<byte> value, LengthFormat lengthFormat)
{
var result = writer.WriteLength(value.Length, lengthFormat);
writer.Write(value);
result += value.Length;

return result;
}

private static bool TryFormat<T>(IBufferWriter<byte> writer, T value, Span<char> buffer, in EncodingContext context, LengthFormat? lengthFormat, ReadOnlySpan<char> format, IFormatProvider? provider, out long bytesWritten)
where T : notnull, ISpanFormattable
Expand Down
93 changes: 92 additions & 1 deletion src/DotNext.Tests/Buffers/BufferWriterSlimTests.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
using System.Numerics;
using System.Text;
using DotNext.Buffers.Binary;
using static System.Globalization.CultureInfo;

namespace DotNext.Buffers;

using Binary;
using IO;
using static DotNext.Text.EncodingExtensions;

public sealed class BufferWriterSlimTests : Test
{
[Fact]
Expand Down Expand Up @@ -360,4 +363,92 @@ public static void ReadWriteBigInteger()

Equal(expected, new BigInteger(writer.WrittenSpan));
}

private static void EncodeDecodeZeroAndMaxValue<T>()
where T : struct, IBinaryInteger<T>, IUnsignedNumber<T>
{
Span<byte> buffer = stackalloc byte[SevenBitEncodedInteger<T>.MaxSizeInBytes];
var writer = new BufferWriterSlim<byte>(buffer);
var reader = new SpanReader<byte>(buffer);

Equal(1, writer.Write7BitEncodedInteger(T.Zero));
Equal(T.Zero, reader.Read7BitEncodedInteger<T>());

writer.Clear(reuseBuffer: true);
reader.Reset();

Equal(SevenBitEncodedInteger<T>.MaxSizeInBytes, writer.Write7BitEncodedInteger(T.AllBitsSet));
Equal(T.AllBitsSet, reader.Read7BitEncodedInteger<T>());
}

[Fact]
public static void EncodeDecodeUInt32() => EncodeDecodeZeroAndMaxValue<uint>();

[Fact]
public static void EncodeDecodeUInt64() => EncodeDecodeZeroAndMaxValue<ulong>();

[InlineData(LengthFormat.BigEndian)]
[InlineData(LengthFormat.LittleEndian)]
[InlineData(LengthFormat.Compressed)]
[Theory]
public static void WriteLengthPrefixedBytes(LengthFormat format)
{
ReadOnlySpan<byte> expected = [1, 2, 3];

var writer = new BufferWriterSlim<byte>();
True(writer.Write(expected, format) > 0);

using var buffer = writer.DetachOrCopyBuffer();
var reader = IAsyncBinaryReader.Create(buffer.Memory);
using var actual = reader.ReadBlock(format);
Equal(expected, actual.Span);
}

[Theory]
[InlineData("UTF-8", null)]
[InlineData("UTF-8", LengthFormat.LittleEndian)]
[InlineData("UTF-8", LengthFormat.BigEndian)]
[InlineData("UTF-8", LengthFormat.Compressed)]
[InlineData("UTF-16LE", null)]
[InlineData("UTF-16LE", LengthFormat.LittleEndian)]
[InlineData("UTF-16LE", LengthFormat.BigEndian)]
[InlineData("UTF-16LE", LengthFormat.Compressed)]
[InlineData("UTF-16BE", null)]
[InlineData("UTF-16BE", LengthFormat.LittleEndian)]
[InlineData("UTF-16BE", LengthFormat.BigEndian)]
[InlineData("UTF-16BE", LengthFormat.Compressed)]
[InlineData("UTF-32LE", null)]
[InlineData("UTF-32LE", LengthFormat.LittleEndian)]
[InlineData("UTF-32LE", LengthFormat.BigEndian)]
[InlineData("UTF-32LE", LengthFormat.Compressed)]
[InlineData("UTF-32BE", null)]
[InlineData("UTF-32BE", LengthFormat.LittleEndian)]
[InlineData("UTF-32BE", LengthFormat.BigEndian)]
[InlineData("UTF-32BE", LengthFormat.Compressed)]
public static void EncodeDecodeString(string encodingName, LengthFormat? format)
{
var encoding = Encoding.GetEncoding(encodingName);
const string expected = "Hello, world!&*(@&*(fghjwgfwffgw Привет, мир!";
var writer = new BufferWriterSlim<byte>();

True(writer.Encode(expected, encoding, format) > 0);

using var buffer = writer.DetachOrCopyBuffer();
MemoryOwner<char> actual;
if (format.HasValue)
{
var reader = IAsyncBinaryReader.Create(buffer.Memory);
actual = reader.Decode(encoding, format.GetValueOrDefault());
Equal(expected, actual.Span);
}
else
{
actual = encoding.GetChars(buffer.Span);
}

using (actual)
{
Equal(expected, actual.Span);
}
}
}
19 changes: 19 additions & 0 deletions src/DotNext/Buffers/ByteBuffer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,25 @@ public static int Write7BitEncodedInteger<T>(this ref SpanWriter<byte> writer, T

return count;
}

/// <summary>
/// Writes 32-bit integer in a compressed format.
/// </summary>
/// <param name="writer">The buffer writer.</param>
/// <param name="value">The integer to be written.</param>
/// <returns>A number of bytes written to the buffer.</returns>
public static int Write7BitEncodedInteger<T>(this ref BufferWriterSlim<byte> writer, T value)
where T : struct, IBinaryInteger<T>, IUnsignedNumber<T>
{
var count = 0;
foreach (var b in new SevenBitEncodedInteger<T>(value))
{
writer.Add() = b;
count += 1;
}

return count;
}

/// <summary>
/// Decodes an integer encoded as 7-bit octets.
Expand Down

0 comments on commit b96205d

Please sign in to comment.