diff --git a/src/Superpower/Parse.cs b/src/Superpower/Parse.cs index 2ecc48d..493f89f 100644 --- a/src/Superpower/Parse.cs +++ b/src/Superpower/Parse.cs @@ -592,5 +592,53 @@ public static TokenListParser Return(T value) return Result.Value((rt.Value, ru.Value, rv.Value, rw.Value, rx.Value), input, rx.Remainder); }; } + /// + /// Creates a parser which applies one of the specified parsers. + /// + /// The kind of the tokens being parsed. + /// The type of value being parsed. + /// The parsers to try from left to right. + /// A parser which applies one of the specified parsers. + public static TokenListParser OneOf(params TokenListParser[] parsers) + { + if (parsers == null) throw new ArgumentNullException(nameof(parsers)); + + if (parsers.Length == 0) + { + return i => TokenListParserResult.Empty(TokenList.Empty); + } + + TokenListParser c = parsers[0]; + for (int i = 1; i < parsers.Length; i++) + { + c = c.Or(parsers[i]); + } + + return c; + } + + /// + /// Creates a parser which applies one of the specified parsers. + /// + /// The type of value being parsed. + /// The parser to try from left to right. + /// A parser which applies one of the specified parsers. + public static TextParser OneOf(params TextParser[] parsers) + { + if (parsers == null) throw new ArgumentNullException(nameof(parsers)); + + if (parsers.Length == 0) + { + return i => Result.Empty(TextSpan.None); + } + + TextParser c = parsers[0]; + for (int i = 1; i < parsers.Length; i++) + { + c = c.Or(parsers[i]); + } + + return c; + } } } diff --git a/test/Superpower.Tests/Combinators/OneOfCombinatorTests.cs b/test/Superpower.Tests/Combinators/OneOfCombinatorTests.cs new file mode 100644 index 0000000..b86a0d7 --- /dev/null +++ b/test/Superpower.Tests/Combinators/OneOfCombinatorTests.cs @@ -0,0 +1,37 @@ +using Superpower.Model; +using Superpower.Parsers; +using Superpower.Tests.Support; +using System.Linq; +using Xunit; + +namespace Superpower.Tests.Combinators +{ + public class OneOfCombinatorTests + { + [Fact] + public void OneOf2Succeeds() + { + var p = Parse.OneOf(Character.Digit, Character.AnyChar); + AssertParser.SucceedsWithAll(Span.MatchedBy(p), "1"); + AssertParser.SucceedsWithAll(Span.MatchedBy(p), "w"); + } + + [Fact] + public void OneOf2TokenSucceeds() + { + var p = Parse.OneOf(Token.EqualTo('1'), Token.EqualTo('w')); + AssertParser.SucceedsWith(p, "1", '1'); + AssertParser.SucceedsWith(p, "w", 'w'); + } + + [Fact] + public void OneOfReportsCorrectError() + { + var names = new[] { "one", "two" }; + TextParser p = Parse.OneOf(names.Select(Span.EqualTo).ToArray()); + AssertParser.SucceedsWith(p.Select(t => t.ToStringValue()), "one", "one"); + AssertParser.SucceedsWith(p.Select(t => t.ToStringValue()), "two", "two"); + AssertParser.FailsWithMessage(p.Select(t => t.ToStringValue()), "four", "Syntax error (line 1, column 1): unexpected `f`, expected `one` or `two`."); + } + } +}