Skip to content

Commit

Permalink
Adding Name and ISeekable improvements (#177)
Browse files Browse the repository at this point in the history
  • Loading branch information
sebastienros authored Nov 30, 2024
1 parent ba4cccf commit cfa362c
Show file tree
Hide file tree
Showing 58 changed files with 1,484 additions and 443 deletions.
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

0 comments on commit cfa362c

Please sign in to comment.