Skip to content

Commit

Permalink
Merge pull request #93 from greymistcube/refactor/less-backtracking
Browse files Browse the repository at this point in the history
⚡ Optimize decoding with less backtracking
  • Loading branch information
greymistcube authored Aug 3, 2023
2 parents 58b07b2 + f369731 commit 4f4c668
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 78 deletions.
116 changes: 38 additions & 78 deletions Bencodex/Decoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,27 +28,24 @@ public Decoder(Stream stream)

public IValue Decode()
{
IValue value = DecodeValue();
if (ReadByte() is { } b)
{
IValue value = DecodeValue() ??
throw new DecodingException(
$"An unexpected trailing byte 0x{b:x} at {_offset - 1}."
);
}
$"An unexpected token byte 0x{0x65:x} at {_offset - 1}");

return value;
return EndOfStream()
? value
: throw new DecodingException(
$"An unexpected trailing byte remains at {_offset}.");
}

private IValue DecodeValue()
private IValue? DecodeValue()
{
const byte e = 0x65; // 'e'

switch (ReadByte())
{
case null:
throw new DecodingException(
$"The byte stream terminates unexpectedly at {_offset}."
);
case 0x65: // 'e'
return null;

case 0x6e: // 'n'
#pragma warning disable SA1129
Expand All @@ -70,38 +67,20 @@ private IValue DecodeValue()

case 0x6c: // 'l'
var elements = new List<IValue>();
while (true)
while (DecodeValue() is IValue element)
{
byte b = ReadByte() ?? throw new DecodingException(
$"The byte stream terminates unexpectedly at {_offset}."
);
if (b == e)
{
break;
}

Back();
IValue element = DecodeValue();
elements.Add(element);
}

return new Bencodex.Types.List(elements);

case 0x64: // 'd'
var pairs = new List<KeyValuePair<IKey, IValue>>();
while (true)
while (DecodeKey() is IKey key)
{
byte b = ReadByte() ?? throw new DecodingException(
$"The byte stream terminates unexpectedly at {_offset}."
);
if (b == e)
{
break;
}

Back();
IKey key = DecodeKey();
IValue value = DecodeValue();
IValue value = DecodeValue()
?? throw new DecodingException(
$"An unexpected token byte 0x{0x65:x} at {_offset - 1}");
pairs.Add(new KeyValuePair<IKey, IValue>(key, value));
}

Expand All @@ -125,14 +104,12 @@ private IValue DecodeValue()
}
}

private IKey DecodeKey()
private IKey? DecodeKey()
{
switch (ReadByte())
{
case null:
throw new DecodingException(
$"Expected a dictionary key, but the byte stream terminates at {_offset}."
);
case 0x65: // 'e'
return null;

case 0x75: // 'u':
return ReadTextAfterPrefix();
Expand Down Expand Up @@ -184,7 +161,7 @@ private byte[] Read(byte[] buffer)
return buffer;
}

private byte? ReadByte()
private byte ReadByte()
{
if (_didBack)
{
Expand All @@ -201,7 +178,20 @@ private byte[] Read(byte[] buffer)

_offset++;
_didBack = false;
return read == 0 ? (byte?)null : _tinyBuffer[0];
return read == 0
? throw new DecodingException($"The byte stream terminates unexpectedly at {_offset}.")
: _tinyBuffer[0];
}

// Checks end of stream. Should be called only once at the very end.
private bool EndOfStream()
{
if (_didBack)
{
return false;
}

return _stream.Read(_tinyBuffer, 0, 1) == 0;
}

private void Back()
Expand All @@ -224,15 +214,7 @@ private int ReadLength()
const int asciiZero = 0x30; // '0'
int length = 0;

var b = ReadByte();

if (b is null)
{
throw new DecodingException(
$"Expected digits, but the byte stream terminates at {_offset}.");
}

byte lastByte = b.Value;
byte lastByte = ReadByte();
while (lastByte != colon)
{
#pragma warning disable SA1131
Expand All @@ -247,10 +229,7 @@ private int ReadLength()
length *= 10;
length += lastByte - asciiZero;

lastByte = ReadByte() ?? throw new DecodingException(
$"Expected a delimiter byte 0x{colon:x}, but the byte stream terminates " +
$"at {_offset}."
);
lastByte = ReadByte();
}

return length;
Expand All @@ -262,44 +241,28 @@ private byte[] ReadDigits(byte delimiter)
byte[] buffer = new byte[defaultBufferSize];

var b = ReadByte();

if (b is null)
{
throw new DecodingException(
$"Expected a minus sign or a digit, " +
$"but the byte stream terminates at {_offset}."
);
}

bool minus = false;
if (b == 0x2d) // '-'
{
minus = true;
b = ReadByte();

if (b is null)
{
throw new DecodingException(
$"Expected digits, but the byte stream terminates at {_offset}."
);
}
}

int digitsLength;

if (minus)
{
buffer[0] = 0x2d;
buffer[1] = b.Value;
buffer[1] = b;
digitsLength = 2;
}
else
{
buffer[0] = b.Value;
buffer[0] = b;
digitsLength = 1;
}

byte lastByte = b.Value;
byte lastByte = b;

while (lastByte != delimiter)
{
Expand All @@ -312,10 +275,7 @@ private byte[] ReadDigits(byte delimiter)
}
#pragma warning restore SA1131

lastByte = ReadByte() ?? throw new DecodingException(
$"Expected a delimiter byte 0x{delimiter:x}, but the byte stream terminates " +
$"at {_offset}."
);
lastByte = ReadByte();

if (digitsLength >= buffer.Length)
{
Expand Down
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ To be released.
- Removed `IndirectValue` class. [[#91]]
- Changed `Codec.Encode()` and `Codec.Decode()` to no longer accept
`IOffloadOptions` as an argument. [[#91]]
- Optimized for faster decoding on encoded `List`s and `Dictionary`s. [[#93]]

[#91]: https://github.com/planetarium/bencodex.net/pull/91
[#93]: https://github.com/planetarium/bencodex.net/pull/93


Version 0.12.0
Expand Down

0 comments on commit 4f4c668

Please sign in to comment.