1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 18:07:34 +00:00

LibJS: Allow multiple labels on the same statement

Since there are only a number of statements where labels can actually be
used we now also only store labels when necessary.
Also now tracks the first continue usage of a label since this might not
be valid but that can only be determined after we have parsed the
statement.
Also ensures the correct error does not get wiped by load_state.
This commit is contained in:
davidot 2021-09-18 23:01:54 +02:00 committed by Linus Groh
parent bfc1b4ba61
commit 79caca8ca2
7 changed files with 194 additions and 47 deletions

View file

@ -586,7 +586,12 @@ RefPtr<Statement> Parser::try_parse_labelled_statement(AllowLabelledFunction all
return {};
}
auto identifier = consume_identifier_reference().value();
auto identifier = [&] {
if (m_state.current_token.value() == "await"sv) {
return consume().value();
}
return consume_identifier_reference().value();
}();
if (!match(TokenType::Colon))
return {};
consume(TokenType::Colon);
@ -594,6 +599,14 @@ RefPtr<Statement> Parser::try_parse_labelled_statement(AllowLabelledFunction all
if (!match_statement())
return {};
state_rollback_guard.disarm();
discard_saved_state();
if (m_state.strict_mode && identifier == "let"sv) {
syntax_error("Strict mode reserved word 'let' is not allowed in label", rule_start.position());
return {};
}
if (match(TokenType::Function) && (allow_function == AllowLabelledFunction::No || m_state.strict_mode)) {
syntax_error("Not allowed to declare a function here");
return {};
@ -604,8 +617,10 @@ RefPtr<Statement> Parser::try_parse_labelled_statement(AllowLabelledFunction all
RefPtr<Statement> labelled_statement;
auto is_iteration_statement = false;
if (match(TokenType::Function)) {
m_state.labels_in_scope.set(identifier, false);
m_state.labels_in_scope.set(identifier, {});
auto function_declaration = parse_function_node<FunctionDeclaration>();
m_state.current_scope->function_declarations.append(function_declaration);
auto hoisting_target = m_state.current_scope->get_current_function_scope();
@ -615,16 +630,23 @@ RefPtr<Statement> Parser::try_parse_labelled_statement(AllowLabelledFunction all
labelled_statement = move(function_declaration);
} else {
auto is_iteration_statement = match(TokenType::For) || match(TokenType::Do) || match(TokenType::While);
m_state.labels_in_scope.set(identifier, is_iteration_statement);
labelled_statement = parse_statement();
m_state.labels_in_scope.set(identifier, {});
labelled_statement = parse_statement(allow_function);
if (is<IterationStatement>(*labelled_statement)) {
is_iteration_statement = true;
static_cast<IterationStatement&>(*labelled_statement).add_label(identifier);
} else if (is<LabelableStatement>(*labelled_statement)) {
static_cast<LabelableStatement&>(*labelled_statement).add_label(identifier);
}
}
if (!is_iteration_statement) {
if (auto entry = m_state.labels_in_scope.find(identifier); entry != m_state.labels_in_scope.end() && entry->value.has_value())
syntax_error("labelled continue statement cannot use non iterating statement", m_state.labels_in_scope.get(identifier).value());
}
m_state.labels_in_scope.remove(identifier);
labelled_statement->add_label(identifier);
state_rollback_guard.disarm();
discard_saved_state();
return labelled_statement.release_nonnull();
}
@ -2376,7 +2398,7 @@ NonnullRefPtr<BreakStatement> Parser::parse_break_statement()
if (match(TokenType::Semicolon)) {
consume();
} else {
if (match(TokenType::Identifier) && !m_state.current_token.trivia_contains_line_terminator()) {
if (!m_state.current_token.trivia_contains_line_terminator() && match_identifier()) {
target_label = consume().value();
auto label = m_state.labels_in_scope.find(target_label);
@ -2404,12 +2426,15 @@ NonnullRefPtr<ContinueStatement> Parser::parse_continue_statement()
consume();
return create_ast_node<ContinueStatement>({ m_state.current_token.filename(), rule_start.position(), position() }, target_label);
}
if (match(TokenType::Identifier) && !m_state.current_token.trivia_contains_line_terminator()) {
if (!m_state.current_token.trivia_contains_line_terminator() && match_identifier()) {
auto label_position = position();
target_label = consume().value();
auto label = m_state.labels_in_scope.find(target_label);
if (label == m_state.labels_in_scope.end() || !label->value)
if (label == m_state.labels_in_scope.end())
syntax_error(String::formatted("Label '{}' not found or invalid", target_label));
else
label->value = label_position;
}
consume_or_insert_semicolon();
return create_ast_node<ContinueStatement>({ m_state.current_token.filename(), rule_start.position(), position() }, target_label);