Skip to content

Commit

Permalink
Adding local benchmarks (#13)
Browse files Browse the repository at this point in the history
  • Loading branch information
sebastienros authored Jan 9, 2021
1 parent 7d0a348 commit 9d2d6da
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 71 deletions.
128 changes: 75 additions & 53 deletions src/Parlot/Character.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,96 +53,118 @@ public static bool IsWhiteSpaceOrNewLine(char ch)
return (ch == '\n') || (ch == '\r') || IsWhiteSpace(ch);
}

public static char ScanHexEscape(ReadOnlySpan<char> text, int index)
public static char ScanHexEscape(ReadOnlySpan<char> text, int index, out int length)
{
var prefix = text[index];
var len = (prefix == 'u') ? 4 : 2;
var lastIndex = Math.Min(len + index, text.Length - index);
var code = 0;

for (var i = index + 1; i < len + index + 1; ++i)
length = 0;

for (var i = index + 1; i < lastIndex + 1; i++)
{
var d = text[i];

if (!IsHexDigit(d))
{
break;
}

length++;
code = code * 16 + HexValue(d);
}

return (char)code;
}

public static ReadOnlySpan<char> DecodeString(ReadOnlySpan<char> buffer)
public static TextSpan DecodeString(string s)
{
return DecodeString(new TextSpan(s));
}

public static TextSpan DecodeString(TextSpan span)
{
// Nothing to do if the string doesn't have any escape char
if (buffer.IndexOf('\\') == -1)
if (span.Buffer.IndexOf('\\', span.Offset, span.Length) == -1)
{
return buffer;
return span;
}

// The asumption is that the new string will be shorter since escapes results are smaller than their source
var data = new char[buffer.Length];

var dataIndex = 0;

for (var i = 0; i < buffer.Length; i++)
var result = String.Create(span.Length, span, static (chars, source) =>
{
var c = buffer[i];
// The asumption is that the new string will be shorter since escapes results are smaller than their source

if (c == '\\')
var dataIndex = 0;
var buffer = source.Buffer;
var bufferLength = buffer.Length;

for (var i = 0; i < bufferLength; i++)
{
i++;
c = buffer[i];
var c = buffer[i];

switch (c)
if (c == '\\')
{
case '0' : data[dataIndex++] = '\0'; break;
case '\'': data[dataIndex++] = '\''; break;
case '"' : data[dataIndex++] = '\"'; break;
case '\\': data[dataIndex++] = '\\'; break;
case 'b' : data[dataIndex++] = '\b'; break;
case 'f' : data[dataIndex++] = '\f'; break;
case 'n' : data[dataIndex++] = '\n'; break;
case 'r' : data[dataIndex++] = '\r'; break;
case 't' : data[dataIndex++] = '\t'; break;
case 'v' : data[dataIndex++] = '\v'; break;
case 'u':
data[dataIndex++] = Character.ScanHexEscape(buffer, i);
i += 4;
break;
case 'x':
data[dataIndex++] = Character.ScanHexEscape(buffer, i);
i += 2;
break;
i++;
c = buffer[i];

switch (c)
{
case '\'': c = '\''; break;
case '"': c = '\"'; break;
case '\\': c = '\\'; break;
case 'b': c = '\b'; break;
case 'f': c = '\f'; break;
case 'n': c = '\n'; break;
case 'r': c = '\r'; break;
case 't': c = '\t'; break;
case 'v': c = '\v'; break;
case 'u':
c = Character.ScanHexEscape(buffer, i, out var length);
i += length;
break;
case 'x':
c = Character.ScanHexEscape(buffer, i, out length);
i += length;
break;
}
}

chars[dataIndex++] = c;
}
else

chars[dataIndex++] = '\0';
});

for (var i = result.Length - 1; i >= 0; i--)
{
if (result[i] != '\0')
{
data[dataIndex++] = c;
return new TextSpan(result, 0, i + 1);
}
}

return new ReadOnlySpan<char>(data, 0, dataIndex).ToString();
return new TextSpan(result);
}

private static int HexValue(char ch)
{
if (ch >= 'A')
if (ch >= '0' && ch <= '9')
{
if (ch >= 'a')
{
if (ch <= 'h')
{
return ch - 'a' + 10;
}
}
else if (ch <= 'H')
{
return ch - 'A' + 10;
}
return ch - 48;
}
else if (ch <= '9')
else if (ch >= 'a' && ch <= 'f')
{
return ch - '0';
return ch - 'a' + 10;
}
else if (ch >= 'A' && ch <= 'F')
{
return ch - 'A' + 10;
}
else
{
return 0;
}

return 0;
}
}
}
11 changes: 2 additions & 9 deletions src/Parlot/Fluent/StringLiteral.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,9 @@ public override bool Parse(ParseContext context, ref ParseResult<TextSpan> resul
if (success)
{
// Remove quotes
var encoded = context.Scanner.Buffer.AsSpan(start + 1, end - start - 2);
var decoded = Character.DecodeString(encoded);
var decoded = Character.DecodeString(new TextSpan(context.Scanner.Buffer, start + 1, end - start - 2));

// Don't create a new string if the decoded string is the same, meaning is
// has no escape sequences.
var span = decoded == encoded || decoded.SequenceEqual(encoded)
? new TextSpan(context.Scanner.Buffer, start + 1, encoded.Length)
: new TextSpan(decoded.ToString());

result.Set(start, end, span);
result.Set(start, end, decoded);
return true;
}
else
Expand Down
15 changes: 10 additions & 5 deletions src/Parlot/Scanner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -358,16 +358,19 @@ private bool ReadQuotedString(char quoteChar, ITokenResult result = null)

Cursor.Advance();

if (!Cursor.Eof && Character.IsHexDigit(Cursor.Current))
if (!Cursor.Eof && Character.IsDecimalDigit(Cursor.Current))
{
Cursor.Advance();
if (!Cursor.Eof && Character.IsHexDigit(Cursor.Current))
if (!Cursor.Eof && Character.IsDecimalDigit(Cursor.Current))
{
Cursor.Advance();
if (!Cursor.Eof && Character.IsHexDigit(Cursor.Current))
if (!Cursor.Eof && Character.IsDecimalDigit(Cursor.Current))
{
Cursor.Advance();
isValidUnicode = true;
if (!Cursor.Eof && Character.IsDecimalDigit(Cursor.Current))
{
isValidUnicode = true;
}
}
}
}
Expand All @@ -388,10 +391,12 @@ private bool ReadQuotedString(char quoteChar, ITokenResult result = null)

if (!Cursor.Eof && Character.IsHexDigit(Cursor.Current))
{
isValidHex = true;
Cursor.Advance();

if (!Cursor.Eof && Character.IsHexDigit(Cursor.Current))
{
isValidHex = true;
Cursor.Advance();
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/Parlot/Fluent/TextSpan.cs → src/Parlot/TextSpan.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System;

namespace Parlot.Fluent
namespace Parlot
{
public readonly struct TextSpan : IEquatable<string>, IEquatable<TextSpan>
{
Expand Down
37 changes: 37 additions & 0 deletions test/Parlot.Benchmarks/ParlotBenchmarks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using System;

namespace Parlot.Benchmarks
{
[MemoryDiagnoser, GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory), ShortRunJob]
public class ParlotBenchmarks
{
private const string _stringWithEscapes = "This is a new line \\n \\t and a tab and some \\xa0";
private const string _stringWithoutEscapes = "This is a new line \n \t and a tab and some \xa0";

[Benchmark]
public TextSpan DecodeStringWithEscapes()
{
return Character.DecodeString(_stringWithEscapes);
}

[Benchmark]
public TextSpan DecodeStringWithoutEscapes()
{
return Character.DecodeString(_stringWithoutEscapes);
}

[Benchmark]
public string DecodeStringWithEscapesToString()
{
return Character.DecodeString(_stringWithEscapes).ToString();
}

[Benchmark]
public string DecodeStringWithoutEscapesToString()
{
return Character.DecodeString(_stringWithoutEscapes).ToString();
}
}
}
8 changes: 5 additions & 3 deletions test/Parlot.Tests/CharacterTests.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
using System;
using Xunit;
using Xunit;

namespace Parlot.Tests
{
public class CharacterTests
{
[Theory]
[InlineData("a\\bc", "a\bc")]
[InlineData("\\xa0", "\xa0")]
[InlineData("\\xfh", "\xfh")]
[InlineData("\\u1234", "\u1234")]
public void ShouldDescodeString(string text, string expected)
{
Assert.Equal(expected, Character.DecodeString(text.AsSpan()).ToString());
Assert.Equal(expected, Character.DecodeString(new TextSpan(text)).ToString());
}
}
}

0 comments on commit 9d2d6da

Please sign in to comment.