diff --git a/Libraries/LibJS/AST.cpp b/Libraries/LibJS/AST.cpp index 6dc50d51ae..52d8e007ff 100644 --- a/Libraries/LibJS/AST.cpp +++ b/Libraries/LibJS/AST.cpp @@ -215,6 +215,16 @@ Value ForStatement::execute(Interpreter& interpreter) const last_value = interpreter.run(*m_body); if (interpreter.exception()) return {}; + if (interpreter.should_unwind()) { + if (interpreter.should_unwind_until(ScopeType::Continuable)) { + interpreter.stop_unwind(); + } else if (interpreter.should_unwind_until(ScopeType::Breakable)) { + interpreter.stop_unwind(); + break; + } else { + return {}; + } + } if (m_update) { m_update->execute(interpreter); if (interpreter.exception()) @@ -226,6 +236,16 @@ Value ForStatement::execute(Interpreter& interpreter) const last_value = interpreter.run(*m_body); if (interpreter.exception()) return {}; + if (interpreter.should_unwind()) { + if (interpreter.should_unwind_until(ScopeType::Continuable)) { + interpreter.stop_unwind(); + } else if (interpreter.should_unwind_until(ScopeType::Breakable)) { + interpreter.stop_unwind(); + break; + } else { + return {}; + } + } if (m_update) { m_update->execute(interpreter); if (interpreter.exception()) @@ -759,7 +779,7 @@ Value VariableDeclaration::execute(Interpreter& interpreter) const return {}; } -Value VariableDeclarator::execute(Interpreter &) const +Value VariableDeclarator::execute(Interpreter&) const { // NOTE: This node is handled by VariableDeclaration. ASSERT_NOT_REACHED(); @@ -1005,6 +1025,12 @@ Value BreakStatement::execute(Interpreter& interpreter) const return {}; } +Value ContinueStatement::execute(Interpreter& interpreter) const +{ + interpreter.unwind(ScopeType::Continuable); + return {}; +} + void SwitchStatement::dump(int indent) const { ASTNode::dump(indent); diff --git a/Libraries/LibJS/AST.h b/Libraries/LibJS/AST.h index 56f4e355f5..0c0dfc3dc2 100644 --- a/Libraries/LibJS/AST.h +++ b/Libraries/LibJS/AST.h @@ -814,4 +814,14 @@ private: virtual const char* class_name() const override { return "BreakStatement"; } }; +class ContinueStatement final : public Statement { +public: + ContinueStatement() {} + + virtual Value execute(Interpreter&) const override; + +private: + virtual const char* class_name() const override { return "ContinueStatement"; } +}; + } diff --git a/Libraries/LibJS/Interpreter.h b/Libraries/LibJS/Interpreter.h index 428d63253c..d37fb1e396 100644 --- a/Libraries/LibJS/Interpreter.h +++ b/Libraries/LibJS/Interpreter.h @@ -43,6 +43,7 @@ enum class ScopeType { Block, Try, Breakable, + Continuable, }; struct Variable { diff --git a/Libraries/LibJS/Lexer.cpp b/Libraries/LibJS/Lexer.cpp index 0c4a36e416..94bf21ccc1 100644 --- a/Libraries/LibJS/Lexer.cpp +++ b/Libraries/LibJS/Lexer.cpp @@ -48,6 +48,7 @@ Lexer::Lexer(StringView source) s_keywords.set("catch", TokenType::Catch); s_keywords.set("class", TokenType::Class); s_keywords.set("const", TokenType::Const); + s_keywords.set("continue", TokenType::Continue); s_keywords.set("default", TokenType::Default); s_keywords.set("delete", TokenType::Delete); s_keywords.set("do", TokenType::Do); diff --git a/Libraries/LibJS/Parser.cpp b/Libraries/LibJS/Parser.cpp index 682039ad51..4b074eb7e5 100644 --- a/Libraries/LibJS/Parser.cpp +++ b/Libraries/LibJS/Parser.cpp @@ -208,6 +208,8 @@ NonnullRefPtr Parser::parse_statement() return parse_try_statement(); case TokenType::Break: return parse_break_statement(); + case TokenType::Continue: + return parse_continue_statement(); case TokenType::Switch: return parse_switch_statement(); case TokenType::Do: @@ -686,6 +688,13 @@ NonnullRefPtr Parser::parse_break_statement() return create_ast_node(); } +NonnullRefPtr Parser::parse_continue_statement() +{ + consume(TokenType::Continue); + // FIXME: Handle labels. + return create_ast_node(); +} + NonnullRefPtr Parser::parse_conditional_expression(NonnullRefPtr test) { consume(TokenType::QuestionMark); @@ -938,6 +947,7 @@ bool Parser::match_statement() const || type == TokenType::CurlyOpen || type == TokenType::Switch || type == TokenType::Break + || type == TokenType::Continue || type == TokenType::Var; } diff --git a/Libraries/LibJS/Parser.h b/Libraries/LibJS/Parser.h index 04bfcc85c9..5b4ed28891 100644 --- a/Libraries/LibJS/Parser.h +++ b/Libraries/LibJS/Parser.h @@ -58,6 +58,7 @@ public: NonnullRefPtr parse_switch_statement(); NonnullRefPtr parse_switch_case(); NonnullRefPtr parse_break_statement(); + NonnullRefPtr parse_continue_statement(); NonnullRefPtr parse_do_while_statement(); NonnullRefPtr parse_conditional_expression(NonnullRefPtr test); diff --git a/Libraries/LibJS/Tests/continue-basic.js b/Libraries/LibJS/Tests/continue-basic.js new file mode 100644 index 0000000000..eda0648f08 --- /dev/null +++ b/Libraries/LibJS/Tests/continue-basic.js @@ -0,0 +1,14 @@ +function assert(x) { if (!x) throw 1; } + +try { + var j = 0; + for (var i = 0; i < 9; ++i) { + if (i == 3) + continue; + ++j; + } + assert(j == 8); + console.log("PASS"); +} catch { +} + diff --git a/Libraries/LibJS/Token.h b/Libraries/LibJS/Token.h index 2291fc1965..cbd8c5199a 100644 --- a/Libraries/LibJS/Token.h +++ b/Libraries/LibJS/Token.h @@ -50,6 +50,7 @@ namespace JS { __ENUMERATE_JS_TOKEN(Colon) \ __ENUMERATE_JS_TOKEN(Comma) \ __ENUMERATE_JS_TOKEN(Const) \ + __ENUMERATE_JS_TOKEN(Continue) \ __ENUMERATE_JS_TOKEN(CurlyClose) \ __ENUMERATE_JS_TOKEN(CurlyOpen) \ __ENUMERATE_JS_TOKEN(Default) \