diff --git a/src/Sprache/Parse.cs b/src/Sprache/Parse.cs
index d5761fa..c36cfff 100644
--- a/src/Sprache/Parse.cs
+++ b/src/Sprache/Parse.cs
@@ -13,6 +13,11 @@ namespace Sprache
///
public static partial class Parse
{
+ ///
+ /// Message for a failure result when left recursion is detected.
+ ///
+ public const string LeftRecursionErrorMessage = "Left recursion in the grammar.";
+
///
/// TryParse a single character matching 'predicate'
///
@@ -393,14 +398,14 @@ public static Parser Ref(Func> reference)
if (i.Memos.ContainsKey(p))
{
var pResult = (IResult)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(i,
- "Left recursion in the grammar.",
- new string[0]);
+ i.Memos[p] = Result.Failure(i, LeftRecursionErrorMessage, new string[0]);
var result = p(i);
i.Memos[p] = result;
return result;
diff --git a/test/Sprache.Tests/Parse.Ref.Tests.cs b/test/Sprache.Tests/Parse.Ref.Tests.cs
new file mode 100644
index 0000000..533fbaa
--- /dev/null
+++ b/test/Sprache.Tests/Parse.Ref.Tests.cs
@@ -0,0 +1,110 @@
+using System.Collections.Generic;
+using System.Linq;
+using Xunit;
+
+namespace Sprache.Tests
+{
+ public class ParseRefTests
+ {
+ private static Parser ParserHash = Parse.String("#").Text().Named("something");
+
+ private static Parser ParserIdentifier = (from id in Parse.String("id").Text() select id).Named("identifier");
+
+ private static Parser 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 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 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 Parser4UnderTest =
+ from _0 in Parse.Ref(() => Parser4UnderTest)
+ select "simplest left recursion";
+
+ private static Parser 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(() => Parser3UnderTest.TryParse("b0"));
+
+ [Fact]
+ public void SimplestLeftRecursion() => Assert.Throws(() => 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> 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(() => ASeq.TryParse("a,a,a"));
+ }
+
+ static readonly Parser> 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> 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(() => ABSeq.End().TryParse("baba"));
+ }
+ }
+}
diff --git a/test/Sprache.Tests/ParseTests.cs b/test/Sprache.Tests/ParseTests.cs
index 01108a1..5336146 100644
--- a/test/Sprache.Tests/ParseTests.cs
+++ b/test/Sprache.Tests/ParseTests.cs
@@ -144,37 +144,6 @@ public void ParsesString_AsSequenceOfChars()
AssertParser.SucceedsWithAll(p, "abc");
}
- static readonly Parser> 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(() => ASeq.TryParse("a,a,a"));
- }
-
- static readonly Parser> 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> 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(() => ABSeq.End().TryParse("baba"));
- }
-
[Fact]
public void WithMany_WhenLastElementFails_FailureReportedAtLastElement()
{