Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle line terminators appropriately in our parser #10

Merged
merged 4 commits into from
Apr 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions JSS.Lib.UnitTests/ParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -754,6 +754,24 @@ public void Parse_ReturnsReturnStatement_WithNoExpression_WhenProvidingReturn_Wi
returnStatement!.ReturnExpression.Should().BeNull();
}

[Test]
public void Parse_ReturnsReturnStatement_WithNoExpression_WhenProvidingReturn_WithNewLineThenExpression()
{
// Arrange
var parser = new Parser("return\n1");

// Act
var parsedProgram = ParseScript(parser);
var rootNodes = parsedProgram.ScriptCode;

// Assert
rootNodes.Should().HaveCount(2);

var returnStatement = rootNodes[0] as ReturnStatement;
returnStatement.Should().NotBeNull();
returnStatement!.ReturnExpression.Should().BeNull();
}

[TestCaseSource(nameof(expressionToExpectedTypeTestCases))]
public void Parse_ReturnsReturnStatement_WhenProvidingReturn(KeyValuePair<string, Type> expressionToExpectedType)
{
Expand Down Expand Up @@ -897,6 +915,20 @@ public void Parse_ReturnsThrowStatement_WhenProvidingThrow(KeyValuePair<string,
throwStatement!.ThrowExpression.Should().BeOfType(expectedExpressionType);
}

[Test]
public void Parse_ThrowsSyntaxError_WhenProvidingThrow_WithNewLineBeforeExpression()
{
// Arrange
var parser = new Parser($"throw\n1");
var expectedError = ErrorHelper.CreateSyntaxError(ErrorType.IllegalNewLineAfterThrow);

// Act

// Assert
AssertThatSyntaxErrorMatchesExpected(parser, expectedError);
}


// Tests for 14.15 The try Statement, https://tc39.es/ecma262/#sec-try-statement
[Test]
public void Parse_ReturnsTryStatement_WithParameterlessCatch_WhenProvidingTry_WithParameterlessCatch()
Expand Down
6 changes: 4 additions & 2 deletions JSS.Lib/ErrorHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
internal enum ErrorType
{
ConstWithoutInitializer,
IllegalNewLineAfterThrow,
TryWithoutCatchOrFinally,
UnaryLHSOfExponentiation,
UnexpectedEOF,
Expand Down Expand Up @@ -35,10 +36,11 @@ static public SyntaxErrorException CreateSyntaxError(ErrorType type, params obje
static private readonly Dictionary<ErrorType, string> errorTypeToFormatString = new()
{
{ ErrorType.ConstWithoutInitializer, "SyntaxError: Missing initializer in const declaration" },
{ ErrorType.IllegalNewLineAfterThrow, "SyntaxError: Illegal newline after throw" },
{ ErrorType.TryWithoutCatchOrFinally, "SyntaxError: Try statement without catch or finally blocks" },
{ ErrorType.UnaryLHSOfExponentiation, "SyntaxError: Unary operator as lhs of exponentiation expression. Consider using parenthesis to disambiguate precidence" },
{ ErrorType.UnexpectedToken, "SyntaxError: Unexpected token '{0}'" },
{ ErrorType.UnexpectedEOF, "SyntaxError: Unexpected end of file" },
{ ErrorType.UnknownSyntaxError, "SyntaxError: Unknown SyntaxError of type {0}" }
{ ErrorType.UnexpectedToken, "SyntaxError: Unexpected token '{0}'" },
{ ErrorType.UnknownSyntaxError, "SyntaxError: Unknown SyntaxError of type {0}" },
};
}
8 changes: 4 additions & 4 deletions JSS.Lib/Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1705,8 +1705,8 @@ private ReturnStatement ParseReturnStatement()
{
_consumer.ConsumeTokenOfType(TokenType.Return);

// FIXME: return [no LineTerminator here] Expression[+In, ?Yield, ?Await] ;
// Don't parse an expression if there is a line terminator after the return
if (_consumer.IsLineTerminator()) return new ReturnStatement(null);

TryParseExpression(out IExpression? returnExpression);
return new ReturnStatement(returnExpression);
}
Expand Down Expand Up @@ -1804,8 +1804,8 @@ private ThrowStatement ParseThrowStatement()
{
_consumer.ConsumeTokenOfType(TokenType.Throw);

// FIXME: throw [no LineTerminator here] Expression[+In, ?Yield, ?Await] ;
// Don't parse an expression if there is a line terminator after the return
if (_consumer.IsLineTerminator()) ErrorHelper.ThrowSyntaxError(ErrorType.IllegalNewLineAfterThrow);

var throwExpression = ParseExpression();

return new ThrowStatement(throwExpression);
Expand Down
15 changes: 15 additions & 0 deletions JSS.Lib/TokenConsumer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,38 @@ public TokenConsumer(List<Token> tokens)

public Token Consume()
{
IgnoreLineTerminator();
return _toConsume[_index++];
}

public Token Peek(int offset = 0)
{
IgnoreLineTerminator();
return _toConsume[_index + offset];
}

public bool IsTokenOfType(TokenType type)
{
IgnoreLineTerminator();
return CanConsume() && Peek().type == type;
}

public Token ConsumeTokenOfType(TokenType type)
{
IgnoreLineTerminator();
if (!CanConsume()) ErrorHelper.ThrowSyntaxError(ErrorType.UnexpectedEOF);
if (!IsTokenOfType(type)) ErrorHelper.ThrowSyntaxError(ErrorType.UnexpectedToken, Peek().data);
return Consume();
}

public bool IsLineTerminator()
{
if (!CanConsume()) return false;
return _toConsume[_index].type == TokenType.LineTerminator;
}

private void IgnoreLineTerminator()
{
while (CanConsume() && _toConsume[_index].type == TokenType.LineTerminator) ++_index;
}
}
1 change: 1 addition & 0 deletions JSS/FileExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public void ExecuteFile()
Print.PrintException(e);
}

Console.WriteLine();
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
Expand Down
Loading