diff options
author | Matthew Olsson <matthewcolsson@gmail.com> | 2020-05-28 13:36:59 -0700 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2020-05-29 16:20:32 +0200 |
commit | d52ea37717eb80388606d2e5945599d00ac0688d (patch) | |
tree | d575e1f80ac654ffc62be73bfbab918aeab8993d /Libraries/LibJS | |
parent | 03615a7872849dc63f0495e779b6ce6f6c09f11c (diff) | |
download | serenity-d52ea37717eb80388606d2e5945599d00ac0688d.zip |
LibJS: Integrate labels into the Interpreter
The interpreter now considers a statement or block's label when
considering whether or not to break. All statements can be labelled.
Diffstat (limited to 'Libraries/LibJS')
-rw-r--r-- | Libraries/LibJS/AST.cpp | 22 | ||||
-rw-r--r-- | Libraries/LibJS/Interpreter.cpp | 5 | ||||
-rw-r--r-- | Libraries/LibJS/Interpreter.h | 14 | ||||
-rw-r--r-- | Libraries/LibJS/Parser.cpp | 12 | ||||
-rw-r--r-- | Libraries/LibJS/Tests/automatic-semicolon-insertion.js | 21 | ||||
-rw-r--r-- | Libraries/LibJS/Tests/labels.js | 34 |
6 files changed, 87 insertions, 21 deletions
diff --git a/Libraries/LibJS/AST.cpp b/Libraries/LibJS/AST.cpp index 55db8784d8..f3cad3a386 100644 --- a/Libraries/LibJS/AST.cpp +++ b/Libraries/LibJS/AST.cpp @@ -282,9 +282,9 @@ Value ForStatement::execute(Interpreter& interpreter) const if (interpreter.exception()) return {}; if (interpreter.should_unwind()) { - if (interpreter.should_unwind_until(ScopeType::Continuable)) { + if (interpreter.should_unwind_until(ScopeType::Continuable, m_label)) { interpreter.stop_unwind(); - } else if (interpreter.should_unwind_until(ScopeType::Breakable)) { + } else if (interpreter.should_unwind_until(ScopeType::Breakable, m_label)) { interpreter.stop_unwind(); break; } else { @@ -303,9 +303,9 @@ Value ForStatement::execute(Interpreter& interpreter) const if (interpreter.exception()) return {}; if (interpreter.should_unwind()) { - if (interpreter.should_unwind_until(ScopeType::Continuable)) { + if (interpreter.should_unwind_until(ScopeType::Continuable, m_label)) { interpreter.stop_unwind(); - } else if (interpreter.should_unwind_until(ScopeType::Breakable)) { + } else if (interpreter.should_unwind_until(ScopeType::Breakable, m_label)) { interpreter.stop_unwind(); break; } else { @@ -370,9 +370,9 @@ Value ForInStatement::execute(Interpreter& interpreter) const if (interpreter.exception()) return {}; if (interpreter.should_unwind()) { - if (interpreter.should_unwind_until(ScopeType::Continuable)) { + if (interpreter.should_unwind_until(ScopeType::Continuable, m_label)) { interpreter.stop_unwind(); - } else if (interpreter.should_unwind_until(ScopeType::Breakable)) { + } else if (interpreter.should_unwind_until(ScopeType::Breakable, m_label)) { interpreter.stop_unwind(); break; } else { @@ -437,9 +437,9 @@ Value ForOfStatement::execute(Interpreter& interpreter) const if (interpreter.exception()) return {}; if (interpreter.should_unwind()) { - if (interpreter.should_unwind_until(ScopeType::Continuable)) { + if (interpreter.should_unwind_until(ScopeType::Continuable, m_label)) { interpreter.stop_unwind(); - } else if (interpreter.should_unwind_until(ScopeType::Breakable)) { + } else if (interpreter.should_unwind_until(ScopeType::Breakable, m_label)) { interpreter.stop_unwind(); break; } else { @@ -1635,7 +1635,7 @@ Value SwitchStatement::execute(Interpreter& interpreter) const if (interpreter.exception()) return {}; if (interpreter.should_unwind()) { - if (interpreter.should_unwind_until(ScopeType::Breakable)) { + if (interpreter.should_unwind_until(ScopeType::Breakable, m_label)) { interpreter.stop_unwind(); return {}; } @@ -1655,13 +1655,13 @@ Value SwitchCase::execute(Interpreter& interpreter) const Value BreakStatement::execute(Interpreter& interpreter) const { - interpreter.unwind(ScopeType::Breakable); + interpreter.unwind(ScopeType::Breakable, m_target_label); return js_undefined(); } Value ContinueStatement::execute(Interpreter& interpreter) const { - interpreter.unwind(ScopeType::Continuable); + interpreter.unwind(ScopeType::Continuable, m_target_label); return js_undefined(); } diff --git a/Libraries/LibJS/Interpreter.cpp b/Libraries/LibJS/Interpreter.cpp index efee9b940a..28b52900f6 100644 --- a/Libraries/LibJS/Interpreter.cpp +++ b/Libraries/LibJS/Interpreter.cpp @@ -72,8 +72,11 @@ Value Interpreter::run(const Statement& statement, ArgumentVector arguments, Sco m_last_value = js_undefined(); for (auto& node : block.children()) { m_last_value = node.execute(*this); - if (m_unwind_until != ScopeType::None) + if (should_unwind()) { + if (should_unwind_until(ScopeType::Breakable, block.label())) + stop_unwind(); break; + } } bool did_return = m_unwind_until == ScopeType::Function; diff --git a/Libraries/LibJS/Interpreter.h b/Libraries/LibJS/Interpreter.h index e0ffbb6722..659d7e67f8 100644 --- a/Libraries/LibJS/Interpreter.h +++ b/Libraries/LibJS/Interpreter.h @@ -91,9 +91,18 @@ public: Heap& heap() { return m_heap; } - void unwind(ScopeType type) { m_unwind_until = type; } + void unwind(ScopeType type, FlyString label = {}) + { + m_unwind_until = type; + m_unwind_until_label = label; + } void stop_unwind() { m_unwind_until = ScopeType::None; } - bool should_unwind_until(ScopeType type) const { return m_unwind_until == type; } + bool should_unwind_until(ScopeType type, FlyString label) const + { + if (m_unwind_until_label.is_null()) + return m_unwind_until == type; + return m_unwind_until == type && m_unwind_until_label == label; + } bool should_unwind() const { return m_unwind_until != ScopeType::None; } Value get_variable(const FlyString& name); @@ -189,6 +198,7 @@ private: Exception* m_exception { nullptr }; ScopeType m_unwind_until { ScopeType::None }; + FlyString m_unwind_until_label; Console m_console; }; diff --git a/Libraries/LibJS/Parser.cpp b/Libraries/LibJS/Parser.cpp index 86c7c70f1d..651bfe2ec4 100644 --- a/Libraries/LibJS/Parser.cpp +++ b/Libraries/LibJS/Parser.cpp @@ -269,9 +269,11 @@ NonnullRefPtr<Statement> Parser::parse_statement() consume(); return create_ast_node<EmptyStatement>(); default: - auto result = try_parse_labelled_statement(); - if (!result.is_null()) - return result.release_nonnull(); + if (match(TokenType::Identifier)) { + auto result = try_parse_labelled_statement(); + if (!result.is_null()) + return result.release_nonnull(); + } if (match_expression()) { auto expr = parse_expression(0); consume_or_insert_semicolon(); @@ -1117,7 +1119,7 @@ NonnullRefPtr<BreakStatement> Parser::parse_break_statement() consume(); return create_ast_node<BreakStatement>(target_label); } - if (match_identifier_name() && !m_parser_state.m_current_token.trivia().contains('\n')) + if (match(TokenType::Identifier) && !m_parser_state.m_current_token.trivia().contains('\n')) target_label = consume().value(); consume_or_insert_semicolon(); return create_ast_node<BreakStatement>(target_label); @@ -1131,7 +1133,7 @@ NonnullRefPtr<ContinueStatement> Parser::parse_continue_statement() consume(); return create_ast_node<ContinueStatement>(target_label); } - if (match_identifier_name() && !m_parser_state.m_current_token.trivia().contains('\n')) + if (match(TokenType::Identifier) && !m_parser_state.m_current_token.trivia().contains('\n')) target_label = consume().value(); consume_or_insert_semicolon(); return create_ast_node<ContinueStatement>(target_label); diff --git a/Libraries/LibJS/Tests/automatic-semicolon-insertion.js b/Libraries/LibJS/Tests/automatic-semicolon-insertion.js index 0673e2bf69..f3c4d3bc00 100644 --- a/Libraries/LibJS/Tests/automatic-semicolon-insertion.js +++ b/Libraries/LibJS/Tests/automatic-semicolon-insertion.js @@ -25,9 +25,13 @@ function bar() { } function foo() { + label: for (var i = 0; i < 4; i++) { break // semicolon inserted here continue // semicolon inserted here + + break label // semicolon inserted here + continue label // semicolon inserted here } var j // semicolon inserted here @@ -39,8 +43,25 @@ function foo() { 1; var curly/* semicolon inserted here */} +function baz() { + let counter = 0; + let outer; + + outer: + for (let i = 0; i < 5; ++i) { + for (let j = 0; j < 5; ++j) { + continue // semicolon inserted here + outer // semicolon inserted here + } + counter++; + } + + return counter; +} + try { assert(foo() === undefined); + assert(baz() === 5); console.log("PASS"); } catch (e) { diff --git a/Libraries/LibJS/Tests/labels.js b/Libraries/LibJS/Tests/labels.js index 21150b296f..4388cac823 100644 --- a/Libraries/LibJS/Tests/labels.js +++ b/Libraries/LibJS/Tests/labels.js @@ -1,12 +1,42 @@ load("test-common.js"); try { - test: - { + test: { let o = 1; assert(o === 1); + break test; + assertNotReached(); } + outer: { + { + break outer; + } + assertNotReached(); + } + + let counter = 0; + outer: + for (a of [1, 2, 3]) { + for (b of [4, 5, 6]) { + if (a === 2 && b === 5) + break outer; + counter++; + } + } + assert(counter === 4); + + let counter = 0; + outer: + for (a of [1, 2, 3]) { + for (b of [4, 5, 6]) { + if (b === 6) + continue outer; + counter++; + } + } + assert(counter === 6); + console.log("PASS"); } catch (e) { console.log("FAIL: " + e); |