diff --git a/Parser.Tests/MParserShould.cs b/Parser.Tests/MParserShould.cs index 8171f41..bd25500 100644 --- a/Parser.Tests/MParserShould.cs +++ b/Parser.Tests/MParserShould.cs @@ -43,5 +43,14 @@ namespace Parser.Tests Assert.IsType(assignment); Assert.IsType(((ExpressionStatementSyntaxNode)assignment).Expression); } + + [Theory] + [InlineData("2 + ;")] + public void ParseStatement(string text) + { + var sut = GetSut(text); + var actual = sut.Parse(); + Assert.Collection(actual.Diagnostics, item => Assert.Equal("Unexpected token 'SemicolonToken', expected 'IdentifierToken'.", item.Message)); + } } } \ No newline at end of file diff --git a/Parser/Internal/DiagnosticsBag.cs b/Parser/Internal/DiagnosticsBag.cs index c54cf40..39f0fdc 100644 --- a/Parser/Internal/DiagnosticsBag.cs +++ b/Parser/Internal/DiagnosticsBag.cs @@ -1,5 +1,6 @@ using System.Collections; using System.Collections.Generic; +using System.Linq; namespace Parser.Internal { @@ -10,6 +11,11 @@ namespace Parser.Internal _diagnostics = new List(); } + public DiagnosticsBag(IEnumerable diagnostics) + { + _diagnostics = diagnostics.ToList(); + } + private readonly List _diagnostics; public IReadOnlyCollection Diagnostics => _diagnostics.AsReadOnly(); @@ -40,6 +46,11 @@ namespace Parser.Internal Report(span, $"Unknown symbol '{c}'."); } + internal void ReportUnexpectedToken(TextSpan span, TokenKind expected, TokenKind actual) + { + Report(span, $"Unexpected token '{actual}', expected '{expected}'."); + } + public IEnumerator GetEnumerator() { return _diagnostics.GetEnumerator(); diff --git a/Parser/Internal/MParserGreen.cs b/Parser/Internal/MParserGreen.cs index f7de28f..4db3e52 100644 --- a/Parser/Internal/MParserGreen.cs +++ b/Parser/Internal/MParserGreen.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; namespace Parser.Internal @@ -11,13 +12,13 @@ namespace Parser.Internal private Position CurrentPosition => Pairs[_index].position; private SyntaxToken PeekToken(int n) => Pairs[_index + n].token; private SyntaxFactory Factory { get; } - private List Errors { get; } + public DiagnosticsBag Diagnostics { get; } public MParserGreen(List<(SyntaxToken, Position)> pairs, SyntaxFactory factory) { Pairs = pairs; Factory = factory; - Errors = new List(); + Diagnostics = new DiagnosticsBag(); } private SyntaxToken EatToken() @@ -32,7 +33,7 @@ namespace Parser.Internal var token = CurrentToken; if (token.Kind != kind) { - Errors.Add($"Unexpected token \"{token.Text}\" instead of {kind} at {CurrentPosition}."); + Diagnostics.ReportUnexpectedToken(token.Span, kind, token.Kind); return TokenFactory.CreateMissing(kind, null, null); } _index++; @@ -54,13 +55,13 @@ namespace Parser.Internal var token = CurrentToken; if (token.Kind != TokenKind.IdentifierToken) { - Errors.Add($"Unexpected token \"{token.Text}\" instead of {TokenKind.IdentifierToken} at {CurrentPosition}."); + Diagnostics.ReportUnexpectedToken(token.Span, TokenKind.IdentifierToken, token.Kind); return TokenFactory.CreateMissing(TokenKind.IdentifierToken, null, null); } if (token.Text != s) { - Errors.Add($"Unexpected token \"{token.Text}\" instead of identifier {s} at {CurrentPosition}."); + Diagnostics.ReportUnexpectedToken(token.Span, TokenKind.IdentifierToken, token.Kind); return TokenFactory.CreateMissing(TokenKind.IdentifierToken, null, null); } @@ -116,19 +117,7 @@ namespace Parser.Internal SyntaxToken assignmentSign; var builder = new SyntaxListBuilder(); - if (CurrentToken.Kind == TokenKind.IdentifierToken) - { - if (PeekToken(1).Kind == TokenKind.EqualsToken) - { - builder.Add(Factory.IdentifierNameSyntax(EatToken())); - assignmentSign = EatToken(TokenKind.EqualsToken); - } - else - { - return null; - } - } - else if (CurrentToken.Kind == TokenKind.OpenSquareBracketToken) + if (CurrentToken.Kind == TokenKind.OpenSquareBracketToken) { builder.Add(EatToken()); var outputs = ParseFunctionOutputList(); @@ -140,10 +129,21 @@ namespace Parser.Internal builder.Add(EatToken(TokenKind.CloseSquareBracketToken)); assignmentSign = EatToken(TokenKind.EqualsToken); } + else if (CurrentToken.Kind == TokenKind.IdentifierToken) + { + if (PeekToken(1).Kind == TokenKind.EqualsToken) + { + var identifierToken = EatIdentifier(); + builder.Add(Factory.IdentifierNameSyntax(identifierToken)); + assignmentSign = EatToken(TokenKind.EqualsToken); + } + else + { + return null; + } + } else { - Errors.Add( - $"Unexpected token {CurrentToken} during parsing function output description at {CurrentPosition}."); return null; } @@ -292,9 +292,6 @@ namespace Parser.Internal ExpressionSyntaxNode expression = null; switch (token.Kind) { - case TokenKind.IdentifierToken: - expression = Factory.IdentifierNameSyntax(EatToken()); - break; case TokenKind.NumberLiteralToken: expression = Factory.NumberLiteralSyntax(EatToken()); break; @@ -316,6 +313,10 @@ namespace Parser.Internal case TokenKind.OpenParenthesisToken: expression = ParseParenthesizedExpression(); break; + default: + var id = EatToken(TokenKind.IdentifierToken); + expression = Factory.IdentifierNameSyntax(id); + break; } if (expression == null) @@ -570,9 +571,16 @@ namespace Parser.Internal EatToken(); var rhs = ParseSubExpression(options, newPrecedence); - if (rhs == null && token.Kind == TokenKind.ColonToken) // for parsing things like a{:} + if (rhs == null) { - rhs = Factory.EmptyExpressionSyntax(); + if (token.Kind == TokenKind.ColonToken) // for parsing things like a{:} + { + rhs = Factory.EmptyExpressionSyntax(); + } + else + { + rhs = null; + } } if (token.Kind == TokenKind.EqualsToken) { diff --git a/Parser/Internal/SyntaxToken.cs b/Parser/Internal/SyntaxToken.cs index beafab0..bfc9007 100644 --- a/Parser/Internal/SyntaxToken.cs +++ b/Parser/Internal/SyntaxToken.cs @@ -6,6 +6,8 @@ namespace Parser.Internal { internal abstract class SyntaxToken : GreenNode { + public TextSpan Span { get; } + internal class SyntaxTokenWithTrivia : SyntaxToken { private readonly string _text; diff --git a/Parser/Internal/TextSpan.cs b/Parser/Internal/TextSpan.cs index 80c054e..1d7d52f 100644 --- a/Parser/Internal/TextSpan.cs +++ b/Parser/Internal/TextSpan.cs @@ -11,5 +11,10 @@ public int Start { get; } public int Length { get; } public int End => Start + Length; + + public override string ToString() + { + return $"{Start}--{End}"; + } } } \ No newline at end of file diff --git a/Parser/MParser.cs b/Parser/MParser.cs index e9e5db0..b514c75 100644 --- a/Parser/MParser.cs +++ b/Parser/MParser.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using Parser.Internal; +using System.Linq; namespace Parser { @@ -16,12 +17,14 @@ namespace Parser public SyntaxTree Parse() { var lexer = new Internal.MLexerGreen(_window); - var diagnostics = lexer.Diagnostics; + var lexerDiagnostics = lexer.Diagnostics; var tokens = lexer.ParseAll(); var parser = new Internal.MParserGreen(tokens, new Internal.SyntaxFactory()); var green = parser.ParseFile(); + var parserDiagnostics = parser.Diagnostics; + var totalDiagnostics = new DiagnosticsBag(lexerDiagnostics.Concat(parserDiagnostics)); var root = new FileSyntaxNode(null, green); - return new SyntaxTree(root, diagnostics); + return new SyntaxTree(root, totalDiagnostics); } } diff --git a/Repl/MRepl.cs b/Repl/MRepl.cs index 36a6fe4..b64feb0 100644 --- a/Repl/MRepl.cs +++ b/Repl/MRepl.cs @@ -14,6 +14,13 @@ namespace Repl var window = new TextWindowWithNull(line); var parser = new MParser(window); var tree = parser.Parse(); + if (tree.Diagnostics.Diagnostics.Count > 0) + { + foreach (var diagnostic in tree.Diagnostics.Diagnostics) + { + Console.WriteLine($"{diagnostic.Span}: {diagnostic.Message}"); + } + } TreeRenderer.RenderTree(tree); } }