Fix treatment of trailing semicolons
This commit is contained in:
parent
b2c6f8cadd
commit
62713311cc
@ -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]
|
||||
|
@ -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)
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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());
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
;
|
||||
|
@ -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" />
|
||||
|
@ -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)
|
||||
|
@ -83,6 +83,11 @@ namespace Parser
|
||||
DefaultVisit(node);
|
||||
}
|
||||
|
||||
public virtual void VisitTrailingSemicolon(TrailingSemicolonSyntaxNode node)
|
||||
{
|
||||
DefaultVisit(node);
|
||||
}
|
||||
|
||||
public virtual void VisitEmptyStatement(EmptyStatementSyntaxNode node)
|
||||
{
|
||||
DefaultVisit(node);
|
||||
|
@ -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".
|
||||
|
Loading…
x
Reference in New Issue
Block a user