diff --git a/Pidgin.Tests/ApiSugarParserTests.cs b/Pidgin.Tests/ApiSugarParserTests.cs new file mode 100644 index 00000000..282b9b40 --- /dev/null +++ b/Pidgin.Tests/ApiSugarParserTests.cs @@ -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 semicolumn = ';'; + Parser> terminator = semicolumn + Char('\r') + Char('\n'); + Assert.Equal(";\r\n", terminator.ParseOrThrow(";\r\n")); + + (!semicolumn).ParseOrThrow(","); // Assert no error + + Parser expr = null!; + //Parser parenthesized = '(' > Rec(() => expr) < ')'; // Cause stack overflow in runtime + Parser 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("(((+)))")); + } + } +} diff --git a/Pidgin/Parser.Operators.cs b/Pidgin/Parser.Operators.cs new file mode 100644 index 00000000..6203aa52 --- /dev/null +++ b/Pidgin/Parser.Operators.cs @@ -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 + { + /// + /// 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. + /// + /// The parser to apply first + /// The alternative parser to apply if parser fails without consuming any input + /// A parser which tries to apply , and then applies if fails without consuming any input. + public static Parser operator |(Parser x, Parser y) + { + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + return x.Or(y); + } + + /// + /// 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. + /// + public static Parser operator >(Parser x, Parser y) + { + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + return x.Then(y); + } + + /// + /// 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. + /// + public static Parser operator <(Parser x, Parser y) + { + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + return x.Before(y); + } + + public static Parser operator <(Parser parser, TToken token) + { + if (parser == null) + { + throw new ArgumentNullException(nameof(parser)); + } + var tokenParser = Parser.Token(token); + return parser.Before(tokenParser); + } + + public static Parser operator >(Parser parser, TToken token) + { + if (typeof(T) == typeof(TToken)) + { + var tokenParser = Parser.Token(token); + return (Parser)(object)parser.Then(tokenParser); + } + throw new NotSupportedException("[TBD] Supported only if `T == TToken`"); + } + + public static Parser operator <(TToken token, Parser parser) + { + return parser > token; + } + + public static Parser operator >(TToken token, Parser parser) + { + return parser < token; + } + + public static Parser> operator +(Parser x, Parser y) + { + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + return Parser.Sequence(x, y); + } + + public static Parser> operator +(Parser> sequence, Parser newElement) + { + if (sequence == null) + { + throw new ArgumentNullException(nameof(sequence)); + } + if (newElement == null) + { + throw new ArgumentNullException(nameof(newElement)); + } + if (sequence is SequenceParser seqParser) + { + return new SequenceParser(seqParser.Parsers.Append(newElement).ToArray()); + } + throw new NotSupportedException("TODO"); + } + + public static Parser operator !(Parser parser) + { + return Parser.Not(parser); + } + } +} diff --git a/Pidgin/Parser.Sequence.cs b/Pidgin/Parser.Sequence.cs index 7840d114..28641d8e 100644 --- a/Pidgin/Parser.Sequence.cs +++ b/Pidgin/Parser.Sequence.cs @@ -77,6 +77,7 @@ public static Parser> Sequence(IEnumerable : Parser> { private readonly Parser[] _parsers; + internal IReadOnlyCollection> Parsers => _parsers; public SequenceParser(Parser[] parsers) { diff --git a/Pidgin/Parser.Token.cs b/Pidgin/Parser.Token.cs index 266b8027..8f004d47 100644 --- a/Pidgin/Parser.Token.cs +++ b/Pidgin/Parser.Token.cs @@ -73,6 +73,9 @@ public sealed override bool TryParse(ref ParseState state, ref PooledLis result = token; return true; } + + public static implicit operator TokenParser(TToken token) + => new TokenParser(token); } internal sealed class PredicateTokenParser : Parser