diff --git a/src/Parlot/Fluent/OneOf.cs b/src/Parlot/Fluent/OneOf.cs index 5fa7edb..8e61ed2 100644 --- a/src/Parlot/Fluent/OneOf.cs +++ b/src/Parlot/Fluent/OneOf.cs @@ -13,16 +13,21 @@ namespace Parlot.Fluent /// We then return the actual result of each parser. /// /// - public sealed class OneOf : Parser, ICompilable + public sealed class OneOf : Parser, ICompilable, ISeekable { internal readonly Parser[] _parsers; internal readonly FrozenDictionary>> _lookupTable; - internal readonly bool _skipWhiteSpace; public OneOf(Parser[] parsers) { _parsers = parsers ?? throw new ArgumentNullException(nameof(parsers)); + // We can't build a lookup table if there is only one parser + if (_parsers.Length <= 1) + { + return; + } + // If all parsers are seekable we can build a lookup table if (_parsers.All(x => x is ISeekable seekable && seekable.CanSeek)) { @@ -53,7 +58,7 @@ public OneOf(Parser[] parsers) else if (_parsers.All(x => x is ISeekable seekable && seekable.SkipWhitespace)) { // All parsers can start with white spaces - _skipWhiteSpace = true; + SkipWhitespace = true; } else if (_parsers.Any(x => x is ISeekable seekable && seekable.SkipWhitespace)) { @@ -65,10 +70,19 @@ public OneOf(Parser[] parsers) if (lookupTable != null) { _lookupTable = lookupTable.ToFrozenDictionary(); + + CanSeek = true; + ExpectedChars = _lookupTable.Keys.ToArray(); } } } + public bool CanSeek { get; } + + public char[] ExpectedChars { get; } = []; + + public bool SkipWhitespace { get; } + public Parser[] Parsers => _parsers; public override bool Parse(ParseContext context, ref ParseResult result) @@ -79,7 +93,7 @@ public override bool Parse(ParseContext context, ref ParseResult result) if (_lookupTable != null) { - if (_skipWhiteSpace) + if (SkipWhitespace) { var start = context.Scanner.Cursor.Position; @@ -201,7 +215,7 @@ public CompilationResult Compile(CompilationContext context) cases ); - if (_skipWhiteSpace) + if (SkipWhitespace) { var start = context.DeclarePositionVariable(result); diff --git a/src/Parlot/Fluent/Parsers.And.cs b/src/Parlot/Fluent/Parsers.And.cs index 7bc98b2..5b162d4 100644 --- a/src/Parlot/Fluent/Parsers.And.cs +++ b/src/Parlot/Fluent/Parsers.And.cs @@ -1,37 +1,45 @@ -using System; - -namespace Parlot.Fluent +namespace Parlot.Fluent { public static partial class Parsers { /// /// Builds a parser that ensure the specified parsers match consecutively. /// - public static Parser> And(this Parser parser, Parser and) => new Sequence(parser, and); + public static Sequence And(this Parser parser, Parser and) => new Sequence(parser, and); /// /// Builds a parser that ensure the specified parsers match consecutively. /// - public static Parser> And(this Parser> parser, Parser and) => new Sequence(parser, and); + public static Sequence And(this Sequence parser, Parser and) => new Sequence(parser, and); + public static Sequence And(this SequenceAndSkip parser, Parser and) => new Sequence(parser, and); + public static Sequence And(this SequenceSkipAnd parser, Parser and) => new Sequence(parser, and); /// /// Builds a parser that ensure the specified parsers match consecutively. /// - public static Parser> And(this Parser> parser, Parser and) => new Sequence(parser, and); + public static Sequence And(this Sequence parser, Parser and) => new Sequence(parser, and); + public static Sequence And(this SequenceAndSkip parser, Parser and) => new Sequence(parser, and); + public static Sequence And(this SequenceSkipAnd parser, Parser and) => new Sequence(parser, and); /// /// Builds a parser that ensure the specified parsers match consecutively. /// - public static Parser> And(this Parser> parser, Parser and) => new Sequence(parser, and); + public static Sequence And(this Sequence parser, Parser and) => new Sequence(parser, and); + public static Sequence And(this SequenceAndSkip parser, Parser and) => new Sequence(parser, and); + public static Sequence And(this SequenceSkipAnd parser, Parser and) => new Sequence(parser, and); /// /// Builds a parser that ensure the specified parsers match consecutively. /// - public static Parser> And(this Parser> parser, Parser and) => new Sequence(parser, and); + public static Sequence And(this Sequence parser, Parser and) => new Sequence(parser, and); + public static Sequence And(this SequenceAndSkip parser, Parser and) => new Sequence(parser, and); + public static Sequence And(this SequenceSkipAnd parser, Parser and) => new Sequence(parser, and); /// /// Builds a parser that ensure the specified parsers match consecutively. /// - public static Parser> And(this Parser> parser, Parser and) => new Sequence(parser, and); + public static Sequence And(this Sequence parser, Parser and) => new Sequence(parser, and); + public static Sequence And(this SequenceAndSkip parser, Parser and) => new Sequence(parser, and); + public static Sequence And(this SequenceSkipAnd parser, Parser and) => new Sequence(parser, and); } } diff --git a/src/Parlot/Fluent/Parsers.AndSkip.cs b/src/Parlot/Fluent/Parsers.AndSkip.cs index e35f2b4..4543e25 100644 --- a/src/Parlot/Fluent/Parsers.AndSkip.cs +++ b/src/Parlot/Fluent/Parsers.AndSkip.cs @@ -7,36 +7,48 @@ public static partial class Parsers /// /// Builds a parser that ensure the specified parsers match consecutively. The last parser's result is then ignored. /// - public static Parser AndSkip(this Parser parser, Parser and) => new SequenceAndSkip(parser, and); + public static SequenceAndSkip AndSkip(this Parser parser, Parser and) => new SequenceAndSkip(parser, and); /// /// Builds a parser that ensure the specified parsers match consecutively. The last parser's result is then ignored. /// - public static Parser> AndSkip(this Parser> parser, Parser and) => new SequenceAndSkip(parser, and); + public static SequenceAndSkip AndSkip(this Sequence parser, Parser and) => new SequenceAndSkip(parser, and); + public static SequenceAndSkip AndSkip(this SequenceAndSkip parser, Parser and) => new SequenceAndSkip(parser, and); + public static SequenceAndSkip AndSkip(this SequenceSkipAnd parser, Parser and) => new SequenceAndSkip(parser, and); /// /// Builds a parser that ensure the specified parsers match consecutively. The last parser's result is then ignored. /// - public static Parser> AndSkip(this Parser> parser, Parser and) => new SequenceAndSkip(parser, and); + public static SequenceAndSkip AndSkip(this Sequence parser, Parser and) => new SequenceAndSkip(parser, and); + public static SequenceAndSkip AndSkip(this SequenceAndSkip parser, Parser and) => new SequenceAndSkip(parser, and); + public static SequenceAndSkip AndSkip(this SequenceSkipAnd parser, Parser and) => new SequenceAndSkip(parser, and); /// /// Builds a parser that ensure the specified parsers match consecutively. The last parser's result is then ignored. /// - public static Parser> AndSkip(this Parser> parser, Parser and) => new SequenceAndSkip(parser, and); + public static SequenceAndSkip AndSkip(this Sequence parser, Parser and) => new SequenceAndSkip(parser, and); + public static SequenceAndSkip AndSkip(this SequenceAndSkip parser, Parser and) => new SequenceAndSkip(parser, and); + public static SequenceAndSkip AndSkip(this SequenceSkipAnd parser, Parser and) => new SequenceAndSkip(parser, and); /// /// Builds a parser that ensure the specified parsers match consecutively. The last parser's result is then ignored. /// - public static Parser> AndSkip(this Parser> parser, Parser and) => new SequenceAndSkip(parser, and); + public static SequenceAndSkip AndSkip(this Sequence parser, Parser and) => new SequenceAndSkip(parser, and); + public static SequenceAndSkip AndSkip(this SequenceAndSkip parser, Parser and) => new SequenceAndSkip(parser, and); + public static SequenceAndSkip AndSkip(this SequenceSkipAnd parser, Parser and) => new SequenceAndSkip(parser, and); /// /// Builds a parser that ensure the specified parsers match consecutively. The last parser's result is then ignored. /// - public static Parser> AndSkip(this Parser> parser, Parser and) => new SequenceAndSkip(parser, and); + public static SequenceAndSkip AndSkip(this Sequence parser, Parser and) => new SequenceAndSkip(parser, and); + public static SequenceAndSkip AndSkip(this SequenceAndSkip parser, Parser and) => new SequenceAndSkip(parser, and); + public static SequenceAndSkip AndSkip(this SequenceSkipAnd parser, Parser and) => new SequenceAndSkip(parser, and); /// /// Builds a parser that ensure the specified parsers match consecutively. The last parser's result is then ignored. /// - public static Parser> AndSkip(this Parser> parser, Parser and) => new SequenceAndSkip(parser, and); + public static SequenceAndSkip AndSkip(this Sequence parser, Parser and) => new SequenceAndSkip(parser, and); + public static SequenceAndSkip AndSkip(this SequenceAndSkip parser, Parser and) => new SequenceAndSkip(parser, and); + public static SequenceAndSkip AndSkip(this SequenceSkipAnd parser, Parser and) => new SequenceAndSkip(parser, and); } } diff --git a/src/Parlot/Fluent/Parsers.SkipAnd.cs b/src/Parlot/Fluent/Parsers.SkipAnd.cs index e5358bb..071e52b 100644 --- a/src/Parlot/Fluent/Parsers.SkipAnd.cs +++ b/src/Parlot/Fluent/Parsers.SkipAnd.cs @@ -1,6 +1,4 @@ -using System; - -namespace Parlot.Fluent +namespace Parlot.Fluent { public static partial class Parsers { @@ -8,36 +6,46 @@ public static partial class Parsers /// /// Builds a parser that ensure the specified parsers match consecutively. The last parser's result is then ignored. /// - public static Parser SkipAnd(this Parser parser, Parser and) => new SequenceSkipAnd(parser, and); + public static SequenceSkipAnd SkipAnd(this Parser parser, Parser and) => new SequenceSkipAnd(parser, and); /// /// Builds a parser that ensure the specified parsers match consecutively. The last parser's result is then ignored. /// - public static Parser> SkipAnd(this Parser> parser, Parser and) => new SequenceSkipAnd(parser, and); + public static SequenceSkipAnd SkipAnd(this Sequence parser, Parser and) => new SequenceSkipAnd(parser, and); + public static SequenceSkipAnd SkipAnd(this SequenceAndSkip parser, Parser and) => new SequenceSkipAnd(parser, and); + public static SequenceSkipAnd SkipAnd(this SequenceSkipAnd parser, Parser and) => new SequenceSkipAnd(parser, and); /// /// Builds a parser that ensure the specified parsers match consecutively. The last parser's result is then ignored. /// - public static Parser> SkipAnd(this Parser> parser, Parser and) => new SequenceSkipAnd(parser, and); + public static SequenceSkipAnd SkipAnd(this Sequence parser, Parser and) => new SequenceSkipAnd(parser, and); + public static SequenceSkipAnd SkipAnd(this SequenceAndSkip parser, Parser and) => new SequenceSkipAnd(parser, and); + public static SequenceSkipAnd SkipAnd(this SequenceSkipAnd parser, Parser and) => new SequenceSkipAnd(parser, and); /// /// Builds a parser that ensure the specified parsers match consecutively. The last parser's result is then ignored. /// - public static Parser> SkipAnd(this Parser> parser, Parser and) => new SequenceSkipAnd(parser, and); + public static SequenceSkipAnd SkipAnd(this Sequence parser, Parser and) => new SequenceSkipAnd(parser, and); + public static SequenceSkipAnd SkipAnd(this SequenceAndSkip parser, Parser and) => new SequenceSkipAnd(parser, and); + public static SequenceSkipAnd SkipAnd(this SequenceSkipAnd parser, Parser and) => new SequenceSkipAnd(parser, and); /// /// Builds a parser that ensure the specified parsers match consecutively. The last parser's result is then ignored. /// - public static Parser> SkipAnd(this Parser> parser, Parser and) => new SequenceSkipAnd(parser, and); + public static SequenceSkipAnd SkipAnd(this Sequence parser, Parser and) => new SequenceSkipAnd(parser, and); + public static SequenceSkipAnd SkipAnd(this SequenceAndSkip parser, Parser and) => new SequenceSkipAnd(parser, and); + public static SequenceSkipAnd ASkipAndnd(this SequenceSkipAnd parser, Parser and) => new SequenceSkipAnd(parser, and); /// /// Builds a parser that ensure the specified parsers match consecutively. The last parser's result is then ignored. /// - public static Parser> SkipAnd(this Parser> parser, Parser and) => new SequenceSkipAnd(parser, and); + public static SequenceSkipAnd SkipAnd(this Sequence parser, Parser and) => new SequenceSkipAnd(parser, and); /// /// Builds a parser that ensure the specified parsers match consecutively. The last parser's result is then ignored. /// - public static Parser> SkipAnd(this Parser> parser, Parser and) => new SequenceSkipAnd(parser, and); + public static SequenceSkipAnd SkipAnd(this Sequence parser, Parser and) => new SequenceSkipAnd(parser, and); + public static SequenceSkipAnd SkipAnd(this SequenceAndSkip parser, Parser and) => new SequenceSkipAnd(parser, and); + public static SequenceSkipAnd SkipAnd(this SequenceSkipAnd parser, Parser and) => new SequenceSkipAnd(parser, and); } } diff --git a/test/Parlot.Tests/CompileTests.cs b/test/Parlot.Tests/CompileTests.cs index 03e9efb..0064518 100644 --- a/test/Parlot.Tests/CompileTests.cs +++ b/test/Parlot.Tests/CompileTests.cs @@ -1,5 +1,7 @@ using Parlot.Fluent; +using System; using System.Collections.Generic; +using System.Globalization; using Xunit; using static Parlot.Fluent.Parsers; @@ -644,5 +646,95 @@ public void ShouldSkipSequences() Assert.True(parser.TryParse("abcd", out var result1)); Assert.Equal("abd", result1.Item1.ToString() + result1.Item2 + result1.Item3); } + + [Fact] + public void ShouldCompileSequencesWithOneOf() + { + OneOf(Terms.Char('+').And(Terms.Char('-'))) + .And(Terms.Integer()) + .Compile(); + } + + [Fact] + public void ShouldParseSequenceCompile() + { + var a = Literals.Char('a'); + var b = Literals.Char('b'); + var c = Literals.Char('c'); + var d = Literals.Char('d'); + var e = Literals.Char('e'); + var f = Literals.Char('f'); + var g = Literals.Char('g'); + var h = Literals.Char('h'); + + Assert.True(a.And(b).Compile().TryParse("ab", out var r)); + Assert.Equal(('a', 'b'), r); + + Assert.True(a.And(b).And(c).Compile().TryParse("abc", out var r1)); + Assert.Equal(('a', 'b', 'c'), r1); + + Assert.True(a.And(b).AndSkip(c).Compile().TryParse("abc", out var r2)); + Assert.Equal(('a', 'b'), r2); + + Assert.True(a.And(b).SkipAnd(c).Compile().TryParse("abc", out var r3)); + Assert.Equal(('a', 'c'), r3); + } + + [Fact] + public void ShouldParseSequenceAndSkipCompile() + { + var a = Literals.Char('a'); + var b = Literals.Char('b'); + var c = Literals.Char('c'); + var d = Literals.Char('d'); + var e = Literals.Char('e'); + var f = Literals.Char('f'); + var g = Literals.Char('g'); + var h = Literals.Char('h'); + + Assert.True(a.AndSkip(b).Compile().TryParse("ab", out var r)); + Assert.Equal(('a'), r); + + Assert.True(a.AndSkip(b).And(c).Compile().TryParse("abc", out var r1)); + Assert.Equal(('a', 'c'), r1); + + Assert.True(a.AndSkip(b).AndSkip(c).Compile().TryParse("abc", out var r2)); + Assert.Equal(('a'), r2); + + Assert.True(a.AndSkip(b).SkipAnd(c).Compile().TryParse("abc", out var r3)); + Assert.Equal(('c'), r3); + } + + [Fact] + public void ShouldParseSequenceSkipAndCompile() + { + var a = Literals.Char('a'); + var b = Literals.Char('b'); + var c = Literals.Char('c'); + var d = Literals.Char('d'); + var e = Literals.Char('e'); + var f = Literals.Char('f'); + var g = Literals.Char('g'); + var h = Literals.Char('h'); + + Assert.True(a.SkipAnd(b).Compile().TryParse("ab", out var r)); + Assert.Equal(('b'), r); + + Assert.True(a.SkipAnd(b).And(c).Compile().TryParse("abc", out var r1)); + Assert.Equal(('b', 'c'), r1); + + Assert.True(a.SkipAnd(b).AndSkip(c).Compile().TryParse("abc", out var r2)); + Assert.Equal(('b'), r2); + + Assert.True(a.SkipAnd(b).SkipAnd(c).Compile().TryParse("abc", out var r3)); + Assert.Equal(('c'), r3); + } + + private class LogicalExpression { } + + private class ValueExpression(decimal Value) : LogicalExpression + { + public decimal Value { get; } = Value; + } } } diff --git a/test/Parlot.Tests/FluentTests.cs b/test/Parlot.Tests/FluentTests.cs index dac04e4..2a70216 100644 --- a/test/Parlot.Tests/FluentTests.cs +++ b/test/Parlot.Tests/FluentTests.cs @@ -1,6 +1,7 @@ using Parlot.Fluent; using System.Collections.Generic; using System.Globalization; +using System.Net.WebSockets; using Xunit; using static Parlot.Fluent.Parsers; @@ -694,5 +695,80 @@ public void ZeroOrManyShouldHandleAllSizes() Assert.Equal([("+", 1L), ("-", 2)], parser.Parse("+1-2")); } + + [Fact] + public void ShouldParseSequence() + { + var a = Literals.Char('a'); + var b = Literals.Char('b'); + var c = Literals.Char('c'); + var d = Literals.Char('d'); + var e = Literals.Char('e'); + var f = Literals.Char('f'); + var g = Literals.Char('g'); + var h = Literals.Char('h'); + + Assert.True(a.And(b).TryParse("ab", out var r)); + Assert.Equal(('a', 'b'), r); + + Assert.True(a.And(b).And(c).TryParse("abc", out var r1)); + Assert.Equal(('a', 'b', 'c'), r1); + + Assert.True(a.And(b).AndSkip(c).TryParse("abc", out var r2)); + Assert.Equal(('a', 'b'), r2); + + Assert.True(a.And(b).SkipAnd(c).TryParse("abc", out var r3)); + Assert.Equal(('a', 'c'), r3); + } + + [Fact] + public void ShouldParseSequenceAndSkip() + { + var a = Literals.Char('a'); + var b = Literals.Char('b'); + var c = Literals.Char('c'); + var d = Literals.Char('d'); + var e = Literals.Char('e'); + var f = Literals.Char('f'); + var g = Literals.Char('g'); + var h = Literals.Char('h'); + + Assert.True(a.AndSkip(b).TryParse("ab", out var r)); + Assert.Equal(('a'), r); + + Assert.True(a.AndSkip(b).And(c).TryParse("abc", out var r1)); + Assert.Equal(('a', 'c'), r1); + + Assert.True(a.AndSkip(b).AndSkip(c).TryParse("abc", out var r2)); + Assert.Equal(('a'), r2); + + Assert.True(a.AndSkip(b).SkipAnd(c).TryParse("abc", out var r3)); + Assert.Equal(('c'), r3); + } + + [Fact] + public void ShouldParseSequenceSkipAnd() + { + var a = Literals.Char('a'); + var b = Literals.Char('b'); + var c = Literals.Char('c'); + var d = Literals.Char('d'); + var e = Literals.Char('e'); + var f = Literals.Char('f'); + var g = Literals.Char('g'); + var h = Literals.Char('h'); + + Assert.True(a.SkipAnd(b).TryParse("ab", out var r)); + Assert.Equal(('b'), r); + + Assert.True(a.SkipAnd(b).And(c).TryParse("abc", out var r1)); + Assert.Equal(('b', 'c'), r1); + + Assert.True(a.SkipAnd(b).AndSkip(c).TryParse("abc", out var r2)); + Assert.Equal(('b'), r2); + + Assert.True(a.SkipAnd(b).SkipAnd(c).TryParse("abc", out var r3)); + Assert.Equal(('c'), r3); + } } }