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 operator overloads for parser actions (#70) #114

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
31 changes: 31 additions & 0 deletions Pidgin.Tests/ApiSugarParserTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System.Collections.Generic;
using Xunit;
using static Pidgin.Parser;

namespace Pidgin.Tests
{
public class ApiSugarParserTests : ParserTestBase
{
[Fact]
public void TestOperatorOverloads()
{
TokenParser<char> semicolumn = ';';
Parser<char, IEnumerable<char>> terminator = semicolumn + Char('\r') + Char('\n');
Assert.Equal(";\r\n", terminator.ParseOrThrow(";\r\n"));

(!semicolumn).ParseOrThrow(","); // Assert no error

Parser<char, char> expr = null!;
//Parser<char, char> parenthesized = '(' > Rec(() => expr) < ')'; // Cause stack overflow in runtime
Parser<char, char> parenthesized = Char('(') > Rec(() => expr) < ')';
expr = Digit
| parenthesized
| Char('+');

Assert.Equal('1', expr.ParseOrThrow("1"));
Assert.Equal('1', expr.ParseOrThrow("(1)"));
Assert.Equal('1', expr.ParseOrThrow("(((1)))"));
Assert.Equal('+', expr.ParseOrThrow("(((+)))"));
}
}
}
130 changes: 130 additions & 0 deletions Pidgin/Parser.Operators.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member

using System;
using System.Collections.Generic;
using System.Linq;

namespace Pidgin
{
public abstract partial class Parser<TToken, T>
{
/// <summary>
/// Creates a parser which tries to apply a first specified parser, applying a second specified parser if the first one fails without consuming any input.
/// The resulting parser fails if both the first parser and the second parser fail, or if the first parser fails after consuming input.
/// </summary>
/// <param name="x">The parser to apply first</param>
/// <param name="y">The alternative parser to apply if <paramref name="x"/> parser fails without consuming any input</param>
/// <returns>A parser which tries to apply <paramref name="x"/>, and then applies <paramref name="y"/> if <paramref name="x"/> fails without consuming any input.</returns>
public static Parser<TToken, T> operator |(Parser<TToken, T> x, Parser<TToken, T> y)
{
if (x == null)
{
throw new ArgumentNullException(nameof(x));
}
if (y == null)
{
throw new ArgumentNullException(nameof(y));
}
return x.Or(y);
}

/// <summary>
/// Creates a parser which applies specified parsers in left-to-right order.
/// The resulting parser returns the result of the second parser, ignoring the result of the first one.
/// </summary>
public static Parser<TToken, T> operator >(Parser<TToken, T> x, Parser<TToken, T> y)
{
if (x == null)
{
throw new ArgumentNullException(nameof(x));
}
if (y == null)
{
throw new ArgumentNullException(nameof(y));
}
return x.Then(y);
}

/// <summary>
/// Creates a parser which applies specified parsers in left-to-right order.
/// The resulting parser returns the result of the first parser, ignoring the result of the second one.
/// </summary>
public static Parser<TToken, T> operator <(Parser<TToken, T> x, Parser<TToken, T> y)
{
if (x == null)
{
throw new ArgumentNullException(nameof(x));
}
if (y == null)
{
throw new ArgumentNullException(nameof(y));
}
return x.Before(y);
}

public static Parser<TToken, T> operator <(Parser<TToken, T> parser, TToken token)
{
if (parser == null)
{
throw new ArgumentNullException(nameof(parser));
}
var tokenParser = Parser<TToken>.Token(token);
return parser.Before(tokenParser);
}

public static Parser<TToken, T> operator >(Parser<TToken, T> parser, TToken token)
{
if (typeof(T) == typeof(TToken))
{
var tokenParser = Parser<TToken>.Token(token);
return (Parser<TToken, T>)(object)parser.Then(tokenParser);
}
throw new NotSupportedException("[TBD] Supported only if `T == TToken`");
}

public static Parser<TToken, T> operator <(TToken token, Parser<TToken, T> parser)
{
return parser > token;
}

public static Parser<TToken, T> operator >(TToken token, Parser<TToken, T> parser)
{
return parser < token;
}

public static Parser<TToken, IEnumerable<T>> operator +(Parser<TToken, T> x, Parser<TToken, T> y)
{
if (x == null)
{
throw new ArgumentNullException(nameof(x));
}
if (y == null)
{
throw new ArgumentNullException(nameof(y));
}
return Parser<TToken>.Sequence(x, y);
}

public static Parser<TToken, IEnumerable<T>> operator +(Parser<TToken, IEnumerable<T>> sequence, Parser<TToken, T> newElement)
{
if (sequence == null)
{
throw new ArgumentNullException(nameof(sequence));
}
if (newElement == null)
{
throw new ArgumentNullException(nameof(newElement));
}
if (sequence is SequenceParser<TToken, T> seqParser)
{
return new SequenceParser<TToken, T>(seqParser.Parsers.Append(newElement).ToArray());
}
throw new NotSupportedException("TODO");
}

public static Parser<TToken, Unit> operator !(Parser<TToken, T> parser)
{
return Parser.Not(parser);
}
}
}
1 change: 1 addition & 0 deletions Pidgin/Parser.Sequence.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ public static Parser<TToken, IEnumerable<T>> Sequence<T>(IEnumerable<Parser<TTok
internal sealed class SequenceParser<TToken, T> : Parser<TToken, IEnumerable<T>>
{
private readonly Parser<TToken, T>[] _parsers;
internal IReadOnlyCollection<Parser<TToken, T>> Parsers => _parsers;

public SequenceParser(Parser<TToken, T>[] parsers)
{
Expand Down
3 changes: 3 additions & 0 deletions Pidgin/Parser.Token.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ public sealed override bool TryParse(ref ParseState<TToken> state, ref PooledLis
result = token;
return true;
}

public static implicit operator TokenParser<TToken>(TToken token)
=> new TokenParser<TToken>(token);
}

internal sealed class PredicateTokenParser<TToken> : Parser<TToken, TToken>
Expand Down