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 actual = sut.Parse();
|
||||||
var statement = actual.Root.Body.Statements[0].AsNode();
|
var statement = actual.Root.Body.Statements[0].AsNode();
|
||||||
Assert.IsType<ExpressionStatementSyntaxNode>(statement);
|
Assert.IsType<ExpressionStatementSyntaxNode>(statement);
|
||||||
if (statement is null)
|
Assert.IsType<BinaryOperationExpressionSyntaxNode>(((ExpressionStatementSyntaxNode)statement!).Expression);
|
||||||
{
|
|
||||||
throw new System.Exception();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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]
|
[Fact]
|
||||||
|
@ -258,7 +258,8 @@ namespace Parser.Binding
|
|||||||
private BoundExpressionStatement BindExpressionStatement(ExpressionStatementSyntaxNode node)
|
private BoundExpressionStatement BindExpressionStatement(ExpressionStatementSyntaxNode node)
|
||||||
{
|
{
|
||||||
var expression = BindExpression(node.Expression);
|
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)
|
private BoundExpression BindExpression(ExpressionSyntaxNode node)
|
||||||
|
@ -25,9 +25,12 @@ namespace Parser.Binding
|
|||||||
return new BoundBlockStatement(syntax, statements);
|
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(
|
public static BoundIfStatement IfStatement(
|
||||||
|
@ -118,15 +118,18 @@ namespace Parser.Binding
|
|||||||
|
|
||||||
public class BoundExpressionStatement : BoundStatement
|
public class BoundExpressionStatement : BoundStatement
|
||||||
{
|
{
|
||||||
public BoundExpression Expression { get; }
|
public BoundExpressionStatement(SyntaxNode syntax, BoundExpression expression, bool discardResult)
|
||||||
|
|
||||||
public override BoundNodeKind Kind => BoundNodeKind.ExpressionStatement;
|
|
||||||
|
|
||||||
public BoundExpressionStatement(SyntaxNode syntax, BoundExpression expression)
|
|
||||||
: base(syntax)
|
: base(syntax)
|
||||||
{
|
{
|
||||||
Expression = expression;
|
Expression = expression;
|
||||||
|
DiscardResult = discardResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BoundExpression Expression { get; }
|
||||||
|
|
||||||
|
public bool DiscardResult { get; }
|
||||||
|
|
||||||
|
public override BoundNodeKind Kind => BoundNodeKind.ExpressionStatement;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class BoundForStatement : BoundStatement
|
public class BoundForStatement : BoundStatement
|
||||||
|
@ -147,7 +147,7 @@ namespace Parser.Binding
|
|||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ExpressionStatement(node.Syntax, expression);
|
return ExpressionStatement(node.Syntax, expression, node.DiscardResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual BoundStatement RewriteEmptyStatement(BoundEmptyStatement node)
|
public virtual BoundStatement RewriteEmptyStatement(BoundEmptyStatement node)
|
||||||
|
@ -210,7 +210,8 @@ namespace Parser
|
|||||||
|
|
||||||
private MObject? EvaluateExpressionStatement(BoundExpressionStatement node)
|
private MObject? EvaluateExpressionStatement(BoundExpressionStatement node)
|
||||||
{
|
{
|
||||||
return EvaluateExpression(node.Expression);
|
var result = EvaluateExpression(node.Expression);
|
||||||
|
return node.DiscardResult ? null : result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private MObject? EvaluateExpression(BoundExpression node)
|
private MObject? EvaluateExpression(BoundExpression node)
|
||||||
|
@ -845,7 +845,28 @@ namespace Parser.Internal
|
|||||||
throw new Exception("Expression statement cannot be empty.");
|
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()
|
private AttributeAssignmentSyntaxNode? ParseAttributeAssignment()
|
||||||
@ -1179,11 +1200,6 @@ namespace Parser.Internal
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CurrentToken.Kind == TokenKind.OpenSquareBracketToken)
|
|
||||||
{
|
|
||||||
return ParseExpressionStatement();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (CurrentToken.Kind == TokenKind.SemicolonToken)
|
if (CurrentToken.Kind == TokenKind.SemicolonToken)
|
||||||
{
|
{
|
||||||
return Factory.EmptyStatementSyntax(EatToken());
|
return Factory.EmptyStatementSyntax(EatToken());
|
||||||
|
@ -78,9 +78,14 @@ namespace Parser.Internal
|
|||||||
return new TryCatchStatementSyntaxNode(tryKeyword, tryBody, catchClause, endKeyword);
|
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)
|
public EmptyStatementSyntaxNode EmptyStatementSyntax(SyntaxToken semicolon)
|
||||||
|
@ -779,18 +779,23 @@ namespace Parser.Internal
|
|||||||
internal class ExpressionStatementSyntaxNode : StatementSyntaxNode
|
internal class ExpressionStatementSyntaxNode : StatementSyntaxNode
|
||||||
{
|
{
|
||||||
internal readonly ExpressionSyntaxNode _expression;
|
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);
|
this.AdjustWidth(expression);
|
||||||
_expression = 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);
|
this.AdjustWidth(expression);
|
||||||
_expression = expression;
|
_expression = expression;
|
||||||
|
this.AdjustWidth(semicolon);
|
||||||
|
_semicolon = semicolon;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal override Parser.SyntaxNode CreateRed(Parser.SyntaxNode parent, int position)
|
internal override Parser.SyntaxNode CreateRed(Parser.SyntaxNode parent, int position)
|
||||||
@ -800,14 +805,52 @@ namespace Parser.Internal
|
|||||||
|
|
||||||
public override GreenNode SetDiagnostics(TokenDiagnostic[] diagnostics)
|
public override GreenNode SetDiagnostics(TokenDiagnostic[] diagnostics)
|
||||||
{
|
{
|
||||||
return new ExpressionStatementSyntaxNode(_expression, diagnostics);
|
return new ExpressionStatementSyntaxNode(_expression, _semicolon, diagnostics);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override GreenNode? GetSlot(int i)
|
public override GreenNode? GetSlot(int i)
|
||||||
{
|
{
|
||||||
return i switch
|
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>
|
||||||
<Class Name="ExpressionStatementSyntaxNode" BaseClass="StatementSyntaxNode" Kind="ExpressionStatement">
|
<Class Name="ExpressionStatementSyntaxNode" BaseClass="StatementSyntaxNode" Kind="ExpressionStatement">
|
||||||
<Field Type="ExpressionSyntaxNode" Name="expression" />
|
<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>
|
||||||
<Class Name="EmptyStatementSyntaxNode" BaseClass="StatementSyntaxNode" Kind="EmptyStatement">
|
<Class Name="EmptyStatementSyntaxNode" BaseClass="StatementSyntaxNode" Kind="EmptyStatement">
|
||||||
<Field Type="SyntaxToken" Name="semicolon" />
|
<Field Type="SyntaxToken" Name="semicolon" />
|
||||||
|
@ -852,6 +852,7 @@ namespace Parser
|
|||||||
public class ExpressionStatementSyntaxNode : StatementSyntaxNode
|
public class ExpressionStatementSyntaxNode : StatementSyntaxNode
|
||||||
{
|
{
|
||||||
private SyntaxNode? _expression;
|
private SyntaxNode? _expression;
|
||||||
|
private SyntaxNode? _semicolon;
|
||||||
internal ExpressionStatementSyntaxNode(SyntaxNode parent, Internal.GreenNode green, int position): base(parent, green, position)
|
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)
|
internal override SyntaxNode? GetNode(int i)
|
||||||
{
|
{
|
||||||
return i switch
|
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
|
public class EmptyStatementSyntaxNode : StatementSyntaxNode
|
||||||
{
|
{
|
||||||
internal EmptyStatementSyntaxNode(SyntaxNode parent, Internal.GreenNode green, int position): base(parent, green, position)
|
internal EmptyStatementSyntaxNode(SyntaxNode parent, Internal.GreenNode green, int position): base(parent, green, position)
|
||||||
|
@ -83,6 +83,11 @@ namespace Parser
|
|||||||
DefaultVisit(node);
|
DefaultVisit(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public virtual void VisitTrailingSemicolon(TrailingSemicolonSyntaxNode node)
|
||||||
|
{
|
||||||
|
DefaultVisit(node);
|
||||||
|
}
|
||||||
|
|
||||||
public virtual void VisitEmptyStatement(EmptyStatementSyntaxNode node)
|
public virtual void VisitEmptyStatement(EmptyStatementSyntaxNode node)
|
||||||
{
|
{
|
||||||
DefaultVisit(node);
|
DefaultVisit(node);
|
||||||
|
@ -135,6 +135,9 @@
|
|||||||
// a list of syntax nodes and/or tokens.
|
// a list of syntax nodes and/or tokens.
|
||||||
List,
|
List,
|
||||||
|
|
||||||
|
// a semicolon that marks expression statements with discarded results.
|
||||||
|
TrailingSemicolon,
|
||||||
|
|
||||||
|
|
||||||
// STATEMENTS
|
// STATEMENTS
|
||||||
// The name ends with "Declaration" or "Statement".
|
// The name ends with "Declaration" or "Statement".
|
||||||
|
Loading…
x
Reference in New Issue
Block a user