Skip to content

Commit

Permalink
Add Else and ThenElse implementation to replace Optional (#113)
Browse files Browse the repository at this point in the history
Fixes #59
  • Loading branch information
sebastienros authored May 21, 2024
1 parent a3b3276 commit bc9680c
Show file tree
Hide file tree
Showing 12 changed files with 229 additions and 66 deletions.
64 changes: 60 additions & 4 deletions docs/parsers.md
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,62 @@ var parser = OneOf(
);
```

### Else

Returns a value if the previous parser failed.

Usage:

```c#
var parser = Terms.Integer().Else<string>(0).And(Terms.Text("years"));

capture.Parse("years");
capture.Parse("123 years");
```

Result:

```
(0, "years")
(123, "years")
```

### ThenElse

Converts the result of a parser, or returns a value if it didn't succeed. This parser always succeeds.

NB: It is implemented using `Then()` and `Else()` parsers.

```c#
Parser<U> ThenElse<U>(Func<T, U> conversion, U elseValue)
Parser<U> ThenElse<U>(Func<ParseContext, T, U> conversion, U elseValue)
Parser<U> ThenElse<U>(U value, U elseValue)
```

Usage:

```c#
var parser =
Terms.Integer().ThenElse<long?>(x => x, null)

parser.Parse("abc");
```

Result:

```
(long?)null
```

When the previous results or the `ParseContext` are not used then the version without delegates can be used:

```c#
var parser = OneOf(
Terms.Text("not").Then(UnaryOperator.Not),
Terms.Text("-").Then(UnaryOperator.Negate)
);
```

### ElseError

Fails parsing with a custom error message when the inner parser didn't match.
Expand Down Expand Up @@ -723,14 +779,14 @@ Returns any characters until the specified parser is matched.
Parser<TextSpan> AnyCharBefore<T>(Parser<T> parser, bool canBeEmpty = false, bool failOnEof = false, bool consumeDelimiter = false)
```

### Empty
### Always

Always returns successfully, with an optional return type or value.

```c#
Parser<T> Empty<T>()
Parser<object> Empty()
Parser<T> Empty<T>(T value)
Parser<T> Always<T>()
Parser<object> Always()
Parser<T> Always<T>(T value)
```

### OneOf
Expand Down
4 changes: 2 additions & 2 deletions src/Parlot/Compilation/ExpressionHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +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)]);
//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 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
6 changes: 3 additions & 3 deletions src/Parlot/Fluent/Empty.cs → src/Parlot/Fluent/Always.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@ namespace Parlot.Fluent
/// <summary>
/// Doesn't parse anything and return the default value.
/// </summary>
public sealed class Empty<T> : Parser<T>, ICompilable
public sealed class Always<T> : Parser<T>, ICompilable
{
private readonly T _value;

public Empty()
public Always()
{
_value = default;
}

public Empty(T value)
public Always(T value)
{
_value = value;
}
Expand Down
2 changes: 1 addition & 1 deletion src/Parlot/Fluent/Discard.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public sealed class Discard<T, U> : Parser<U>, ICompilable

public Discard(Parser<T> parser)
{
_value = default(U);
_value = default;
_parser = parser;
}

Expand Down
71 changes: 71 additions & 0 deletions src/Parlot/Fluent/Else.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using Parlot.Compilation;
using System.Linq.Expressions;

namespace Parlot.Fluent
{
/// <summary>
/// Returns a default value if the previous parser failed.
/// </summary>
public sealed class Else<T> : Parser<T>, ICompilable
{
private readonly Parser<T> _parser;
private readonly T _value;

public Else(Parser<T> parser, T value)
{
_parser = parser;
_value = value;
}

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

if (!_parser.Parse(context, ref result))
{
result.Set(result.Start, result.End, _value);
}

return true;
}

public CompilationResult Compile(CompilationContext context)
{
var result = new CompilationResult();

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

var parserCompileResult = _parser.Build(context);

// success = true;
//
// parser instructions
//
// if (parser.success)
// {
// value = parser.Value
// }
// else
// {
// value = defaultValue
// }

result.Body.Add(
Expression.Block(
parserCompileResult.Variables,
Expression.Block(parserCompileResult.Body),
context.DiscardResult
? Expression.Empty()
: Expression.IfThenElse(
parserCompileResult.Success,
Expression.Assign(value, parserCompileResult.Value),
Expression.Assign(value, Expression.Constant(_value, typeof(T)))
)
)
);

return result;
}
}
}
25 changes: 0 additions & 25 deletions src/Parlot/Fluent/Optional.cs

This file was deleted.

22 changes: 21 additions & 1 deletion src/Parlot/Fluent/Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,25 @@ public abstract partial class Parser<T>
public Parser<U> Then<U>(Func<ParseContext, T, U> conversion) => new Then<T, U>(this, conversion);

/// <summary>
/// Builds a parser that converts the previous result, and can alter the current <see cref="ParseContext"/>.
/// Builds a parser that converts the previous result.
/// </summary>
public Parser<U> Then<U>(U value) => new Then<T, U>(this, value);

/// <summary>
/// Builds a parser that converts the previous result when it succeeds or returns a default value if it fails.
/// </summary>
public Parser<U> ThenElse<U>(Func<T, U> conversion, U elseValue) => new Then<T, U>(this, conversion).Else(elseValue);

/// <summary>
/// Builds a parser that converts the previous result or returns a default value if it fails, and can alter the current <see cref="ParseContext"/>.
/// </summary>
public Parser<U> ThenElse<U>(Func<ParseContext, T, U> conversion, U elseValue) => new Then<T, U>(this, conversion).Else(elseValue);

/// <summary>
/// Builds a parser that converts the previous result or returns a default value if it fails.
/// </summary>
public Parser<U> ThenElse<U>(U value, U elseValue) => new Then<T, U>(this, value).Else(elseValue);

/// <summary>
/// Builds a parser that emits an error when the previous parser failed.
/// </summary>
Expand Down Expand Up @@ -60,5 +75,10 @@ public abstract partial class Parser<T>
/// Builds a parser that discards the previous result and replaces it by the specified type or value.
/// </summary>
public Parser<U> Discard<U>(U value) => new Discard<T, U>(this, value);

/// <summary>
/// Builds a parser that returns a default value if the previous parser fails.
/// </summary>
public Parser<T> Else(T value) => new Else<T>(this, value);
}
}
10 changes: 5 additions & 5 deletions src/Parlot/Fluent/Parsers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public static partial class Parsers
/// <summary>
/// Builds a parser that looks for zero or one time the specified parser.
/// </summary>
public static Parser<OptionalResult<T>> ZeroOrOne<T>(Parser<T> parser) => new ZeroOrOne<T>(parser);
public static Parser<T> ZeroOrOne<T>(Parser<T> parser, T defaultValue = default) => new ZeroOrOne<T>(parser);

/// <summary>
/// Builds a parser that looks for zero or many times the specified parser.
Expand All @@ -42,7 +42,7 @@ public static partial class Parsers
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.
/// Builds a parser that succeeds when the specified parser fails to match.
/// </summary>
public static Parser<T> Not<T>(Parser<T> parser) => new Not<T>(parser);

Expand Down Expand Up @@ -75,17 +75,17 @@ public static partial class Parsers
/// <summary>
/// Builds a parser that always succeeds.
/// </summary>
public static Parser<T> Empty<T>() => new Empty<T>();
public static Parser<T> Always<T>() => new Always<T>();

/// <summary>
/// Builds a parser that always succeeds.
/// </summary>
public static Parser<object> Empty() => new Empty<object>();
public static Parser<object> Always() => new Always<object>();

/// <summary>
/// Builds a parser that always succeeds.
/// </summary>
public static Parser<T> Empty<T>(T value) => new Empty<T>(value);
public static Parser<T> Always<T>(T value) => new Always<T>(value);

}

Expand Down
4 changes: 2 additions & 2 deletions src/Parlot/Fluent/Then.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public sealed class Then<T, U> : Parser<U>, ICompilable, ISeekable
private readonly U _value = default;
private readonly Parser<T> _parser;

public Then(Parser<T> parser)
private Then(Parser<T> parser)
{
_parser = parser ?? throw new ArgumentNullException(nameof(parser));

Expand Down Expand Up @@ -108,7 +108,7 @@ public CompilationResult Compile(CompilationContext context)
}
else
{
transformation = Expression.Constant(_value);
transformation = Expression.Constant(_value, typeof(U));
}

var block = Expression.Block(
Expand Down
21 changes: 13 additions & 8 deletions src/Parlot/Fluent/ZeroOrOne.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@

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

public ZeroOrOne(Parser<T> parser)
public ZeroOrOne(Parser<T> parser, T defaultValue = default)
{
_parser = parser ?? throw new ArgumentNullException(nameof(parser));

_defaultValue = defaultValue;
if (_parser is ISeekable seekable)
{
CanSeek = seekable.CanSeek;
Expand All @@ -27,15 +28,15 @@ public ZeroOrOne(Parser<T> parser)

public bool SkipWhitespace { get; }

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

var parsed = new ParseResult<T>();

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

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

// ZeroOrOne always succeeds
return true;
Expand All @@ -46,9 +47,9 @@ public CompilationResult Compile(CompilationContext context)
var result = new CompilationResult();

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

// T value;
// T value = _defaultValue;
//
// parse1 instructions
//
Expand All @@ -63,7 +64,11 @@ public CompilationResult Compile(CompilationContext context)
Expression.Block(parserCompileResult.Body),
context.DiscardResult
? Expression.Empty()
: Expression.Assign(value, context.NewOptionalResult<T>(parserCompileResult.Success, parserCompileResult.Value))
: Expression.IfThenElse(
parserCompileResult.Success,
Expression.Assign(value, parserCompileResult.Value),
Expression.Assign(value, Expression.Default(typeof(T)))
)
)
);

Expand Down
Loading

0 comments on commit bc9680c

Please sign in to comment.