Fix treatment of trailing semicolons

This commit is contained in:
Alexander Luzgarev 2020-07-17 09:53:19 +02:00
parent b2c6f8cadd
commit 62713311cc
13 changed files with 175 additions and 30 deletions

View File

@ -37,12 +37,33 @@ namespace Parser.Tests
var actual = sut.Parse();
var statement = actual.Root.Body.Statements[0].AsNode();
Assert.IsType<ExpressionStatementSyntaxNode>(statement);
if (statement is null)
{
throw new System.Exception();
}
Assert.IsType<BinaryOperationExpressionSyntaxNode>(((ExpressionStatementSyntaxNode)statement!).Expression);
}
Assert.IsType<BinaryOperationExpressionSyntaxNode>(((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<ExpressionStatementSyntaxNode>(statement);
Assert.IsType<BinaryOperationExpressionSyntaxNode>(((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<ExpressionStatementSyntaxNode>(statement1);
Assert.IsType<BinaryOperationExpressionSyntaxNode>(((ExpressionStatementSyntaxNode)statement1!).Expression);
var statement2 = actual.Root.Body.Statements[1].AsToken();
Assert.Equal(TokenKind.SemicolonToken, statement2.Kind);
}
[Fact]

View File

@ -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)

View File

@ -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(

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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<SyntaxTrivia> 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());

View File

@ -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)

View File

@ -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
}
;

View File

@ -88,6 +88,10 @@
</Class>
<Class Name="ExpressionStatementSyntaxNode" BaseClass="StatementSyntaxNode" Kind="ExpressionStatement">
<Field Type="ExpressionSyntaxNode" Name="expression" />
<Field Type="TrailingSemicolonSyntaxNode" Name="semicolon" Nullable="true" />
</Class>
<Class Name="TrailingSemicolonSyntaxNode" BaseClass="SyntaxNode" Kind="TrailingSemicolon">
<Field Type="SyntaxToken" Name="semicolon" />
</Class>
<Class Name="EmptyStatementSyntaxNode" BaseClass="StatementSyntaxNode" Kind="EmptyStatement">
<Field Type="SyntaxToken" Name="semicolon" />

View File

@ -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)

View File

@ -83,6 +83,11 @@ namespace Parser
DefaultVisit(node);
}
public virtual void VisitTrailingSemicolon(TrailingSemicolonSyntaxNode node)
{
DefaultVisit(node);
}
public virtual void VisitEmptyStatement(EmptyStatementSyntaxNode node)
{
DefaultVisit(node);

View File

@ -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".