Skip to content

Commit

Permalink
ReadOnlySpan, CharMap, nullable and new Compilation helpers (#138)
Browse files Browse the repository at this point in the history
  • Loading branch information
sebastienros authored Jul 11, 2024
1 parent afc46f5 commit e15b211
Show file tree
Hide file tree
Showing 65 changed files with 1,055 additions and 620 deletions.
2 changes: 1 addition & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="System.Memory" Version="4.5.5" />
<PackageVersion Include="FastExpressionCompiler.Internal.src" Version="4.2.0" />
<PackageVersion Include="FastExpressionCompiler.Internal.src" Version="4.2.1" />
<PackageVersion Include="System.Collections.Immutable" Version="8.0.0" />

<!-- Benchmarks -->
Expand Down
5 changes: 4 additions & 1 deletion NuGet.config
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
Expand All @@ -8,5 +8,8 @@
<packageSource key="nuget.org">
<package pattern="*" />
</packageSource>
<packageSource key="FastExpression">
<package pattern="*" />
</packageSource>
</packageSourceMapping>
</configuration>
3 changes: 2 additions & 1 deletion Parlot.sln
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
.editorconfig = .editorconfig
.github\workflows\build.yml = .github\workflows\build.yml
Directory.Packages.props = Directory.Packages.props
NuGet.config = NuGet.config
.github\workflows\publish.yml = .github\workflows\publish.yml
README.md = README.md
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples", "src\Samples\Samples.csproj", "{B9A796FE-4BEB-499A-B506-25F20C749527}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Samples", "src\Samples\Samples.csproj", "{B9A796FE-4BEB-499A-B506-25F20C749527}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down
89 changes: 89 additions & 0 deletions src/Parlot/CharMap.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;

namespace Parlot;

internal sealed class CharMap<T> where T : class
{
private readonly T[] _asciiMap = new T[128];
private Dictionary<uint, T>? _nonAsciiMap;

public CharMap()
{
ExpectedChars = Array.Empty<char>();
}

public CharMap(IEnumerable<KeyValuePair<char, T>> map)
{
var charSet = new HashSet<char>();

foreach (var item in map)
{
charSet.Add(item.Key);
}

ExpectedChars = [.. charSet];
Array.Sort(ExpectedChars);

foreach (var item in map)
{
var c = item.Key;
if (c < 128)
{
_asciiMap[c] ??= item.Value;
}
else
{
_nonAsciiMap ??= [];

if (!_nonAsciiMap.ContainsKey(c))
{
_nonAsciiMap[c] = item.Value;
}
}
}
}

public void Set(char c, T value)
{
ExpectedChars = new HashSet<char>([c, .. ExpectedChars]).ToArray();
Array.Sort(ExpectedChars);

if (c < 128)
{
_asciiMap[c] ??= value;
}
else
{
_nonAsciiMap ??= [];

if (!_nonAsciiMap.ContainsKey(c))
{
_nonAsciiMap[c] = value;
}
}
}

public char[] ExpectedChars { get; private set; }

public T? this[uint c]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
T[] asciiMap = _asciiMap;
if (c < (uint)asciiMap.Length)
{
return asciiMap[c];
}
else
{
T? map = null;
_nonAsciiMap?.TryGetValue(c, out map);
return map;
}
}
}
}
16 changes: 2 additions & 14 deletions src/Parlot/Character.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,6 @@ public static bool IsWhiteSpace(char ch)
return (_characterData[ch] & (byte) CharacterMask.WhiteSpace) != 0;
}

public static bool IsWhiteSpaceNonAscii(char ch)
{
return (ch >= 0x1680 && (
ch == 0x1680 ||
ch == 0x180E ||
(ch >= 0x2000 && ch <= 0x200A) ||
ch == 0x202F ||
ch == 0x205F ||
ch == 0x3000 ||
ch == 0xFEFF));
}

public static bool IsWhiteSpaceOrNewLine(char ch)
{
return (_characterData[ch] & (byte) CharacterMask.WhiteSpaceOrNewLine) != 0;
Expand Down Expand Up @@ -85,7 +73,7 @@ public static char ScanHexEscape(string text, int index, out int length)
public static TextSpan DecodeString(TextSpan span)
{
// Nothing to do if the string doesn't have any escape char
if (span.Buffer.AsSpan(span.Offset, span.Length).IndexOf('\\') == -1)
if (string.IsNullOrEmpty(span.Buffer) || span.Buffer.AsSpan(span.Offset, span.Length).IndexOf('\\') == -1)
{
return span;
}
Expand All @@ -99,7 +87,7 @@ public static TextSpan DecodeString(TextSpan span)
// The assumption is that the new string will be shorter since escapes results are smaller than their source
var dataIndex = 0;
var buffer = source.Buffer;
var buffer = source.Buffer!;
var start = source.Offset;
var end = source.Offset + source.Length;
Expand Down
39 changes: 39 additions & 0 deletions src/Parlot/Compilation/CompilationContext.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Parlot.Fluent;
using System;
using System.Collections.Generic;
using System.Linq.Expressions;

Expand Down Expand Up @@ -51,5 +52,43 @@ public CompilationContext()
/// This is done to optimize compiled parser that are usually used for pattern matching only.
/// </remarks>
public bool DiscardResult { get; set; } = false;

/// <summary>
/// Creates a <see cref="CompilationResult"/> instance with a <see cref="CompilationResult.Value"/> and <see cref="CompilationResult.Success"/>
/// variables.
/// </summary>
/// <typeparam name="TValue">The type of the value returned by the parser instance.</typeparam>
/// <param name="defaultSuccess">The default value of the <see cref="CompilationResult.Success"/> variable.</param>
/// <param name="defaultValue">The default value of the <see cref="CompilationResult.Value"/> variable.</param>
/// <returns></returns>
public CompilationResult CreateCompilationResult<TValue>(bool defaultSuccess = false, Expression? defaultValue = null)
{
return CreateCompilationResult(typeof(TValue), defaultSuccess, defaultValue);
}

/// <summary>
/// Creates a <see cref="CompilationResult"/> instance with a <see cref="CompilationResult.Value"/> and <see cref="CompilationResult.Success"/>
/// variables.
/// </summary>
/// <param name="valueType">The type of the value returned by the parser instance.</param>
/// <param name="defaultSuccess">The default value of the <see cref="CompilationResult.Success"/> variable.</param>
/// <param name="defaultValue">The default value of the <see cref="CompilationResult.Value"/> variable.</param>
/// <returns></returns>
public CompilationResult CreateCompilationResult(Type valueType, bool defaultSuccess = false, Expression? defaultValue = null)
{
var successVariable = Expression.Variable(typeof(bool), $"success{this.NextNumber}");
var valueVariable = Expression.Variable(valueType, $"value{this.NextNumber}");

var result = new CompilationResult { Success = successVariable, Value = valueVariable };

result.Variables.Add(successVariable);
result.Variables.Add(valueVariable);

result.Body.Add(Expression.Assign(successVariable, Expression.Constant(defaultSuccess, typeof(bool))));
result.Body.Add(Expression.Assign(valueVariable, defaultValue ?? Expression.Default(valueType)));

return result;
}

}
}
29 changes: 24 additions & 5 deletions src/Parlot/Compilation/CompilationResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,48 @@ namespace Parlot.Compilation
/// to parse the expected input.
/// The convention is that these statements are returned in the <see cref="Body"/> property, and any variable that needs to be declared in the block
/// that the <see cref="Body"/> is used in are set in the <see cref="Variables"/> list.
/// The <see cref="Success"/> property reprsents the variable that contains the success of the statements once executed, and if <code>true</code> then
/// The <see cref="Success"/> property represents the variable that contains the success of the statements once executed, and if <code>true</code> then
/// the <see cref="Value"/> property contains the result.
/// </summary>
public class CompilationResult
{
internal CompilationResult()
{
}

/// <summary>
/// Gets the list of <see cref="ParameterExpression"/> representing the variables used by the compiled result.
/// </summary>
public List<ParameterExpression> Variables { get; } = new();
public List<ParameterExpression> Variables { get; } = [];

/// <summary>
/// Gets the list of <see cref="Expression"/> representing the body of the compiled results.
/// </summary>
public List<Expression> Body { get; } = new();
public List<Expression> Body { get; } = [];

/// <summary>
/// Gets or sets the <see cref="ParameterExpression"/> of the <see cref="bool"/> variable representing the success of the parsing statements.
/// </summary>
public ParameterExpression Success { get; set; }
public required ParameterExpression Success { get; set; }

/// <summary>
/// Gets or sets the <see cref="ParameterExpression"/> of the <see cref="bool"/> variable representing the value of the parsing statements.
/// </summary>
public ParameterExpression Value { get; set; }
public required ParameterExpression Value { get; set; }

public ParameterExpression DeclareVariable<T>(string name, Expression? defaultValue = null)
{
return DeclareVariable(name, typeof(T), defaultValue);
}

public ParameterExpression DeclareVariable(string name, Type type, Expression? defaultValue = null)
{
var variable = Expression.Variable(type, name);

Variables.Add(variable);
Body.Add(Expression.Assign(variable, defaultValue ?? Expression.Default(type)));

return variable;
}
}
}
Loading

0 comments on commit e15b211

Please sign in to comment.