Skip to content

Commit

Permalink
Merge pull request sprache#179 from marcin-golebiowski/fix-ref
Browse files Browse the repository at this point in the history
Improve Parse.Ref
  • Loading branch information
nblumhardt authored Sep 20, 2021
2 parents 3f3d153 + 0a7bd6f commit 9d1721b
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 36 deletions.
15 changes: 10 additions & 5 deletions src/Sprache/Parse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ namespace Sprache
/// </summary>
public static partial class Parse
{
/// <summary>
/// Message for a failure result when left recursion is detected.
/// </summary>
public const string LeftRecursionErrorMessage = "Left recursion in the grammar.";

/// <summary>
/// TryParse a single character matching 'predicate'
/// </summary>
Expand Down Expand Up @@ -393,14 +398,14 @@ public static Parser<T> Ref<T>(Func<Parser<T>> reference)
if (i.Memos.ContainsKey(p))
{
var pResult = (IResult<T>)i.Memos[p];
if (pResult.WasSuccessful)
if (pResult.WasSuccessful)
return pResult;
throw new ParseException(pResult.ToString());

if (!pResult.WasSuccessful && pResult.Message == LeftRecursionErrorMessage)
throw new ParseException(pResult.ToString());
}

i.Memos[p] = Result.Failure<T>(i,
"Left recursion in the grammar.",
new string[0]);
i.Memos[p] = Result.Failure<T>(i, LeftRecursionErrorMessage, new string[0]);
var result = p(i);
i.Memos[p] = result;
return result;
Expand Down
110 changes: 110 additions & 0 deletions test/Sprache.Tests/Parse.Ref.Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
using System.Collections.Generic;
using System.Linq;
using Xunit;

namespace Sprache.Tests
{
public class ParseRefTests
{
private static Parser<string> ParserHash = Parse.String("#").Text().Named("something");

private static Parser<string> ParserIdentifier = (from id in Parse.String("id").Text() select id).Named("identifier");

private static Parser<string> Parser1UnderTest =
(from _0 in Parse.Ref(() => ParserIdentifier)
from _1 in Parse.Ref(() => ParserHash)
from _2 in Parse.String("_")
select "alternative_1")
.Or(from _0 in Parse.Ref(() => ParserIdentifier)
from _1 in Parse.Ref(() => ParserHash)
select "alternative_2")
.Or(from _0 in ParserIdentifier select _0);

private static Parser<string> Parser2UnderTest =
(from _0 in Parse.String("a").Text()
from _1 in Parse.Ref(() => Parser2UnderTest)
select _0 + _1)
.Or(from _0 in Parse.String("b").Text()
from _1 in Parse.Ref(() => Parser2UnderTest)
select _0 + _1)
.Or(from _0 in Parse.String("0").Text() select _0);

private static Parser<string> Parser3UnderTest =
(from _0 in Parse.Ref(() => Parser3UnderTest)
from _1 in Parse.String("a").Text()
select _0 + _1)
.Or(from _0 in Parse.String("b").Text()
from _1 in Parse.Ref(() => Parser3UnderTest)
select _0 + _1)
.Or(from _0 in Parse.String("0").Text() select _0);

private static Parser<string> Parser4UnderTest =
from _0 in Parse.Ref(() => Parser4UnderTest)
select "simplest left recursion";

private static Parser<string> Parser5UnderTest =
(from _0 in Parse.String("_").Text()
from _1 in Parse.Ref(() => Parser5UnderTest)
select _0 + _1)
.Or(from _0 in Parse.String("+").Text()
from _1 in Parse.Ref(() => Parser5UnderTest)
select _0 + _1)
.Or(Parse.Return(""));

[Fact]
public void MultipleRefs() => AssertParser.SucceedsWith(Parser1UnderTest, "id=1", o => Assert.Equal("id", o));

[Fact]
public void RecursiveParserWithoutLeftRecursion() => AssertParser.SucceedsWith(Parser2UnderTest, "ababba0", o => Assert.Equal("ababba0", o));

[Fact]
public void RecursiveParserWithLeftRecursion() => Assert.Throws<ParseException>(() => Parser3UnderTest.TryParse("b0"));

[Fact]
public void SimplestLeftRecursion() => Assert.Throws<ParseException>(() => Parser4UnderTest.TryParse("test"));

[Fact]
public void EmptyAlternative1() => AssertParser.SucceedsWith(Parser5UnderTest, "_+_+a", o => Assert.Equal("_+_+", o));

[Fact]
public void Issue166()
{
var letterA = Parse.Char('a');
var letterReferenced = Parse.Ref(() => letterA);
var someAlternative = letterReferenced.Or(letterReferenced);

Assert.False(someAlternative.TryParse("b").WasSuccessful);
}

static readonly Parser<IEnumerable<char>> ASeq =
(from first in Parse.Ref(() => ASeq)
from comma in Parse.Char(',')
from rest in Parse.Char('a').Once()
select first.Concat(rest))
.Or(Parse.Char('a').Once());

[Fact]
public void DetectsLeftRecursion()
{
Assert.Throws<ParseException>(() => ASeq.TryParse("a,a,a"));
}

static readonly Parser<IEnumerable<char>> ABSeq =
(from first in Parse.Ref(() => BASeq)
from rest in Parse.Char('a').Once()
select first.Concat(rest))
.Or(Parse.Char('a').Once());

static readonly Parser<IEnumerable<char>> BASeq =
(from first in Parse.Ref(() => ABSeq)
from rest in Parse.Char('b').Once()
select first.Concat(rest))
.Or(Parse.Char('b').Once());

[Fact]
public void DetectsMutualLeftRecursion()
{
Assert.Throws<ParseException>(() => ABSeq.End().TryParse("baba"));
}
}
}
31 changes: 0 additions & 31 deletions test/Sprache.Tests/ParseTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,37 +144,6 @@ public void ParsesString_AsSequenceOfChars()
AssertParser.SucceedsWithAll(p, "abc");
}

static readonly Parser<IEnumerable<char>> ASeq =
(from first in Parse.Ref(() => ASeq)
from comma in Parse.Char(',')
from rest in Parse.Char('a').Once()
select first.Concat(rest))
.Or(Parse.Char('a').Once());

[Fact]
public void DetectsLeftRecursion()
{
Assert.Throws<ParseException>(() => ASeq.TryParse("a,a,a"));
}

static readonly Parser<IEnumerable<char>> ABSeq =
(from first in Parse.Ref(() => BASeq)
from rest in Parse.Char('a').Once()
select first.Concat(rest))
.Or(Parse.Char('a').Once());

static readonly Parser<IEnumerable<char>> BASeq =
(from first in Parse.Ref(() => ABSeq)
from rest in Parse.Char('b').Once()
select first.Concat(rest))
.Or(Parse.Char('b').Once());

[Fact]
public void DetectsMutualLeftRecursion()
{
Assert.Throws<ParseException>(() => ABSeq.End().TryParse("baba"));
}

[Fact]
public void WithMany_WhenLastElementFails_FailureReportedAtLastElement()
{
Expand Down

0 comments on commit 9d1721b

Please sign in to comment.