Skip to content

Commit

Permalink
Add support for optional results (#103)
Browse files Browse the repository at this point in the history
  • Loading branch information
sebastienros authored May 9, 2024
1 parent 8a0e312 commit ab8826e
Show file tree
Hide file tree
Showing 15 changed files with 211 additions and 128 deletions.
19 changes: 17 additions & 2 deletions src/Parlot/Compilation/ExpressionHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,12 @@ public static class ExpressionHelper
internal static MethodInfo Cursor_AdvanceNoNewLines = typeof(Cursor).GetMethod(nameof(Parlot.Cursor.AdvanceNoNewLines), [typeof(int)]);

internal static ConstructorInfo TextSpan_Constructor = typeof(TextSpan).GetConstructor([typeof(string), typeof(int), typeof(int)]);
internal static ConstructorInfo GetOptionalResult_Constructor<T>() => typeof(OptionalResult<T>).GetConstructor([typeof(bool), typeof(T)]);

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 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");
public static MemberExpression Cursor(this CompilationContext context) => Expression.Field(context.Scanner(), "Cursor");
Expand Down Expand Up @@ -62,14 +67,24 @@ public static ParameterExpression DeclareSuccessVariable(this CompilationContext
return result.Success;
}

public static ParameterExpression DeclareVariable<T>(this CompilationContext context, CompilationResult result, string name, Expression defaultValue = null)
{
var variable = Expression.Variable(typeof(T), name);
result.Variables.Add(variable);

result.Body.Add(Expression.Assign(variable, defaultValue ?? Expression.Constant(default(T), typeof(T))));

return variable;
}

public static ParameterExpression DeclareValueVariable<T>(this CompilationContext context, CompilationResult result)
{
return DeclareValueVariable(context, result, Expression.Default(typeof(T)));
}

public static ParameterExpression DeclareValueVariable(this CompilationContext context, CompilationResult result, Expression defaultValue)
public static ParameterExpression DeclareValueVariable(this CompilationContext context, CompilationResult result, Expression defaultValue, Type variableType = null)
{
result.Value = Expression.Variable(defaultValue.Type, $"value{context.NextNumber}");
result.Value = Expression.Variable(variableType ?? defaultValue.Type, $"value{context.NextNumber}");

if (!context.DiscardResult)
{
Expand Down
6 changes: 3 additions & 3 deletions src/Parlot/Fluent/OneOrMany.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace Parlot.Fluent
{
public sealed class OneOrMany<T> : Parser<List<T>>, ICompilable, ISeekable
public sealed class OneOrMany<T> : Parser<IReadOnlyList<T>>, ICompilable, ISeekable
{
private readonly Parser<T> _parser;

Expand All @@ -28,7 +28,7 @@ public OneOrMany(Parser<T> parser)

public bool SkipWhitespace { get; }

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

Expand All @@ -51,7 +51,7 @@ public override bool Parse(ParseContext context, ref ParseResult<List<T>> result

} while (_parser.Parse(context, ref parsed));

result = new ParseResult<List<T>>(start, end, results);
result = new ParseResult<IReadOnlyList<T>>(start, end, results);
return true;
}

Expand Down
25 changes: 25 additions & 0 deletions src/Parlot/Fluent/Optional.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
namespace Parlot.Fluent
{
/// <summary>
/// Represents an optional result.
/// </summary>
/// <typeparam name="T">The type of the result.</typeparam>
public readonly struct OptionalResult<T>
{
public OptionalResult(bool hasValue, T value)
{
HasValue = hasValue;
Value = value;
}

/// <summary>
/// Whether the result has a value or not.
/// </summary>
public bool HasValue { get; }

/// <summary>
/// Gets the value of the result if any.
/// </summary>
public T Value { get; }
}
}
8 changes: 4 additions & 4 deletions src/Parlot/Fluent/Parsers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public static partial class Parsers
/// <summary>
/// Builds a parser that looks for zero or many times a parser separated by another one.
/// </summary>
public static Parser<List<T>> Separated<U, T>(Parser<U> separator, Parser<T> parser) => new Separated<U, T>(separator, parser);
public static Parser<IReadOnlyList<T>> Separated<U, T>(Parser<U> separator, Parser<T> parser) => new Separated<U, T>(separator, parser);

/// <summary>
/// Builds a parser that skips white spaces before another one.
Expand All @@ -29,17 +29,17 @@ public static partial class Parsers
/// <summary>
/// Builds a parser that looks for zero or one time the specified parser.
/// </summary>
public static Parser<T> ZeroOrOne<T>(Parser<T> parser) => new ZeroOrOne<T>(parser);
public static Parser<OptionalResult<T>> ZeroOrOne<T>(Parser<T> parser) => new ZeroOrOne<T>(parser);

/// <summary>
/// Builds a parser that looks for zero or many times the specified parser.
/// </summary>
public static Parser<List<T>> ZeroOrMany<T>(Parser<T> parser) => new ZeroOrMany<T>(parser);
public static Parser<IReadOnlyList<T>> ZeroOrMany<T>(Parser<T> parser) => new ZeroOrMany<T>(parser);

/// <summary>
/// Builds a parser that looks for one or many times the specified parser.
/// </summary>
public static Parser<List<T>> OneOrMany<T>(Parser<T> parser) => new OneOrMany<T>(parser);
public static Parser<IReadOnlyList<T>> OneOrMany<T>(Parser<T> parser) => new OneOrMany<T>(parser);

/// <summary>
/// Builds a parser that succeed when the specified parser fails to match.
Expand Down
31 changes: 23 additions & 8 deletions src/Parlot/Fluent/Separated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace Parlot.Fluent
{
public sealed class Separated<U, T> : Parser<List<T>>, ICompilable, ISeekable
public sealed class Separated<U, T> : Parser<IReadOnlyList<T>>, ICompilable, ISeekable
{
private readonly Parser<U> _separator;
private readonly Parser<T> _parser;
Expand All @@ -30,7 +30,7 @@ public Separated(Parser<U> separator, Parser<T> parser)

public bool SkipWhitespace { get; }

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

Expand Down Expand Up @@ -80,7 +80,7 @@ public override bool Parse(ParseContext context, ref ParseResult<List<T>> result
results.Add(parsed.Value);
}

result = new ParseResult<List<T>>(start, end.Offset, results);
result = new ParseResult<IReadOnlyList<T>>(start, end.Offset, results ?? (IReadOnlyList<T>)Array.Empty<T>());
return true;
}

Expand All @@ -89,11 +89,16 @@ public CompilationResult Compile(CompilationContext context)
var result = new CompilationResult();

var success = context.DeclareSuccessVariable(result, false);
var value = context.DeclareValueVariable(result, Expression.New(typeof(List<T>)));

var value = context.DeclareValueVariable(result, ExpressionHelper.ArrayEmpty<T>(), typeof(IReadOnlyList<T>));

var results = context.DeclareVariable<List<T>>(result, $"results{context.NextNumber}");

var end = context.DeclarePositionVariable(result);

// value = new List<T>();
// success = false;
//
// IReadonlyList<T> value = Array.Empty<T>();
// List<T> results = null;
//
// while (true)
// {
Expand All @@ -102,7 +107,8 @@ public CompilationResult Compile(CompilationContext context)
// if (parser1.Success)
// {
// success = true;
// value.Add(parse1.Value);
// if (results == null) results = new List<T>();
// results.Add(parse1.Value);
// end = currenPosition;
// }
// else
Expand Down Expand Up @@ -136,7 +142,16 @@ public CompilationResult Compile(CompilationContext context)
Expression.Block(
context.DiscardResult
? Expression.Empty()
: Expression.Call(value, typeof(List<T>).GetMethod("Add"), parserCompileResult.Value),
: Expression.Block(
Expression.IfThen(
Expression.Equal(results, Expression.Constant(null, typeof(List<T>))),
Expression.Block(
Expression.Assign(results, ExpressionHelper.New<List<T>>()),
Expression.Assign(value, results)
)
),
Expression.Call(results, typeof(List<T>).GetMethod("Add"), parserCompileResult.Value)
),
Expression.Assign(success, Expression.Constant(true)),
Expression.Assign(end, context.Position())
),
Expand Down
2 changes: 1 addition & 1 deletion src/Parlot/Fluent/Switch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public CompilationResult Compile(CompilationContext context)
[nextParser, parseResult],
Expression.Assign(nextParser, Expression.Invoke(Expression.Constant(_action), new[] { context.ParseContext, previousParserCompileResult.Value })),
Expression.IfThen(
Expression.NotEqual(Expression.Constant(null), nextParser),
Expression.NotEqual(Expression.Constant(null, typeof(Parser<U>)), nextParser),
Expression.Block(
Expression.Assign(success,
Expression.Call(
Expand Down
35 changes: 28 additions & 7 deletions src/Parlot/Fluent/ZeroOrMany.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace Parlot.Fluent
{
public sealed class ZeroOrMany<T> : Parser<List<T>>, ICompilable, ISeekable
public sealed class ZeroOrMany<T> : Parser<IReadOnlyList<T>>, ICompilable, ISeekable
{
private readonly Parser<T> _parser;

Expand All @@ -28,11 +28,11 @@ public ZeroOrMany(Parser<T> parser)

public bool SkipWhitespace { get; }

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

var results = new List<T>();
List<T> results = null;

var start = 0;
var end = 0;
Expand All @@ -52,10 +52,12 @@ public override bool Parse(ParseContext context, ref ParseResult<List<T>> result
}

end = parsed.End;

results ??= [];
results.Add(parsed.Value);
}

result = new ParseResult<List<T>>(start, end, results);
result = new ParseResult<IReadOnlyList<T>>(start, end, results ?? (IReadOnlyList<T>)Array.Empty<T>());
return true;
}

Expand All @@ -64,18 +66,28 @@ public CompilationResult Compile(CompilationContext context)
var result = new CompilationResult();

var _ = context.DeclareSuccessVariable(result, true);
var value = context.DeclareValueVariable(result, Expression.New(typeof(List<T>)));
var value = context.DeclareValueVariable(result, ExpressionHelper.ArrayEmpty<T>(), typeof(IReadOnlyList<T>));

var results = context.DeclareVariable<List<T>>(result, $"results{context.NextNumber}");

// value = new List<T>();
// success = true;
//
// IReadonlyList<T> value = Array.Empty<T>();
// List<T> results = null;
//
// while (true)
// {
//
// parse1 instructions
//
// if (parser1.Success)
// {
// if (results == null)
// {
// results = new List<T>();
// value = results;
// }
//
// results.Add(parse1.Value);
// }
// else
Expand All @@ -102,7 +114,16 @@ public CompilationResult Compile(CompilationContext context)
parserCompileResult.Success,
context.DiscardResult
? Expression.Empty()
: Expression.Call(value, typeof(List<T>).GetMethod("Add"), parserCompileResult.Value),
: Expression.Block(
Expression.IfThen(
Expression.Equal(results, Expression.Constant(null, typeof(List<T>))),
Expression.Block(
Expression.Assign(results, ExpressionHelper.New<List<T>>()),
Expression.Assign(value, results)
)
),
Expression.Call(results, typeof(List<T>).GetMethod("Add"), parserCompileResult.Value)
),
Expression.Break(breakLabel)
),
Expression.IfThen(
Expand Down
27 changes: 13 additions & 14 deletions src/Parlot/Fluent/ZeroOrOne.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

namespace Parlot.Fluent
{
public sealed class ZeroOrOne<T> : Parser<T>, ICompilable, ISeekable
public sealed class ZeroOrOne<T> : Parser<OptionalResult<T>>, ICompilable, ISeekable
{
private readonly Parser<T> _parser;

Expand All @@ -27,12 +27,17 @@ public ZeroOrOne(Parser<T> parser)

public bool SkipWhitespace { get; }

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

_parser.Parse(context, ref result);
var parsed = new ParseResult<T>();

var success = _parser.Parse(context, ref parsed);

result.Set(parsed.Start, parsed.End, new OptionalResult<T>(success, parsed.Value));

// ZeroOrOne always succeeds
return true;
}

Expand All @@ -41,30 +46,24 @@ public CompilationResult Compile(CompilationContext context)
var result = new CompilationResult();

var success = context.DeclareSuccessVariable(result, true);
var value = context.DeclareValueVariable(result, Expression.Default(typeof(T)));
var value = context.DeclareValueVariable(result, Expression.Default(typeof(OptionalResult<T>)));

// T value;
//
// parse1 instructions
//
// if (parser1.Success)
// {
// value parse1.Value;
// }
//
// value = new OptionalResult<T>(parser1.Success, parse1.Value);
//

var parserCompileResult = _parser.Build(context);

var block = Expression.Block(
parserCompileResult.Variables,
Expression.Block(
Expression.Block(parserCompileResult.Body),
Expression.IfThen(
parserCompileResult.Success,
context.DiscardResult
context.DiscardResult
? Expression.Empty()
: Expression.Assign(value, parserCompileResult.Value)
)
: Expression.Assign(value, context.NewOptionalResult<T>(parserCompileResult.Success, parserCompileResult.Value))
)
);

Expand Down
4 changes: 2 additions & 2 deletions src/Samples/Json/JsonModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ public interface IJson

public class JsonArray : IJson
{
public IJson[] Elements { get; }
public JsonArray(IJson[] elements)
public IReadOnlyList<IJson> Elements { get; }
public JsonArray(IReadOnlyList<IJson> elements)
{
Elements = elements;
}
Expand Down
2 changes: 1 addition & 1 deletion src/Samples/Json/JsonParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ static JsonParser()

var jsonArray =
Between(LBracket, Separated(Comma, json), RBracket)
.Then<IJson>(static els => new JsonArray(els.ToArray()));
.Then<IJson>(static els => new JsonArray(els));

var jsonMember =
String.And(Colon).And(json)
Expand Down
Loading

0 comments on commit ab8826e

Please sign in to comment.