From 3d2bcb2ace2462aaee76db35b5472ff29a9f7bd4 Mon Sep 17 00:00:00 2001 From: Preston Taylor <95388976+PrestonLTaylor@users.noreply.github.com> Date: Wed, 17 Apr 2024 19:58:40 +0100 Subject: [PATCH 1/4] bug: Print new line after printing completion in file executor This would cause "Press any key to exit." to be printed on the same line as the completion before this commit --- JSS/FileExecutor.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/JSS/FileExecutor.cs b/JSS/FileExecutor.cs index 3827ac4..849143f 100644 --- a/JSS/FileExecutor.cs +++ b/JSS/FileExecutor.cs @@ -26,6 +26,7 @@ public void ExecuteFile() Print.PrintException(e); } + Console.WriteLine(); Console.WriteLine("Press any key to exit."); Console.ReadKey(); } From c0ea8f2905e0be73eacd4e23f5912eb154493280 Mon Sep 17 00:00:00 2001 From: Preston Taylor <95388976+PrestonLTaylor@users.noreply.github.com> Date: Wed, 17 Apr 2024 20:00:03 +0100 Subject: [PATCH 2/4] bug: Ignore line terminators when parsing JavaScript code We would be unable to parse anything past the first line of a JavaScript file as we would do nothing with line terminators. We now always ignore line terminators when consuming tokens. --- JSS.Lib/TokenConsumer.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/JSS.Lib/TokenConsumer.cs b/JSS.Lib/TokenConsumer.cs index a00979b..21a143e 100644 --- a/JSS.Lib/TokenConsumer.cs +++ b/JSS.Lib/TokenConsumer.cs @@ -15,23 +15,32 @@ public TokenConsumer(List 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(); } + + private void IgnoreLineTerminator() + { + while (CanConsume() && _toConsume[_index].type == TokenType.LineTerminator) ++_index; + } } From 8a17851dcccdd9dad7985f7c66e95fafe53a355c Mon Sep 17 00:00:00 2001 From: Preston Taylor <95388976+PrestonLTaylor@users.noreply.github.com> Date: Wed, 17 Apr 2024 20:16:21 +0100 Subject: [PATCH 3/4] feat: Throw when there is a line terminator before throw expression The spec says that a line terminator isn't allowed before a throw expression. As the expression is required, we throw a syntax error with an appropriate error message. --- JSS.Lib.UnitTests/ParserTests.cs | 14 ++++++++++++++ JSS.Lib/ErrorHelper.cs | 6 ++++-- JSS.Lib/Parser.cs | 4 ++-- JSS.Lib/TokenConsumer.cs | 6 ++++++ 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/JSS.Lib.UnitTests/ParserTests.cs b/JSS.Lib.UnitTests/ParserTests.cs index 9febbd8..3997581 100644 --- a/JSS.Lib.UnitTests/ParserTests.cs +++ b/JSS.Lib.UnitTests/ParserTests.cs @@ -897,6 +897,20 @@ public void Parse_ReturnsThrowStatement_WhenProvidingThrow(KeyValuePair 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}" }, }; } diff --git a/JSS.Lib/Parser.cs b/JSS.Lib/Parser.cs index 40fe6bd..0f50575 100644 --- a/JSS.Lib/Parser.cs +++ b/JSS.Lib/Parser.cs @@ -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); diff --git a/JSS.Lib/TokenConsumer.cs b/JSS.Lib/TokenConsumer.cs index 21a143e..3b39aef 100644 --- a/JSS.Lib/TokenConsumer.cs +++ b/JSS.Lib/TokenConsumer.cs @@ -39,6 +39,12 @@ public Token ConsumeTokenOfType(TokenType type) 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; From c0647441425d88b7c550b5d94aff8ad0420da89e Mon Sep 17 00:00:00 2001 From: Preston Taylor <95388976+PrestonLTaylor@users.noreply.github.com> Date: Wed, 17 Apr 2024 20:20:28 +0100 Subject: [PATCH 4/4] feat: Don't parse expression if a line terminator is after return The spec says that there if there is a line terminator after a return that means an expression shouldn't be parsed. --- JSS.Lib.UnitTests/ParserTests.cs | 18 ++++++++++++++++++ JSS.Lib/Parser.cs | 4 ++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/JSS.Lib.UnitTests/ParserTests.cs b/JSS.Lib.UnitTests/ParserTests.cs index 3997581..95fa88d 100644 --- a/JSS.Lib.UnitTests/ParserTests.cs +++ b/JSS.Lib.UnitTests/ParserTests.cs @@ -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 expressionToExpectedType) { diff --git a/JSS.Lib/Parser.cs b/JSS.Lib/Parser.cs index 0f50575..3e7c487 100644 --- a/JSS.Lib/Parser.cs +++ b/JSS.Lib/Parser.cs @@ -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); }