Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding Name and ISeekable improvements #177

Merged
merged 13 commits into from
Nov 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion docs/parsers.md
Original file line number Diff line number Diff line change
Expand Up @@ -553,7 +553,7 @@ Result:

### Deferred

Creates a parser that can be references before it is actually defined. This is used when there is a cyclic dependency between parsers.
Creates a parser that can be referenced before it is actually defined. This is used when there is a cyclic dependency between parsers.

```c#
Deferred<T> Deferred<T>()
Expand Down Expand Up @@ -777,6 +777,7 @@ Result:
```
failure: "Unexpected char c"
```

### When

Adds some additional logic for a parser to succeed.
Expand Down Expand Up @@ -823,6 +824,15 @@ Parser<U> Discard<U>()
Parser<U> Discard<U>(U value)
```

### Lookup

Builds a parser that lists all possible matches to improve performance. Most parsers implement `ISeekable` parsers in order to provide `OneOf` a way to build a lookup table and identify the potential next parsers in the chain. Some parsers don't implement `ISeekable` because they are built too late, like `Deferred`. The `Lookup` parser circumvents that lack.

```c#
Parser<T> Lookup<U>(params ReadOnlySpan<char> expectedChars)
Parser<T> Lookup(params ISeekable[] parsers)
```

## Other parsers

### AnyCharBefore
Expand Down
5 changes: 5 additions & 0 deletions docs/writing.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,15 @@ public override bool Parse(ParseContext context, ref ParseResult<ValueTuple<T1,
if (_parser2.Parse(context, ref parseResult2))
{
result.Set(parseResult1.Start, parseResult2.End, new ValueTuple<T1, T2>(parseResult1.Value, parseResult2.Value));

context.ExitParser(this);
return true;
}

context.Scanner.Cursor.ResetPosition(start);
}

context.ExitParser(this);
return false;
}
```
Expand All @@ -51,10 +54,12 @@ public override bool Parse(ParseContext context, ref ParseResult<T> result)
{
if (parser.Parse(context, ref result))
{
context.ExitParser(this);
return true;
}
}

context.ExitParser(this);
return false;
}
```
Expand Down
9 changes: 8 additions & 1 deletion src/Parlot/CharMap.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;

namespace Parlot;

/// <summary>
/// Combines maps of ascii and non-ascii characters.
/// If all characters are ascii, the non-ascii dictionary is not used.
/// </summary>
internal sealed class CharMap<T> where T : class
{
public static MethodInfo IndexerMethodInfo = typeof(CharMap<T>).GetMethod("get_Item", BindingFlags.Public | BindingFlags.Instance)!;

private readonly T[] _asciiMap = new T[128];
private Dictionary<uint, T>? _nonAsciiMap;

Expand Down
11 changes: 8 additions & 3 deletions src/Parlot/Compilation/CompilationContext.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Parlot.Fluent;
using Parlot.Fluent;
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
Expand Down Expand Up @@ -48,7 +48,7 @@ public CompilationContext()
/// Gets or sets whether the current compilation phase should ignore the results of the parsers.
/// </summary>
/// <remarks>
/// When set to false, the compiled statements don't need to record and define the <see cref="CompilationResult.Value"/> property.
/// When set to true, the compiled statements don't need to record and define the <see cref="CompilationResult.Value"/> property.
/// This is done to optimize compiled parser that are usually used for pattern matching only.
/// </remarks>
public bool DiscardResult { get; set; }
Expand Down Expand Up @@ -85,7 +85,12 @@ public CompilationResult CreateCompilationResult(Type valueType, bool defaultSuc
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)));

// Don't need to assign a type's default value
if (defaultValue != null)
{
result.Body.Add(Expression.Assign(valueVariable, defaultValue ?? Expression.Default(valueType)));
}

return result;
}
Expand Down
8 changes: 7 additions & 1 deletion src/Parlot/Compilation/CompiledParser.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Parlot.Fluent;
using Parlot.Fluent;
using System;

namespace Parlot.Compilation;
Expand Down Expand Up @@ -26,22 +26,28 @@ public class CompiledParser<T> : Parser<T>, ICompiledParser

public CompiledParser(Func<ParseContext, ValueTuple<bool, T>> parse, Parser<T> source)
{
Name = "Compiled";
_parse = parse ?? throw new ArgumentNullException(nameof(parse));
Source = source;
}

public override bool Parse(ParseContext context, ref ParseResult<T> result)
{
context.EnterParser(this);

var cursor = context.Scanner.Cursor;
var start = cursor.Offset;
var parsed = _parse(context);

if (parsed.Item1)
{
result.Set(start, cursor.Offset, parsed.Item2);

context.ExitParser(this);
return true;
}

context.ExitParser(this);
return false;
}
}
31 changes: 24 additions & 7 deletions src/Parlot/Compilation/ExpressionHelper.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Parlot.Fluent;
using Parlot.Fluent;
using System;
using System.Linq.Expressions;
using System.Reflection;
Expand All @@ -8,7 +8,7 @@ namespace Parlot.Compilation;

public static class ExpressionHelper
{
internal static readonly MethodInfo ParserContext_SkipWhiteSpaceMethod = typeof(ParseContext).GetMethod(nameof(ParseContext.SkipWhiteSpace), Array.Empty<Type>())!;
internal static readonly MethodInfo ParserContext_SkipWhiteSpaceMethod = typeof(ParseContext).GetMethod(nameof(ParseContext.SkipWhiteSpace), [])!;
internal static readonly MethodInfo ParserContext_WhiteSpaceParser = typeof(ParseContext).GetProperty(nameof(ParseContext.WhiteSpaceParser))?.GetGetMethod()!;
internal static readonly MethodInfo Scanner_ReadText_NoResult = typeof(Scanner).GetMethod(nameof(Parlot.Scanner.ReadText), [typeof(ReadOnlySpan<char>), typeof(StringComparison)])!;
internal static readonly MethodInfo Scanner_ReadChar = typeof(Scanner).GetMethod(nameof(Parlot.Scanner.ReadChar), [typeof(char)])!;
Expand All @@ -23,23 +23,21 @@ public static class ExpressionHelper
internal static readonly MethodInfo Scanner_ReadDoubleQuotedString = typeof(Scanner).GetMethod(nameof(Parlot.Scanner.ReadDoubleQuotedString), [])!;
internal static readonly MethodInfo Scanner_ReadQuotedString = typeof(Scanner).GetMethod(nameof(Parlot.Scanner.ReadQuotedString), [])!;

internal static readonly MethodInfo Cursor_Advance = typeof(Cursor).GetMethod(nameof(Parlot.Cursor.Advance), Array.Empty<Type>())!;
internal static readonly MethodInfo Cursor_Advance = typeof(Cursor).GetMethod(nameof(Parlot.Cursor.Advance), [])!;
internal static readonly MethodInfo Cursor_AdvanceNoNewLines = typeof(Cursor).GetMethod(nameof(Parlot.Cursor.AdvanceNoNewLines), [typeof(int)])!;
internal static readonly MethodInfo Cursor_ResetPosition = typeof(Cursor).GetMethod("ResetPosition")!;
internal static readonly MethodInfo Character_IsWhiteSpace = typeof(Character).GetMethod(nameof(Character.IsWhiteSpace))!;

internal static readonly ConstructorInfo Exception_ToString = typeof(Exception).GetConstructor([typeof(string)])!;

internal static readonly ConstructorInfo TextSpan_Constructor = typeof(TextSpan).GetConstructor([typeof(string), typeof(int), typeof(int)])!;

internal static readonly MethodInfo ReadOnlySpan_ToString = typeof(ReadOnlySpan<char>).GetMethod(nameof(ToString), [])!;

internal static readonly MethodInfo MemoryExtensions_AsSpan = typeof(MemoryExtensions).GetMethod(nameof(MemoryExtensions.AsSpan), [typeof(string)])!;

public static Expression ArrayEmpty<T>() => ((Expression<Func<object>>)(() => Array.Empty<T>())).Body;
public static Expression New<T>() where T : new() => ((Expression<Func<T>>)(() => new T())).Body;

public static readonly Expression<Func<Cursor, char, char, bool>> CharacterIsInRange = (cursor, b, c) => Character.IsInRange(cursor.Current, b, c);

//public static Expression NewOptionalResult<T>(this CompilationContext _, Expression hasValue, Expression value) => Expression.New(GetOptionalResult_Constructor<T>(), [hasValue, value]);
public static Expression NewTextSpan(this CompilationContext _, Expression buffer, Expression offset, Expression count) => Expression.New(TextSpan_Constructor, [buffer, offset, count]);
public static MemberExpression Scanner(this CompilationContext context) => Expression.Field(context.ParseContext, "Scanner");
Expand All @@ -53,11 +51,28 @@ public static class ExpressionHelper
public static MemberExpression Buffer(this CompilationContext context) => Expression.Field(context.Scanner(), "Buffer");
public static Expression ThrowObject(this CompilationContext _, Expression o) => Expression.Throw(Expression.New(Exception_ToString, Expression.Call(o, o.Type.GetMethod("ToString", [])!)));
public static Expression ThrowParseException(this CompilationContext context, Expression message) => Expression.Throw(Expression.New(typeof(ParseException).GetConstructors().First(), [message, context.Position()]));
public static Expression BreakPoint(this CompilationContext _, Expression state, Action<object> action) => Expression.Invoke(Expression.Constant(action, typeof(Action<object>)), Expression.Convert(state, typeof(object)));

public static MethodCallExpression ReadSingleQuotedString(this CompilationContext context) => Expression.Call(context.Scanner(), Scanner_ReadSingleQuotedString);
public static MethodCallExpression ReadDoubleQuotedString(this CompilationContext context) => Expression.Call(context.Scanner(), Scanner_ReadDoubleQuotedString);
public static MethodCallExpression ReadQuotedString(this CompilationContext context) => Expression.Call(context.Scanner(), Scanner_ReadQuotedString);
public static MethodCallExpression ReadChar(this CompilationContext context, char c) => Expression.Call(context.Scanner(), Scanner_ReadChar, Expression.Constant(c));

// Surprisingly whiting the same direct code with this helper is slower that calling scanner.ReadChar()
public static Expression ReadCharInlined(this CompilationContext context, char c, CompilationResult result)
{
var constant = Expression.Constant(c);
return Expression.IfThen(
Expression.Equal(context.Current(), constant), // if (cursor.Current == 'c')
Expression.Block(
Expression.Assign(result.Success, TrueExpression),
context.Advance(),
context.DiscardResult
? Expression.Empty()
: Expression.Assign(result.Value, constant)
)
);
}
public static MethodCallExpression ReadDecimal(this CompilationContext context) => Expression.Call(context.Scanner(), Scanner_ReadDecimal);
public static MethodCallExpression ReadDecimal(this CompilationContext context, Expression allowLeadingSign, Expression allowDecimalSeparator, Expression allowGroupSeparator, Expression allowExponent, Expression number, Expression decimalSeparator, Expression groupSeparator) => Expression.Call(context.Scanner(), Scanner_ReadDecimalAllArguments, allowLeadingSign, allowDecimalSeparator, allowGroupSeparator, allowExponent, number, decimalSeparator, groupSeparator);
public static MethodCallExpression ReadInteger(this CompilationContext context) => Expression.Call(context.Scanner(), Scanner_ReadInteger);
Expand Down Expand Up @@ -88,4 +103,6 @@ public static MethodCallExpression ParserSkipWhiteSpace(this CompilationContext
{
return Expression.Call(context.ParseContext, ParserContext_SkipWhiteSpaceMethod);
}

public static ConstantExpression TrueExpression { get; } = Expression.Constant(true, typeof(bool));
}
4 changes: 3 additions & 1 deletion src/Parlot/Fluent/Always.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Parlot.Compilation;
using Parlot.Compilation;
using System.Linq.Expressions;

namespace Parlot.Fluent;
Expand All @@ -12,6 +12,7 @@ public sealed class Always<T> : Parser<T>, ICompilable

public Always(T value)
{
Name = "Always";
_value = value;
}

Expand All @@ -21,6 +22,7 @@ public override bool Parse(ParseContext context, ref ParseResult<T> result)

result.Set(context.Scanner.Cursor.Offset, context.Scanner.Cursor.Offset, _value);

context.ExitParser(this);
return true;
}

Expand Down
11 changes: 10 additions & 1 deletion src/Parlot/Fluent/Between.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Parlot.Compilation;
using Parlot.Compilation;
using Parlot.Rewriting;
using System;
using System.Linq.Expressions;
Expand All @@ -23,6 +23,8 @@ public Between(Parser<A> before, Parser<T> parser, Parser<B> after)
ExpectedChars = seekable.ExpectedChars;
SkipWhitespace = seekable.SkipWhitespace;
}

Name = $"Between({before.Name},{parser.Name},{after.Name})";
}

public bool CanSeek { get; }
Expand All @@ -43,13 +45,17 @@ public override bool Parse(ParseContext context, ref ParseResult<T> result)

if (!_before.Parse(context, ref parsedA))
{
context.ExitParser(this);

// Don't reset position since _before should do it
return false;
}

if (!_parser.Parse(context, ref result))
{
cursor.ResetPosition(start);

context.ExitParser(this);
return false;
}

Expand All @@ -58,9 +64,12 @@ public override bool Parse(ParseContext context, ref ParseResult<T> result)
if (!_after.Parse(context, ref parsedB))
{
cursor.ResetPosition(start);

context.ExitParser(this);
return false;
}

context.ExitParser(this);
return true;
}

Expand Down
27 changes: 11 additions & 16 deletions src/Parlot/Fluent/Capture.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Parlot.Compilation;
using Parlot.Compilation;
using System.Linq.Expressions;

namespace Parlot.Fluent;
Expand All @@ -10,6 +10,7 @@ public sealed class Capture<T> : Parser<TextSpan>, ICompilable
public Capture(Parser<T> parser)
{
_parser = parser;
Name = $"{parser.Name} (Capture)";
}

public override bool Parse(ParseContext context, ref ParseResult<TextSpan> result)
Expand All @@ -28,11 +29,11 @@ public override bool Parse(ParseContext context, ref ParseResult<TextSpan> resul

result.Set(start.Offset, end, new TextSpan(context.Scanner.Buffer, start.Offset, length));

context.ExitParser(this);
return true;
}

context.Scanner.Cursor.ResetPosition(start);

context.ExitParser(this);
return false;
}

Expand Down Expand Up @@ -61,31 +62,25 @@ public CompilationResult Compile(CompilationContext context)
//
// success = true;
// }
// else
// {
// context.Scanner.Cursor.ResetPosition(start);
// }

var startOffset = context.Offset(start);
var startOffset = result.DeclareVariable<int>($"startOffset{context.NextNumber}", context.Offset(start));

result.Body.Add(
Expression.Block(
parserCompileResult.Variables,
Expression.Block(parserCompileResult.Body),
Expression.IfThenElse(
parserCompileResult.Success,
Expression.Block(
context.DiscardResult
? Expression.Empty()
: Expression.Assign(result.Value,
Expression.IfThen(
test: parserCompileResult.Success,
ifTrue: Expression.Block(
// Never discard result here, that would nullify this parser
Expression.Assign(result.Value,
context.NewTextSpan(
context.Buffer(),
startOffset,
Expression.Subtract(context.Offset(), startOffset)
)),
Expression.Assign(result.Success, Expression.Constant(true, typeof(bool)))
),
context.ResetPosition(start)
)
)
)
);
Expand Down
6 changes: 5 additions & 1 deletion src/Parlot/Fluent/CharLiteral.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Parlot.Compilation;
using Parlot.Compilation;
using Parlot.Rewriting;
using System.Linq.Expressions;

Expand All @@ -10,6 +10,7 @@ public CharLiteral(char c)
{
Char = c;
ExpectedChars = [c];
Name = $"Char('{c}')";
}

public char Char { get; }
Expand All @@ -31,9 +32,12 @@ public override bool Parse(ParseContext context, ref ParseResult<char> result)
var start = cursor.Offset;
cursor.Advance();
result.Set(start, cursor.Offset, Char);

context.ExitParser(this);
return true;
}

context.ExitParser(this);
return false;
}

Expand Down
Loading