diff --git a/Parser.Tests/MParserShould.cs b/Parser.Tests/MParserShould.cs index 9a5a63e..249a23b 100644 --- a/Parser.Tests/MParserShould.cs +++ b/Parser.Tests/MParserShould.cs @@ -37,12 +37,33 @@ namespace Parser.Tests var actual = sut.Parse(); var statement = actual.Root.Body.Statements[0].AsNode(); Assert.IsType(statement); - if (statement is null) - { - throw new System.Exception(); - } + Assert.IsType(((ExpressionStatementSyntaxNode)statement!).Expression); + } - Assert.IsType(((ExpressionStatementSyntaxNode)statement).Expression); + [Fact] + public void ParseExpressionStatementWithSemicolon() + { + var text = "2 + 3;"; + var sut = GetSut(text); + var actual = sut.Parse(); + Assert.Single(actual.Root.Body.Statements); + var statement = actual.Root.Body.Statements[0].AsNode(); + Assert.IsType(statement); + Assert.IsType(((ExpressionStatementSyntaxNode)statement!).Expression); + } + + [Fact] + public void ParseExpressionStatementWithSemicolonAfterNewLine() + { + var text = "2 + 3\n;"; + var sut = GetSut(text); + var actual = sut.Parse(); + Assert.Equal(2, actual.Root.Body.Statements.Count); + var statement1 = actual.Root.Body.Statements[0].AsNode(); + Assert.IsType(statement1); + Assert.IsType(((ExpressionStatementSyntaxNode)statement1!).Expression); + var statement2 = actual.Root.Body.Statements[1].AsToken(); + Assert.Equal(TokenKind.SemicolonToken, statement2.Kind); } [Fact] diff --git a/Parser/Binding/Binder.cs b/Parser/Binding/Binder.cs index 9b8fccd..6848a28 100644 --- a/Parser/Binding/Binder.cs +++ b/Parser/Binding/Binder.cs @@ -258,7 +258,8 @@ namespace Parser.Binding private BoundExpressionStatement BindExpressionStatement(ExpressionStatementSyntaxNode node) { var expression = BindExpression(node.Expression); - return ExpressionStatement(node, expression); + var discardResult = node.Semicolon is not null; + return ExpressionStatement(node, expression, discardResult); } private BoundExpression BindExpression(ExpressionSyntaxNode node) diff --git a/Parser/Binding/BoundNodeFactory.cs b/Parser/Binding/BoundNodeFactory.cs index f8b60ab..2513301 100644 --- a/Parser/Binding/BoundNodeFactory.cs +++ b/Parser/Binding/BoundNodeFactory.cs @@ -25,9 +25,12 @@ namespace Parser.Binding return new BoundBlockStatement(syntax, statements); } - public static BoundExpressionStatement ExpressionStatement(SyntaxNode syntax, BoundExpression expression) + public static BoundExpressionStatement ExpressionStatement( + SyntaxNode syntax, + BoundExpression expression, + bool discardResult) { - return new BoundExpressionStatement(syntax, expression); + return new BoundExpressionStatement(syntax, expression, discardResult); } public static BoundIfStatement IfStatement( diff --git a/Parser/Binding/BoundRoot.cs b/Parser/Binding/BoundRoot.cs index 5f9d0ca..e59f7dc 100644 --- a/Parser/Binding/BoundRoot.cs +++ b/Parser/Binding/BoundRoot.cs @@ -118,15 +118,18 @@ namespace Parser.Binding public class BoundExpressionStatement : BoundStatement { - public BoundExpression Expression { get; } - - public override BoundNodeKind Kind => BoundNodeKind.ExpressionStatement; - - public BoundExpressionStatement(SyntaxNode syntax, BoundExpression expression) + public BoundExpressionStatement(SyntaxNode syntax, BoundExpression expression, bool discardResult) : base(syntax) { Expression = expression; + DiscardResult = discardResult; } + + public BoundExpression Expression { get; } + + public bool DiscardResult { get; } + + public override BoundNodeKind Kind => BoundNodeKind.ExpressionStatement; } public class BoundForStatement : BoundStatement diff --git a/Parser/Binding/BoundTreeRewriter.cs b/Parser/Binding/BoundTreeRewriter.cs index 83dd602..24e997d 100644 --- a/Parser/Binding/BoundTreeRewriter.cs +++ b/Parser/Binding/BoundTreeRewriter.cs @@ -147,7 +147,7 @@ namespace Parser.Binding return node; } - return ExpressionStatement(node.Syntax, expression); + return ExpressionStatement(node.Syntax, expression, node.DiscardResult); } public virtual BoundStatement RewriteEmptyStatement(BoundEmptyStatement node) diff --git a/Parser/Evaluator.cs b/Parser/Evaluator.cs index b03aa7d..e4b56f6 100644 --- a/Parser/Evaluator.cs +++ b/Parser/Evaluator.cs @@ -210,7 +210,8 @@ namespace Parser private MObject? EvaluateExpressionStatement(BoundExpressionStatement node) { - return EvaluateExpression(node.Expression); + var result = EvaluateExpression(node.Expression); + return node.DiscardResult ? null : result; } private MObject? EvaluateExpression(BoundExpression node) diff --git a/Parser/Internal/MParserGreen.cs b/Parser/Internal/MParserGreen.cs index dfce71e..a58011a 100644 --- a/Parser/Internal/MParserGreen.cs +++ b/Parser/Internal/MParserGreen.cs @@ -845,7 +845,28 @@ namespace Parser.Internal throw new Exception("Expression statement cannot be empty."); } - return Factory.ExpressionStatementSyntax(expression); + if (CurrentToken.Kind == TokenKind.SemicolonToken && !TriviaContainsNewLine(expression.TrailingTrivia)) + { + var semicolon = EatToken(); + return Factory.ExpressionStatementSyntax( + expression, + Factory.TrailingSemicolonSyntax(semicolon)); + } + + return Factory.ExpressionStatementSyntax(expression, semicolon: null); + } + + private bool TriviaContainsNewLine(IReadOnlyList trivia) + { + foreach(var t in trivia) + { + if (t.Text.Contains('\n')) + { + return true; + } + } + + return false; } private AttributeAssignmentSyntaxNode? ParseAttributeAssignment() @@ -1179,11 +1200,6 @@ namespace Parser.Internal } } - if (CurrentToken.Kind == TokenKind.OpenSquareBracketToken) - { - return ParseExpressionStatement(); - } - if (CurrentToken.Kind == TokenKind.SemicolonToken) { return Factory.EmptyStatementSyntax(EatToken()); diff --git a/Parser/Internal/SyntaxFactory.Generated.cs b/Parser/Internal/SyntaxFactory.Generated.cs index 826f4d1..6280330 100644 --- a/Parser/Internal/SyntaxFactory.Generated.cs +++ b/Parser/Internal/SyntaxFactory.Generated.cs @@ -78,9 +78,14 @@ namespace Parser.Internal return new TryCatchStatementSyntaxNode(tryKeyword, tryBody, catchClause, endKeyword); } - public ExpressionStatementSyntaxNode ExpressionStatementSyntax(ExpressionSyntaxNode expression) + public ExpressionStatementSyntaxNode ExpressionStatementSyntax(ExpressionSyntaxNode expression, TrailingSemicolonSyntaxNode? semicolon) { - return new ExpressionStatementSyntaxNode(expression); + return new ExpressionStatementSyntaxNode(expression, semicolon); + } + + public TrailingSemicolonSyntaxNode TrailingSemicolonSyntax(SyntaxToken semicolon) + { + return new TrailingSemicolonSyntaxNode(semicolon); } public EmptyStatementSyntaxNode EmptyStatementSyntax(SyntaxToken semicolon) diff --git a/Parser/Internal/SyntaxNode.Generated.cs b/Parser/Internal/SyntaxNode.Generated.cs index 149e4bc..b020d0e 100644 --- a/Parser/Internal/SyntaxNode.Generated.cs +++ b/Parser/Internal/SyntaxNode.Generated.cs @@ -779,18 +779,23 @@ namespace Parser.Internal internal class ExpressionStatementSyntaxNode : StatementSyntaxNode { internal readonly ExpressionSyntaxNode _expression; - internal ExpressionStatementSyntaxNode(ExpressionSyntaxNode expression): base(TokenKind.ExpressionStatement) + internal readonly TrailingSemicolonSyntaxNode? _semicolon; + internal ExpressionStatementSyntaxNode(ExpressionSyntaxNode expression, TrailingSemicolonSyntaxNode? semicolon): base(TokenKind.ExpressionStatement) { - Slots = 1; + Slots = 2; this.AdjustWidth(expression); _expression = expression; + this.AdjustWidth(semicolon); + _semicolon = semicolon; } - internal ExpressionStatementSyntaxNode(ExpressionSyntaxNode expression, TokenDiagnostic[] diagnostics): base(TokenKind.ExpressionStatement, diagnostics) + internal ExpressionStatementSyntaxNode(ExpressionSyntaxNode expression, TrailingSemicolonSyntaxNode? semicolon, TokenDiagnostic[] diagnostics): base(TokenKind.ExpressionStatement, diagnostics) { - Slots = 1; + Slots = 2; this.AdjustWidth(expression); _expression = expression; + this.AdjustWidth(semicolon); + _semicolon = semicolon; } internal override Parser.SyntaxNode CreateRed(Parser.SyntaxNode parent, int position) @@ -800,14 +805,52 @@ namespace Parser.Internal public override GreenNode SetDiagnostics(TokenDiagnostic[] diagnostics) { - return new ExpressionStatementSyntaxNode(_expression, diagnostics); + return new ExpressionStatementSyntaxNode(_expression, _semicolon, diagnostics); } public override GreenNode? GetSlot(int i) { return i switch { - 0 => _expression, _ => null + 0 => _expression, 1 => _semicolon, _ => null + } + + ; + } + } + + internal class TrailingSemicolonSyntaxNode : SyntaxNode + { + internal readonly SyntaxToken _semicolon; + internal TrailingSemicolonSyntaxNode(SyntaxToken semicolon): base(TokenKind.TrailingSemicolon) + { + Slots = 1; + this.AdjustWidth(semicolon); + _semicolon = semicolon; + } + + internal TrailingSemicolonSyntaxNode(SyntaxToken semicolon, TokenDiagnostic[] diagnostics): base(TokenKind.TrailingSemicolon, diagnostics) + { + Slots = 1; + this.AdjustWidth(semicolon); + _semicolon = semicolon; + } + + internal override Parser.SyntaxNode CreateRed(Parser.SyntaxNode parent, int position) + { + return new Parser.TrailingSemicolonSyntaxNode(parent, this, position); + } + + public override GreenNode SetDiagnostics(TokenDiagnostic[] diagnostics) + { + return new TrailingSemicolonSyntaxNode(_semicolon, diagnostics); + } + + public override GreenNode? GetSlot(int i) + { + return i switch + { + 0 => _semicolon, _ => null } ; diff --git a/Parser/SyntaxDefinition.xml b/Parser/SyntaxDefinition.xml index fcbc09a..be2d64f 100644 --- a/Parser/SyntaxDefinition.xml +++ b/Parser/SyntaxDefinition.xml @@ -88,6 +88,10 @@ + + + + diff --git a/Parser/SyntaxNode.Generated.cs b/Parser/SyntaxNode.Generated.cs index 22a137a..9bc5b65 100644 --- a/Parser/SyntaxNode.Generated.cs +++ b/Parser/SyntaxNode.Generated.cs @@ -852,6 +852,7 @@ namespace Parser public class ExpressionStatementSyntaxNode : StatementSyntaxNode { private SyntaxNode? _expression; + private SyntaxNode? _semicolon; internal ExpressionStatementSyntaxNode(SyntaxNode parent, Internal.GreenNode green, int position): base(parent, green, position) { } @@ -865,11 +866,20 @@ namespace Parser } } + public TrailingSemicolonSyntaxNode? Semicolon + { + get + { + var red = this.GetRed(ref this._semicolon, 1); + return red is null ? default : (TrailingSemicolonSyntaxNode)red; + } + } + internal override SyntaxNode? GetNode(int i) { return i switch { - 0 => GetRed(ref _expression!, 0), _ => null + 0 => GetRed(ref _expression!, 0), 1 => GetRed(ref _semicolon, 1), _ => null } ; @@ -881,6 +891,36 @@ namespace Parser } } + public class TrailingSemicolonSyntaxNode : SyntaxNode + { + internal TrailingSemicolonSyntaxNode(SyntaxNode parent, Internal.GreenNode green, int position): base(parent, green, position) + { + } + + public SyntaxToken Semicolon + { + get + { + return new SyntaxToken(this, ((Parser.Internal.TrailingSemicolonSyntaxNode)_green)._semicolon, this.GetChildPosition(0)); + } + } + + internal override SyntaxNode? GetNode(int i) + { + return i switch + { + _ => null + } + + ; + } + + public override void Accept(SyntaxVisitor visitor) + { + visitor.VisitTrailingSemicolon(this); + } + } + public class EmptyStatementSyntaxNode : StatementSyntaxNode { internal EmptyStatementSyntaxNode(SyntaxNode parent, Internal.GreenNode green, int position): base(parent, green, position) diff --git a/Parser/SyntaxVisitor.Generated.cs b/Parser/SyntaxVisitor.Generated.cs index 7249d3b..9e2035e 100644 --- a/Parser/SyntaxVisitor.Generated.cs +++ b/Parser/SyntaxVisitor.Generated.cs @@ -83,6 +83,11 @@ namespace Parser DefaultVisit(node); } + public virtual void VisitTrailingSemicolon(TrailingSemicolonSyntaxNode node) + { + DefaultVisit(node); + } + public virtual void VisitEmptyStatement(EmptyStatementSyntaxNode node) { DefaultVisit(node); diff --git a/Parser/TokenKind.cs b/Parser/TokenKind.cs index 4645763..7b52cfc 100644 --- a/Parser/TokenKind.cs +++ b/Parser/TokenKind.cs @@ -135,6 +135,9 @@ // a list of syntax nodes and/or tokens. List, + // a semicolon that marks expression statements with discarded results. + TrailingSemicolon, + // STATEMENTS // The name ends with "Declaration" or "Statement".