Skip to content

Commit

Permalink
Improve error recovery
Browse files Browse the repository at this point in the history
This tries to find the start of the next definition and avoids skipping it
  • Loading branch information
ltrzesniewski committed Jan 11, 2024
1 parent 4b78cdc commit 4e1fc47
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 9 deletions.
25 changes: 22 additions & 3 deletions src/Abc.Zebus.MessageDsl.Tests/MessageDsl/ParsedContractsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -703,8 +703,10 @@ public void should_generate_ast_for_incomplete_code()
}

[Test]
[TestCase("Foo")]
[TestCase("Foo")] // This one skips Bar :(
[TestCase("Foo;")]
[TestCase("Foo then")]
[TestCase("Foo then other stuff")]
[TestCase("Foo(")]
[TestCase("Foo(;")]
[TestCase("Foo(int")]
Expand All @@ -717,6 +719,12 @@ public void should_generate_ast_for_incomplete_code()
[TestCase("Foo(int first, string;")]
[TestCase("Foo(int first, string second")]
[TestCase("Foo(int first, string second;")]
[TestCase("enum")]
[TestCase("enum Foo")]
[TestCase("enum Foo {")]
[TestCase("enum Foo { First")]
[TestCase("enum Foo { First,")]
[TestCase("enum Foo { First, Second")]
public void should_split_on_incomplete_message(string firstLine)
{
var contracts = ParseInvalid(
Expand All @@ -728,8 +736,16 @@ public void should_split_on_incomplete_message(string firstLine)
);

contracts.Messages.Count.ShouldBeGreaterThan(1);
contracts.Messages[0].Name.ShouldEqual("Foo");
contracts.Messages[1].Name.ShouldEqualOneOf("Bar", "Baz");

if (firstLine.StartsWith("Foo"))
{
contracts.Messages[0].Name.ShouldEqual("Foo");
contracts.Messages[1].Name.ShouldEqual(firstLine != "Foo" ? "Bar" : "Baz");
}
else
{
contracts.Messages[0].Name.ShouldEqual("Bar");
}
}

[Test]
Expand Down Expand Up @@ -798,6 +814,9 @@ private static ParsedContracts Parse(string definitionText)
foreach (var message in contracts.Messages)
Console.WriteLine($"MESSAGE: {message}({string.Join(", ", message.Parameters.Select(p => $"[{p.Tag}] {p}"))})");

foreach (var member in contracts.Enums)
Console.WriteLine($"ENUM: {member}");

return contracts;
}

Expand Down
39 changes: 33 additions & 6 deletions src/Abc.Zebus.MessageDsl/Dsl/MessageContractsErrorStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,25 @@ namespace Abc.Zebus.MessageDsl.Dsl;

internal class MessageContractsErrorStrategy : DefaultErrorStrategy
{
private MarkAndIndex? _startOfCurrentDefinition;

public override void Sync(Parser recognizer)
{
if (InErrorRecoveryMode(recognizer))
return;

if (recognizer.Context.RuleIndex == MessageContractsParser.RULE_definition)
{
if (_startOfCurrentDefinition is { } mark)
recognizer.InputStream.Release(mark.Mark);

_startOfCurrentDefinition = new MarkAndIndex(recognizer.InputStream.Mark(), recognizer.InputStream.Index);
}

base.Sync(recognizer);

if (InErrorRecoveryMode(recognizer) && IsInDefinitionRule(recognizer))
throw new GoToNextDefinitionException(recognizer);
if (InErrorRecoveryMode(recognizer))
GoToNextDefinition(recognizer);
}

public override void ReportError(Parser recognizer, RecognitionException e)
Expand All @@ -21,18 +31,26 @@ public override void ReportError(Parser recognizer, RecognitionException e)
return;

base.ReportError(recognizer, e);

if (IsInDefinitionRule(recognizer))
throw new GoToNextDefinitionException(recognizer);
GoToNextDefinition(recognizer);
}

public override void Recover(Parser recognizer, RecognitionException e)
{
if (e is GoToNextDefinitionException)
{
// Unwind the stack to the end of the current definition rule
if (recognizer.Context.RuleIndex != MessageContractsParser.RULE_definition)
throw e;

// Rewind the input to the start of the definition, then skip a single token
if (_startOfCurrentDefinition is { } mark)
{
_startOfCurrentDefinition = null;
recognizer.InputStream.Seek(mark.Index);
recognizer.Consume();
recognizer.InputStream.Release(mark.Mark);
}

while (true)
{
// Consume tokens until we reach a possible separator between definitions
Expand All @@ -50,6 +68,12 @@ public override void Recover(Parser recognizer, RecognitionException e)
base.Recover(recognizer, e);
}

private static void GoToNextDefinition(Parser recognizer)
{
if (IsInDefinitionRule(recognizer))
throw new GoToNextDefinitionException(recognizer);
}

private static bool IsInDefinitionRule(Parser recognizer)
{
RuleContext ctx = recognizer.Context;
Expand All @@ -65,5 +89,8 @@ private static bool IsInDefinitionRule(Parser recognizer)
return false;
}

private class GoToNextDefinitionException(Parser recognizer) : RecognitionException(recognizer, recognizer.InputStream, recognizer.Context);
private sealed class GoToNextDefinitionException(Parser recognizer)
: RecognitionException(recognizer, recognizer.InputStream, recognizer.Context);

private readonly record struct MarkAndIndex(int Mark, int Index);
}

0 comments on commit 4e1fc47

Please sign in to comment.