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

Add support for optional results #103

Merged
merged 5 commits into from
May 9, 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
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