Skip to content

Commit b96205d

Browse files
committed
Added Encode extension to BufferWriterSlim type
1 parent 0aa4c56 commit b96205d

File tree

3 files changed

+190
-13
lines changed

3 files changed

+190
-13
lines changed

src/DotNext.IO/Buffers/BufferWriter.cs

+79-12
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,9 @@ static int Write7BitEncodedInteger(ref SpanWriter<byte> writer, int value)
6060

6161
internal static int WriteLength(this IBufferWriter<byte> buffer, int length, LengthFormat lengthFormat)
6262
{
63-
var writer = new SpanWriter<byte>(buffer.GetSpan(SevenBitEncodedInt.MaxSizeInBytes));
64-
buffer.Advance(writer.WriteLength(length, lengthFormat));
65-
return writer.WrittenCount;
63+
var bytesWritten = WriteLength(buffer.GetSpan(SevenBitEncodedInt.MaxSizeInBytes), length, lengthFormat);
64+
buffer.Advance(bytesWritten);
65+
return bytesWritten;
6666
}
6767

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

74+
private static int WriteLength(this ref BufferWriterSlim<byte> buffer, int length, LengthFormat lengthFormat)
75+
{
76+
var bytesWritten = WriteLength(buffer.GetSpan(SevenBitEncodedInt.MaxSizeInBytes), length, lengthFormat);
77+
buffer.Advance(bytesWritten);
78+
return bytesWritten;
79+
}
80+
7481
/// <summary>
7582
/// Encodes string using the specified encoding.
7683
/// </summary>
7784
/// <param name="writer">The buffer writer.</param>
78-
/// <param name="value">The sequence of characters.</param>
85+
/// <param name="chars">The sequence of characters.</param>
7986
/// <param name="context">The encoding context.</param>
8087
/// <param name="lengthFormat">String length encoding format; or <see langword="null"/> to prevent encoding of string length.</param>
8188
/// <returns>The number of written bytes.</returns>
82-
public static long Encode(this IBufferWriter<byte> writer, ReadOnlySpan<char> value, in EncodingContext context, LengthFormat? lengthFormat = null)
89+
public static long Encode(this IBufferWriter<byte> writer, ReadOnlySpan<char> chars, in EncodingContext context, LengthFormat? lengthFormat = null)
8390
{
8491
var result = lengthFormat.HasValue
85-
? writer.WriteLength(context.Encoding.GetByteCount(value), lengthFormat.GetValueOrDefault())
92+
? writer.WriteLength(context.Encoding.GetByteCount(chars), lengthFormat.GetValueOrDefault())
8693
: 0L;
8794

88-
context.GetEncoder().Convert(value, writer, true, out var bytesWritten, out _);
95+
context.GetEncoder().Convert(chars, writer, true, out var bytesWritten, out _);
8996
result += bytesWritten;
9097

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

108115
var bytesWritten = context.TryGetEncoder() is { } encoder
109-
? encoder.GetBytes(value, writer.RemainingSpan, flush: true)
110-
: context.Encoding.GetBytes(value, writer.RemainingSpan);
116+
? encoder.GetBytes(chars, writer.RemainingSpan, flush: true)
117+
: context.Encoding.GetBytes(chars, writer.RemainingSpan);
111118
result += bytesWritten;
112119
writer.Advance(bytesWritten);
113120

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

129136
return result;
130137
}
138+
139+
/// <summary>
140+
/// Encodes string using the specified encoding.
141+
/// </summary>
142+
/// <param name="writer">The buffer writer.</param>
143+
/// <param name="chars">The sequence of characters.</param>
144+
/// <param name="context">The encoding context.</param>
145+
/// <param name="lengthFormat">String length encoding format; or <see langword="null"/> to prevent encoding of string length.</param>
146+
/// <returns>The number of written bytes.</returns>
147+
public static int Encode(this ref BufferWriterSlim<byte> writer, scoped ReadOnlySpan<char> chars, in EncodingContext context,
148+
LengthFormat? lengthFormat = null)
149+
{
150+
Span<byte> buffer;
151+
int byteCount, result;
152+
if (lengthFormat.HasValue)
153+
{
154+
byteCount = context.Encoding.GetByteCount(chars);
155+
result = writer.WriteLength(byteCount, lengthFormat.GetValueOrDefault());
156+
157+
buffer = writer.GetSpan(byteCount);
158+
byteCount = context.TryGetEncoder() is { } encoder
159+
? encoder.GetBytes(chars, buffer, flush: true)
160+
: context.Encoding.GetBytes(chars, buffer);
161+
162+
result += byteCount;
163+
writer.Advance(byteCount);
164+
}
165+
else
166+
{
167+
result = 0;
168+
var encoder = context.GetEncoder();
169+
byteCount = context.Encoding.GetMaxByteCount(1);
170+
for (int charsUsed, bytesWritten; !chars.IsEmpty; chars = chars.Slice(charsUsed), result += bytesWritten)
171+
{
172+
buffer = writer.GetSpan(byteCount);
173+
var maxChars = buffer.Length / byteCount;
174+
175+
encoder.Convert(chars, buffer, chars.Length <= maxChars, out charsUsed, out bytesWritten, out _);
176+
writer.Advance(bytesWritten);
177+
}
178+
}
179+
180+
return result;
181+
}
182+
183+
/// <summary>
184+
/// Writes a sequence of bytes prefixed with the length.
185+
/// </summary>
186+
/// <param name="writer">The buffer writer.</param>
187+
/// <param name="value">A sequence of bytes to be written.</param>
188+
/// <param name="lengthFormat">A format of the buffer length to be written.</param>
189+
/// <returns>A number of bytes written.</returns>
190+
public static int Write(this ref BufferWriterSlim<byte> writer, scoped ReadOnlySpan<byte> value, LengthFormat lengthFormat)
191+
{
192+
var result = writer.WriteLength(value.Length, lengthFormat);
193+
writer.Write(value);
194+
result += value.Length;
195+
196+
return result;
197+
}
131198

132199
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)
133200
where T : notnull, ISpanFormattable

src/DotNext.Tests/Buffers/BufferWriterSlimTests.cs

+92-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
using System.Numerics;
22
using System.Text;
3-
using DotNext.Buffers.Binary;
43
using static System.Globalization.CultureInfo;
54

65
namespace DotNext.Buffers;
76

7+
using Binary;
8+
using IO;
9+
using static DotNext.Text.EncodingExtensions;
10+
811
public sealed class BufferWriterSlimTests : Test
912
{
1013
[Fact]
@@ -360,4 +363,92 @@ public static void ReadWriteBigInteger()
360363

361364
Equal(expected, new BigInteger(writer.WrittenSpan));
362365
}
366+
367+
private static void EncodeDecodeZeroAndMaxValue<T>()
368+
where T : struct, IBinaryInteger<T>, IUnsignedNumber<T>
369+
{
370+
Span<byte> buffer = stackalloc byte[SevenBitEncodedInteger<T>.MaxSizeInBytes];
371+
var writer = new BufferWriterSlim<byte>(buffer);
372+
var reader = new SpanReader<byte>(buffer);
373+
374+
Equal(1, writer.Write7BitEncodedInteger(T.Zero));
375+
Equal(T.Zero, reader.Read7BitEncodedInteger<T>());
376+
377+
writer.Clear(reuseBuffer: true);
378+
reader.Reset();
379+
380+
Equal(SevenBitEncodedInteger<T>.MaxSizeInBytes, writer.Write7BitEncodedInteger(T.AllBitsSet));
381+
Equal(T.AllBitsSet, reader.Read7BitEncodedInteger<T>());
382+
}
383+
384+
[Fact]
385+
public static void EncodeDecodeUInt32() => EncodeDecodeZeroAndMaxValue<uint>();
386+
387+
[Fact]
388+
public static void EncodeDecodeUInt64() => EncodeDecodeZeroAndMaxValue<ulong>();
389+
390+
[InlineData(LengthFormat.BigEndian)]
391+
[InlineData(LengthFormat.LittleEndian)]
392+
[InlineData(LengthFormat.Compressed)]
393+
[Theory]
394+
public static void WriteLengthPrefixedBytes(LengthFormat format)
395+
{
396+
ReadOnlySpan<byte> expected = [1, 2, 3];
397+
398+
var writer = new BufferWriterSlim<byte>();
399+
True(writer.Write(expected, format) > 0);
400+
401+
using var buffer = writer.DetachOrCopyBuffer();
402+
var reader = IAsyncBinaryReader.Create(buffer.Memory);
403+
using var actual = reader.ReadBlock(format);
404+
Equal(expected, actual.Span);
405+
}
406+
407+
[Theory]
408+
[InlineData("UTF-8", null)]
409+
[InlineData("UTF-8", LengthFormat.LittleEndian)]
410+
[InlineData("UTF-8", LengthFormat.BigEndian)]
411+
[InlineData("UTF-8", LengthFormat.Compressed)]
412+
[InlineData("UTF-16LE", null)]
413+
[InlineData("UTF-16LE", LengthFormat.LittleEndian)]
414+
[InlineData("UTF-16LE", LengthFormat.BigEndian)]
415+
[InlineData("UTF-16LE", LengthFormat.Compressed)]
416+
[InlineData("UTF-16BE", null)]
417+
[InlineData("UTF-16BE", LengthFormat.LittleEndian)]
418+
[InlineData("UTF-16BE", LengthFormat.BigEndian)]
419+
[InlineData("UTF-16BE", LengthFormat.Compressed)]
420+
[InlineData("UTF-32LE", null)]
421+
[InlineData("UTF-32LE", LengthFormat.LittleEndian)]
422+
[InlineData("UTF-32LE", LengthFormat.BigEndian)]
423+
[InlineData("UTF-32LE", LengthFormat.Compressed)]
424+
[InlineData("UTF-32BE", null)]
425+
[InlineData("UTF-32BE", LengthFormat.LittleEndian)]
426+
[InlineData("UTF-32BE", LengthFormat.BigEndian)]
427+
[InlineData("UTF-32BE", LengthFormat.Compressed)]
428+
public static void EncodeDecodeString(string encodingName, LengthFormat? format)
429+
{
430+
var encoding = Encoding.GetEncoding(encodingName);
431+
const string expected = "Hello, world!&*(@&*(fghjwgfwffgw Привет, мир!";
432+
var writer = new BufferWriterSlim<byte>();
433+
434+
True(writer.Encode(expected, encoding, format) > 0);
435+
436+
using var buffer = writer.DetachOrCopyBuffer();
437+
MemoryOwner<char> actual;
438+
if (format.HasValue)
439+
{
440+
var reader = IAsyncBinaryReader.Create(buffer.Memory);
441+
actual = reader.Decode(encoding, format.GetValueOrDefault());
442+
Equal(expected, actual.Span);
443+
}
444+
else
445+
{
446+
actual = encoding.GetChars(buffer.Span);
447+
}
448+
449+
using (actual)
450+
{
451+
Equal(expected, actual.Span);
452+
}
453+
}
363454
}

src/DotNext/Buffers/ByteBuffer.cs

+19
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,25 @@ public static int Write7BitEncodedInteger<T>(this ref SpanWriter<byte> writer, T
342342

343343
return count;
344344
}
345+
346+
/// <summary>
347+
/// Writes 32-bit integer in a compressed format.
348+
/// </summary>
349+
/// <param name="writer">The buffer writer.</param>
350+
/// <param name="value">The integer to be written.</param>
351+
/// <returns>A number of bytes written to the buffer.</returns>
352+
public static int Write7BitEncodedInteger<T>(this ref BufferWriterSlim<byte> writer, T value)
353+
where T : struct, IBinaryInteger<T>, IUnsignedNumber<T>
354+
{
355+
var count = 0;
356+
foreach (var b in new SevenBitEncodedInteger<T>(value))
357+
{
358+
writer.Add() = b;
359+
count += 1;
360+
}
361+
362+
return count;
363+
}
345364

346365
/// <summary>
347366
/// Decodes an integer encoded as 7-bit octets.

0 commit comments

Comments
 (0)