diff --git a/src/Abc.Zebus.MessageDsl.Tests/MessageDsl/ParsedContractsTests.cs b/src/Abc.Zebus.MessageDsl.Tests/MessageDsl/ParsedContractsTests.cs index 84c3295..a13b09e 100644 --- a/src/Abc.Zebus.MessageDsl.Tests/MessageDsl/ParsedContractsTests.cs +++ b/src/Abc.Zebus.MessageDsl.Tests/MessageDsl/ParsedContractsTests.cs @@ -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")] @@ -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( @@ -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] @@ -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; } diff --git a/src/Abc.Zebus.MessageDsl/Dsl/MessageContractsErrorStrategy.cs b/src/Abc.Zebus.MessageDsl/Dsl/MessageContractsErrorStrategy.cs index 1cd6331..074ca9f 100644 --- a/src/Abc.Zebus.MessageDsl/Dsl/MessageContractsErrorStrategy.cs +++ b/src/Abc.Zebus.MessageDsl/Dsl/MessageContractsErrorStrategy.cs @@ -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) @@ -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 @@ -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; @@ -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); } diff --git a/src/Abc.Zebus.MessageDsl.Generator/Support/CompilerServices.cs b/src/Abc.Zebus.MessageDsl/Support/CompilerServices.cs similarity index 100% rename from src/Abc.Zebus.MessageDsl.Generator/Support/CompilerServices.cs rename to src/Abc.Zebus.MessageDsl/Support/CompilerServices.cs