diff --git a/Userland/Libraries/LibJS/AST.cpp b/Userland/Libraries/LibJS/AST.cpp index ba34ce824a..2499b0adb1 100644 --- a/Userland/Libraries/LibJS/AST.cpp +++ b/Userland/Libraries/LibJS/AST.cpp @@ -438,9 +438,8 @@ Value ForStatement::execute(Interpreter& interpreter, GlobalObject& global_objec return last_value; } -static FlyString variable_from_for_declaration(Interpreter& interpreter, GlobalObject& global_object, const ASTNode& node, RefPtr wrapper) +static Variant, NonnullRefPtr> variable_from_for_declaration(Interpreter& interpreter, GlobalObject& global_object, const ASTNode& node, RefPtr wrapper) { - FlyString variable_name; if (is(node)) { auto& variable_declaration = static_cast(node); VERIFY(!variable_declaration.declarations().is_empty()); @@ -449,13 +448,14 @@ static FlyString variable_from_for_declaration(Interpreter& interpreter, GlobalO interpreter.enter_scope(*wrapper, ScopeType::Block, global_object); } variable_declaration.execute(interpreter, global_object); - variable_name = variable_declaration.declarations().first().id().string(); - } else if (is(node)) { - variable_name = static_cast(node).string(); - } else { - VERIFY_NOT_REACHED(); + return variable_declaration.declarations().first().target(); } - return variable_name; + + if (is(node)) { + return NonnullRefPtr(static_cast(node)); + } + + VERIFY_NOT_REACHED(); } Value ForInStatement::execute(Interpreter& interpreter, GlobalObject& global_object) const @@ -468,7 +468,7 @@ Value ForInStatement::execute(Interpreter& interpreter, GlobalObject& global_obj VERIFY_NOT_REACHED(); } RefPtr wrapper; - auto variable_name = variable_from_for_declaration(interpreter, global_object, m_lhs, wrapper); + auto target = variable_from_for_declaration(interpreter, global_object, m_lhs, wrapper); auto wrapper_cleanup = ScopeGuard([&] { if (wrapper) interpreter.exit_scope(*wrapper); @@ -483,7 +483,7 @@ Value ForInStatement::execute(Interpreter& interpreter, GlobalObject& global_obj while (object) { auto property_names = object->get_enumerable_own_property_names(Object::PropertyKind::Key); for (auto& value : property_names) { - interpreter.vm().set_variable(variable_name, value, global_object, has_declaration); + interpreter.vm().assign(target, value, global_object, has_declaration); if (interpreter.exception()) return {}; last_value = interpreter.execute_statement(global_object, *m_body).value_or(last_value); @@ -517,7 +517,7 @@ Value ForOfStatement::execute(Interpreter& interpreter, GlobalObject& global_obj VERIFY_NOT_REACHED(); } RefPtr wrapper; - auto variable_name = variable_from_for_declaration(interpreter, global_object, m_lhs, wrapper); + auto target = variable_from_for_declaration(interpreter, global_object, m_lhs, wrapper); auto wrapper_cleanup = ScopeGuard([&] { if (wrapper) interpreter.exit_scope(*wrapper); @@ -528,7 +528,7 @@ Value ForOfStatement::execute(Interpreter& interpreter, GlobalObject& global_obj return {}; get_iterator_values(global_object, rhs_result, [&](Value value) { - interpreter.vm().set_variable(variable_name, value, global_object, has_declaration); + interpreter.vm().assign(target, value, global_object, has_declaration); last_value = interpreter.execute_statement(global_object, *m_body).value_or(last_value); if (interpreter.exception()) return IterationDecision::Break; @@ -1122,6 +1122,36 @@ void NullLiteral::dump(int indent) const outln("null"); } +void BindingPattern::dump(int indent) const +{ + print_indent(indent); + outln("BindingPattern {}", kind == Kind::Array ? "Array" : "Object"); + print_indent(++indent); + outln("(Properties)"); + for (auto& property : properties) { + print_indent(indent + 1); + outln("(Identifier)"); + if (property.name) { + property.name->dump(indent + 2); + } else { + print_indent(indent + 2); + outln("(None)"); + } + + print_indent(indent + 1); + outln("(Pattern)"); + if (property.pattern) { + property.pattern->dump(indent + 2); + } else { + print_indent(indent + 2); + outln("(None)"); + } + + print_indent(indent + 1); + outln("(Is Rest = {})", property.is_rest); + } +} + void FunctionNode::dump(int indent, const String& class_name) const { print_indent(indent); @@ -1134,7 +1164,13 @@ void FunctionNode::dump(int indent, const String& class_name) const print_indent(indent + 2); if (parameter.is_rest) out("..."); - outln("{}", parameter.name); + parameter.binding.visit( + [&](const FlyString& name) { + outln("{}", name); + }, + [&](const BindingPattern& pattern) { + pattern.dump(indent + 2); + }); if (parameter.default_value) parameter.default_value->dump(indent + 3); } @@ -1543,10 +1579,16 @@ Value VariableDeclaration::execute(Interpreter& interpreter, GlobalObject& globa auto initalizer_result = init->execute(interpreter, global_object); if (interpreter.exception()) return {}; - auto variable_name = declarator.id().string(); - if (is(*init)) - update_function_name(initalizer_result, variable_name); - interpreter.vm().set_variable(variable_name, initalizer_result, global_object, true); + declarator.target().visit( + [&](const NonnullRefPtr& id) { + auto variable_name = id->string(); + if (is(*init)) + update_function_name(initalizer_result, variable_name); + interpreter.vm().set_variable(variable_name, initalizer_result, global_object, true); + }, + [&](const NonnullRefPtr& pattern) { + interpreter.vm().assign(pattern, initalizer_result, global_object, true); + }); } } return {}; @@ -1586,7 +1628,7 @@ void VariableDeclaration::dump(int indent) const void VariableDeclarator::dump(int indent) const { ASTNode::dump(indent); - m_id->dump(indent + 1); + m_target.visit([indent](const auto& value) { value->dump(indent + 1); }); if (m_init) m_init->dump(indent + 1); } diff --git a/Userland/Libraries/LibJS/AST.h b/Userland/Libraries/LibJS/AST.h index 5d6f472f29..74471caa4c 100644 --- a/Userland/Libraries/LibJS/AST.h +++ b/Userland/Libraries/LibJS/AST.h @@ -10,8 +10,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -22,6 +24,7 @@ namespace JS { class VariableDeclaration; class FunctionDeclaration; +class Identifier; template static inline NonnullRefPtr @@ -185,10 +188,32 @@ public: Value execute(Interpreter&, GlobalObject&) const override { return {}; } }; +struct BindingPattern : RefCounted { + struct BindingProperty { + RefPtr name; + RefPtr alias; + RefPtr pattern; + RefPtr initializer; + bool is_rest { false }; + }; + + enum class Kind { + Array, + Object, + }; + + void dump(int indent) const; + template + void for_each_assigned_name(C&& callback) const; + + Vector properties; + Kind kind { Kind::Object }; +}; + class FunctionNode { public: struct Parameter { - FlyString name; + Variant> binding; RefPtr default_value; bool is_rest { false }; }; @@ -907,25 +932,32 @@ class VariableDeclarator final : public ASTNode { public: VariableDeclarator(SourceRange source_range, NonnullRefPtr id) : ASTNode(move(source_range)) - , m_id(move(id)) + , m_target(move(id)) { } - VariableDeclarator(SourceRange source_range, NonnullRefPtr id, RefPtr init) + VariableDeclarator(SourceRange source_range, NonnullRefPtr target, RefPtr init) : ASTNode(move(source_range)) - , m_id(move(id)) + , m_target(move(target)) , m_init(move(init)) { } - const Identifier& id() const { return m_id; } + VariableDeclarator(SourceRange source_range, Variant, NonnullRefPtr> target, RefPtr init) + : ASTNode(move(source_range)) + , m_target(move(target)) + , m_init(move(init)) + { + } + + auto& target() const { return m_target; } const Expression* init() const { return m_init; } virtual Value execute(Interpreter&, GlobalObject&) const override; virtual void dump(int indent) const override; private: - NonnullRefPtr m_id; + Variant, NonnullRefPtr> m_target; RefPtr m_init; }; @@ -1269,4 +1301,16 @@ public: virtual Value execute(Interpreter&, GlobalObject&) const override; }; +template +void BindingPattern::for_each_assigned_name(C&& callback) const +{ + for (auto& property : properties) { + if (property.name) { + callback(property.name->string()); + continue; + } + property.pattern->template for_each_assigned_name(forward(callback)); + } +} + } diff --git a/Userland/Libraries/LibJS/Interpreter.cpp b/Userland/Libraries/LibJS/Interpreter.cpp index 7044957c19..eb5a45f03a 100644 --- a/Userland/Libraries/LibJS/Interpreter.cpp +++ b/Userland/Libraries/LibJS/Interpreter.cpp @@ -102,11 +102,27 @@ void Interpreter::enter_scope(const ScopeNode& scope_node, ScopeType scope_type, for (auto& declaration : scope_node.variables()) { for (auto& declarator : declaration.declarations()) { if (is(scope_node)) { - global_object.put(declarator.id().string(), js_undefined()); + declarator.target().visit( + [&](const NonnullRefPtr& id) { + global_object.put(id->string(), js_undefined()); + }, + [&](const NonnullRefPtr& binding) { + binding->for_each_assigned_name([&](const auto& name) { + global_object.put(name, js_undefined()); + }); + }); if (exception()) return; } else { - scope_variables_with_declaration_kind.set(declarator.id().string(), { js_undefined(), declaration.declaration_kind() }); + declarator.target().visit( + [&](const NonnullRefPtr& id) { + scope_variables_with_declaration_kind.set(id->string(), { js_undefined(), declaration.declaration_kind() }); + }, + [&](const NonnullRefPtr& binding) { + binding->for_each_assigned_name([&](const auto& name) { + scope_variables_with_declaration_kind.set(name, { js_undefined(), declaration.declaration_kind() }); + }); + }); } } } diff --git a/Userland/Libraries/LibJS/Parser.cpp b/Userland/Libraries/LibJS/Parser.cpp index 17844cf86d..1364c207bd 100644 --- a/Userland/Libraries/LibJS/Parser.cpp +++ b/Userland/Libraries/LibJS/Parser.cpp @@ -359,7 +359,7 @@ RefPtr Parser::try_parse_arrow_function_expression(bool expe // check if it's about a wrong token (something like duplicate parameter name must // not abort), know parsing failed and rollback the parser state. auto previous_syntax_errors = m_parser_state.m_errors.size(); - parameters = parse_function_parameters(function_length, FunctionNodeParseOptions::IsArrowFunction); + parameters = parse_formal_parameters(function_length, FunctionNodeParseOptions::IsArrowFunction); if (m_parser_state.m_errors.size() > previous_syntax_errors && m_parser_state.m_errors[previous_syntax_errors].message.starts_with("Unexpected token")) return nullptr; if (!match(TokenType::ParenClose)) @@ -369,7 +369,7 @@ RefPtr Parser::try_parse_arrow_function_expression(bool expe // No parens - this must be an identifier followed by arrow. That's it. if (!match(TokenType::Identifier)) return nullptr; - parameters.append({ consume().value(), {} }); + parameters.append({ FlyString { consume().value() }, {} }); } // If there's a newline between the closing paren and arrow it's not a valid arrow function, // ASI should kick in instead (it'll then fail with "Unexpected token Arrow") @@ -590,7 +590,7 @@ NonnullRefPtr Parser::parse_class_expression(bool expect_class_ constructor_body->append(create_ast_node({ m_parser_state.m_current_token.filename(), rule_start.position(), position() }, move(super_call))); constructor_body->add_variables(m_parser_state.m_var_scopes.last()); - constructor = create_ast_node({ m_parser_state.m_current_token.filename(), rule_start.position(), position() }, class_name, move(constructor_body), Vector { FunctionNode::Parameter { "args", nullptr, true } }, 0, NonnullRefPtrVector(), true); + constructor = create_ast_node({ m_parser_state.m_current_token.filename(), rule_start.position(), position() }, class_name, move(constructor_body), Vector { FunctionNode::Parameter { FlyString { "args" }, nullptr, true } }, 0, NonnullRefPtrVector(), true); } else { constructor = create_ast_node({ m_parser_state.m_current_token.filename(), rule_start.position(), position() }, class_name, move(constructor_body), Vector {}, 0, NonnullRefPtrVector(), true); } @@ -1344,7 +1344,7 @@ NonnullRefPtr Parser::parse_function_node(u8 parse_options) } consume(TokenType::ParenOpen); i32 function_length = -1; - auto parameters = parse_function_parameters(function_length, parse_options); + auto parameters = parse_formal_parameters(function_length, parse_options); consume(TokenType::ParenClose); if (function_length == -1) @@ -1363,7 +1363,7 @@ NonnullRefPtr Parser::parse_function_node(u8 parse_options) return create_ast_node({ m_parser_state.m_current_token.filename(), rule_start.position(), position() }, name, move(body), move(parameters), function_length, NonnullRefPtrVector(), is_strict); } -Vector Parser::parse_function_parameters(int& function_length, u8 parse_options) +Vector Parser::parse_formal_parameters(int& function_length, u8 parse_options) { auto rule_start = push_start(); bool has_default_parameter = false; @@ -1371,12 +1371,15 @@ Vector Parser::parse_function_parameters(int& function_ Vector parameters; - auto consume_and_validate_identifier = [&]() -> Token { + auto consume_identifier_or_binding_pattern = [&]() -> Variant> { + if (auto pattern = parse_binding_pattern()) + return pattern.release_nonnull(); + auto token = consume(TokenType::Identifier); auto parameter_name = token.value(); for (auto& parameter : parameters) { - if (parameter_name != parameter.name) + if (auto* ptr = parameter.binding.get_pointer(); !ptr || parameter_name != *ptr) continue; String message; if (parse_options & FunctionNodeParseOptions::IsArrowFunction) @@ -1391,23 +1394,22 @@ Vector Parser::parse_function_parameters(int& function_ syntax_error(message, Position { token.line_number(), token.line_column() }); break; } - return token; + return FlyString { token.value() }; }; - while (match(TokenType::Identifier) || match(TokenType::TripleDot)) { + while (match(TokenType::CurlyOpen) || match(TokenType::BracketOpen) || match(TokenType::Identifier) || match(TokenType::TripleDot)) { if (parse_options & FunctionNodeParseOptions::IsGetterFunction) syntax_error("Getter function must have no arguments"); if (parse_options & FunctionNodeParseOptions::IsSetterFunction && (parameters.size() >= 1 || match(TokenType::TripleDot))) syntax_error("Setter function must have one argument"); + auto is_rest = false; if (match(TokenType::TripleDot)) { consume(); has_rest_parameter = true; - auto parameter_name = consume_and_validate_identifier().value(); function_length = parameters.size(); - parameters.append({ parameter_name, nullptr, true }); - break; + is_rest = true; } - auto parameter_name = consume_and_validate_identifier().value(); + auto parameter = consume_identifier_or_binding_pattern(); RefPtr default_value; if (match(TokenType::Equals)) { consume(); @@ -1415,16 +1417,144 @@ Vector Parser::parse_function_parameters(int& function_ function_length = parameters.size(); default_value = parse_expression(2); } - parameters.append({ parameter_name, default_value }); + parameters.append({ move(parameter), default_value, is_rest }); if (match(TokenType::ParenClose)) break; consume(TokenType::Comma); + if (is_rest) + break; } if (parse_options & FunctionNodeParseOptions::IsSetterFunction && parameters.is_empty()) syntax_error("Setter function must have one argument"); return parameters; } +RefPtr Parser::parse_binding_pattern() +{ + auto rule_start = push_start(); + + auto pattern_ptr = adopt_ref(*new BindingPattern); + auto& pattern = *pattern_ptr; + TokenType closing_token; + auto allow_named_property = false; + auto elide_extra_commas = false; + auto allow_nested_pattern = false; + + if (match(TokenType::BracketOpen)) { + consume(); + pattern.kind = BindingPattern::Kind::Array; + closing_token = TokenType::BracketClose; + elide_extra_commas = true; + allow_nested_pattern = true; + } else if (match(TokenType::CurlyOpen)) { + consume(); + pattern.kind = BindingPattern::Kind::Object; + closing_token = TokenType::CurlyClose; + allow_named_property = true; + } else { + return {}; + } + + while (!match(closing_token)) { + if (elide_extra_commas && match(TokenType::Comma)) + consume(); + + ScopeGuard consume_commas { [&] { + if (match(TokenType::Comma)) + consume(); + } }; + + auto is_rest = false; + + if (match(TokenType::TripleDot)) { + consume(); + is_rest = true; + } + + if (match(TokenType::Identifier)) { + auto identifier_start = position(); + auto token = consume(TokenType::Identifier); + auto name = create_ast_node( + { m_parser_state.m_current_token.filename(), identifier_start, position() }, + token.value()); + + if (!is_rest && allow_named_property && match(TokenType::Colon)) { + consume(); + if (!match(TokenType::Identifier)) { + syntax_error("Expected a binding pattern as the value of a named element in destructuring object"); + } else { + auto identifier_start = position(); + auto token = consume(TokenType::Identifier); + auto alias_name = create_ast_node( + { m_parser_state.m_current_token.filename(), identifier_start, position() }, + token.value()); + pattern.properties.append(BindingPattern::BindingProperty { + .name = move(name), + .alias = move(alias_name), + .pattern = nullptr, + .initializer = nullptr, + .is_rest = false, + }); + } + continue; + } + + RefPtr initializer; + if (match(TokenType::Equals)) { + consume(); + initializer = parse_expression(2); + } + pattern.properties.append(BindingPattern::BindingProperty { + .name = move(name), + .alias = nullptr, + .pattern = nullptr, + .initializer = move(initializer), + .is_rest = is_rest, + }); + if (is_rest) + break; + continue; + } + + if (allow_nested_pattern) { + auto binding_pattern = parse_binding_pattern(); + if (!binding_pattern) { + if (is_rest) + syntax_error("Expected a binding pattern after ... in destructuring list"); + else + syntax_error("Expected a binding pattern or identifier in destructuring list"); + } else { + RefPtr initializer; + if (match(TokenType::Equals)) { + consume(); + initializer = parse_expression(2); + } + pattern.properties.append(BindingPattern::BindingProperty { + .name = nullptr, + .alias = nullptr, + .pattern = move(binding_pattern), + .initializer = move(initializer), + .is_rest = is_rest, + }); + if (is_rest) + break; + continue; + } + + continue; + } + + break; + } + + while (elide_extra_commas && match(TokenType::Comma)) + consume(); + + consume(closing_token); + + return pattern; +} + NonnullRefPtr Parser::parse_variable_declaration(bool for_loop_variable_declaration) { auto rule_start = push_start(); @@ -1447,19 +1577,49 @@ NonnullRefPtr Parser::parse_variable_declaration(bool for_l NonnullRefPtrVector declarations; for (;;) { - auto id = consume(TokenType::Identifier).value(); + Variant, NonnullRefPtr, Empty> target { Empty() }; + if (match(TokenType::Identifier)) { + target = create_ast_node( + { m_parser_state.m_current_token.filename(), rule_start.position(), position() }, + consume(TokenType::Identifier).value()); + } else if (match(TokenType::TripleDot)) { + consume(); + if (auto pattern = parse_binding_pattern()) + target = pattern.release_nonnull(); + else + syntax_error("Expected a binding pattern after ... in variable declaration"); + } else if (auto pattern = parse_binding_pattern()) { + target = pattern.release_nonnull(); + } + + if (target.has()) { + syntax_error("Expected an identifer or a binding pattern"); + if (match(TokenType::Comma)) { + consume(); + continue; + } + break; + } + RefPtr init; if (match(TokenType::Equals)) { consume(); init = parse_expression(2); } else if (!for_loop_variable_declaration && declaration_kind == DeclarationKind::Const) { syntax_error("Missing initializer in 'const' variable declaration"); + } else if (target.has()) { + syntax_error("Missing initializer in destructuring assignment"); } - auto identifier = create_ast_node({ m_parser_state.m_current_token.filename(), rule_start.position(), position() }, move(id)); - if (init && is(*init)) { - static_cast(*init).set_name_if_possible(id); + + if (init && is(*init) && target.has>()) { + static_cast(*init).set_name_if_possible(target.get>()->string()); } - declarations.append(create_ast_node({ m_parser_state.m_current_token.filename(), rule_start.position(), position() }, move(identifier), move(init))); + + declarations.append(create_ast_node( + { m_parser_state.m_current_token.filename(), rule_start.position(), position() }, + move(target).downcast, NonnullRefPtr>(), + move(init))); + if (match(TokenType::Comma)) { consume(); continue; diff --git a/Userland/Libraries/LibJS/Parser.h b/Userland/Libraries/LibJS/Parser.h index 8add7c272d..85000c7a1e 100644 --- a/Userland/Libraries/LibJS/Parser.h +++ b/Userland/Libraries/LibJS/Parser.h @@ -40,7 +40,8 @@ public: template NonnullRefPtr parse_function_node(u8 parse_options = FunctionNodeParseOptions::CheckForFunctionAndName); - Vector parse_function_parameters(int& function_length, u8 parse_options = 0); + Vector parse_formal_parameters(int& function_length, u8 parse_options = 0); + RefPtr parse_binding_pattern(); NonnullRefPtr parse_declaration(); NonnullRefPtr parse_statement(); diff --git a/Userland/Libraries/LibJS/Runtime/FunctionPrototype.cpp b/Userland/Libraries/LibJS/Runtime/FunctionPrototype.cpp index 7594d88bdf..e07c24e593 100644 --- a/Userland/Libraries/LibJS/Runtime/FunctionPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/FunctionPrototype.cpp @@ -131,13 +131,16 @@ JS_DEFINE_NATIVE_FUNCTION(FunctionPrototype::to_string) StringBuilder parameters_builder; auto first = true; for (auto& parameter : script_function.parameters()) { - if (!first) - parameters_builder.append(", "); - first = false; - parameters_builder.append(parameter.name); - if (parameter.default_value) { - // FIXME: See note below - parameters_builder.append(" = TODO"); + // FIXME: Also stringify binding patterns. + if (auto* name_ptr = parameter.binding.get_pointer()) { + if (!first) + parameters_builder.append(", "); + first = false; + parameters_builder.append(*name_ptr); + if (parameter.default_value) { + // FIXME: See note below + parameters_builder.append(" = TODO"); + } } } function_name = script_function.name(); diff --git a/Userland/Libraries/LibJS/Runtime/ScriptFunction.cpp b/Userland/Libraries/LibJS/Runtime/ScriptFunction.cpp index 3673a0e66f..e80954b4a0 100644 --- a/Userland/Libraries/LibJS/Runtime/ScriptFunction.cpp +++ b/Userland/Libraries/LibJS/Runtime/ScriptFunction.cpp @@ -71,13 +71,27 @@ LexicalEnvironment* ScriptFunction::create_environment() { HashMap variables; for (auto& parameter : m_parameters) { - variables.set(parameter.name, { js_undefined(), DeclarationKind::Var }); + parameter.binding.visit( + [&](const FlyString& name) { variables.set(name, { js_undefined(), DeclarationKind::Var }); }, + [&](const NonnullRefPtr& binding) { + binding->for_each_assigned_name([&](const auto& name) { + variables.set(name, { js_undefined(), DeclarationKind::Var }); + }); + }); } if (is(body())) { for (auto& declaration : static_cast(body()).variables()) { for (auto& declarator : declaration.declarations()) { - variables.set(declarator.id().string(), { js_undefined(), declaration.declaration_kind() }); + declarator.target().visit( + [&](const NonnullRefPtr& id) { + variables.set(id->string(), { js_undefined(), declaration.declaration_kind() }); + }, + [&](const NonnullRefPtr& binding) { + binding->for_each_assigned_name([&](const auto& name) { + variables.set(name, { js_undefined(), declaration.declaration_kind() }); + }); + }); } } } @@ -108,23 +122,30 @@ Value ScriptFunction::execute_function_body() auto& call_frame_args = vm.call_frame().arguments; for (size_t i = 0; i < m_parameters.size(); ++i) { - auto parameter = m_parameters[i]; - Value argument_value; - if (parameter.is_rest) { - auto* array = Array::create(global_object()); - for (size_t rest_index = i; rest_index < call_frame_args.size(); ++rest_index) - array->indexed_properties().append(call_frame_args[rest_index]); - argument_value = move(array); - } else if (i < call_frame_args.size() && !call_frame_args[i].is_undefined()) { - argument_value = call_frame_args[i]; - } else if (parameter.default_value) { - argument_value = parameter.default_value->execute(*interpreter, global_object()); - if (vm.exception()) - return {}; - } else { - argument_value = js_undefined(); - } - vm.current_scope()->put_to_scope(parameter.name, { argument_value, DeclarationKind::Var }); + auto& parameter = m_parameters[i]; + parameter.binding.visit( + [&](const auto& param) { + Value argument_value; + if (parameter.is_rest) { + auto* array = Array::create(global_object()); + for (size_t rest_index = i; rest_index < call_frame_args.size(); ++rest_index) + array->indexed_properties().append(call_frame_args[rest_index]); + argument_value = move(array); + } else if (i < call_frame_args.size() && !call_frame_args[i].is_undefined()) { + argument_value = call_frame_args[i]; + } else if (parameter.default_value) { + argument_value = parameter.default_value->execute(*interpreter, global_object()); + if (vm.exception()) + return; + } else { + argument_value = js_undefined(); + } + + vm.assign(param, argument_value, global_object(), true, vm.current_scope()); + }); + + if (vm.exception()) + return {}; } return interpreter->execute_statement(global_object(), m_body, ScopeType::Function); diff --git a/Userland/Libraries/LibJS/Runtime/VM.cpp b/Userland/Libraries/LibJS/Runtime/VM.cpp index 650cc509c1..cb6ce81a87 100644 --- a/Userland/Libraries/LibJS/Runtime/VM.cpp +++ b/Userland/Libraries/LibJS/Runtime/VM.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -129,26 +130,181 @@ Symbol* VM::get_global_symbol(const String& description) return new_global_symbol; } -void VM::set_variable(const FlyString& name, Value value, GlobalObject& global_object, bool first_assignment) +void VM::set_variable(const FlyString& name, Value value, GlobalObject& global_object, bool first_assignment, ScopeObject* specific_scope) { - if (m_call_stack.size()) { + Optional possible_match; + if (!specific_scope && m_call_stack.size()) { for (auto* scope = current_scope(); scope; scope = scope->parent()) { - auto possible_match = scope->get_from_scope(name); + possible_match = scope->get_from_scope(name); if (possible_match.has_value()) { - if (!first_assignment && possible_match.value().declaration_kind == DeclarationKind::Const) { - throw_exception(global_object, ErrorType::InvalidAssignToConst); - return; - } - - scope->put_to_scope(name, { value, possible_match.value().declaration_kind }); - return; + specific_scope = scope; + break; } } } + if (specific_scope && possible_match.has_value()) { + if (!first_assignment && possible_match.value().declaration_kind == DeclarationKind::Const) { + throw_exception(global_object, ErrorType::InvalidAssignToConst); + return; + } + + specific_scope->put_to_scope(name, { value, possible_match.value().declaration_kind }); + return; + } + + if (specific_scope) { + specific_scope->put_to_scope(name, { value, DeclarationKind::Var }); + return; + } + global_object.put(name, value); } +void VM::assign(const FlyString& target, Value value, GlobalObject& global_object, bool first_assignment, ScopeObject* specific_scope) +{ + set_variable(target, move(value), global_object, first_assignment, specific_scope); +} + +void VM::assign(const Variant, NonnullRefPtr>& target, Value value, GlobalObject& global_object, bool first_assignment, ScopeObject* specific_scope) +{ + if (auto id_ptr = target.get_pointer>()) + return assign((*id_ptr)->string(), move(value), global_object, first_assignment, specific_scope); + + assign(target.get>(), move(value), global_object, first_assignment, specific_scope); +} + +void VM::assign(const NonnullRefPtr& target, Value value, GlobalObject& global_object, bool first_assignment, ScopeObject* specific_scope) +{ + auto& binding = *target; + + switch (binding.kind) { + case BindingPattern::Kind::Array: { + auto iterator = get_iterator(global_object, value, "sync"sv, {}); + if (!iterator) + return; + + size_t index = 0; + while (true) { + if (exception()) + return; + + if (index >= binding.properties.size()) + break; + + auto pattern_property = binding.properties[index]; + ++index; + + if (pattern_property.is_rest) { + auto* array = Array::create(global_object); + for (;;) { + auto next_object = iterator_next(*iterator); + if (!next_object) + return; + + auto done_property = next_object->get(names.done); + if (exception()) + return; + + if (!done_property.is_empty() && done_property.to_boolean()) + break; + + auto next_value = next_object->get(names.value); + if (exception()) + return; + + array->indexed_properties().append(next_value); + } + value = array; + } else { + auto next_object = iterator_next(*iterator); + if (!next_object) + return; + + auto done_property = next_object->get(names.done); + if (exception()) + return; + + if (!done_property.is_empty() && done_property.to_boolean()) + break; + + value = next_object->get(names.value); + if (exception()) + return; + } + + if (value.is_undefined() && pattern_property.initializer) + value = pattern_property.initializer->execute(interpreter(), global_object); + + if (exception()) + return; + + if (pattern_property.name) { + set_variable(pattern_property.name->string(), value, global_object, first_assignment, specific_scope); + if (pattern_property.is_rest) + break; + continue; + } + + if (pattern_property.pattern) { + assign(NonnullRefPtr(*pattern_property.pattern), value, global_object, first_assignment, specific_scope); + if (pattern_property.is_rest) + break; + continue; + } + } + break; + } + case BindingPattern::Kind::Object: { + auto object = value.to_object(global_object); + HashTable seen_names; + for (auto& property : binding.properties) { + VERIFY(!property.pattern); + JS::Value value_to_assign; + if (property.is_rest) { + auto* rest_object = Object::create_empty(global_object); + rest_object->set_prototype(nullptr); + for (auto& property : object->shape().property_table()) { + if (!property.value.attributes.has_enumerable()) + continue; + if (seen_names.contains(property.key.to_display_string())) + continue; + rest_object->put(property.key, object->get(property.key)); + if (exception()) + return; + } + value_to_assign = rest_object; + } else { + value_to_assign = object->get(property.name->string()); + } + + seen_names.set(property.name->string()); + if (exception()) + break; + + auto assignment_name = property.name->string(); + if (property.alias) + assignment_name = property.alias->string(); + + if (value_to_assign.is_empty()) + value_to_assign = js_undefined(); + + if (value_to_assign.is_undefined() && property.initializer) + value_to_assign = property.initializer->execute(interpreter(), global_object); + + if (exception()) + break; + + set_variable(assignment_name, value_to_assign, global_object, first_assignment, specific_scope); + + if (property.is_rest) + break; + } + break; + } + } +} + Value VM::get_variable(const FlyString& name, GlobalObject& global_object) { if (!m_call_stack.is_empty()) { diff --git a/Userland/Libraries/LibJS/Runtime/VM.h b/Userland/Libraries/LibJS/Runtime/VM.h index 74f5705099..481aec676a 100644 --- a/Userland/Libraries/LibJS/Runtime/VM.h +++ b/Userland/Libraries/LibJS/Runtime/VM.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -23,6 +24,9 @@ namespace JS { +class Identifier; +struct BindingPattern; + enum class ScopeType { None, Function, @@ -180,7 +184,10 @@ public: ScopeType unwind_until() const { return m_unwind_until; } Value get_variable(const FlyString& name, GlobalObject&); - void set_variable(const FlyString& name, Value, GlobalObject&, bool first_assignment = false); + void set_variable(const FlyString& name, Value, GlobalObject&, bool first_assignment = false, ScopeObject* specific_scope = nullptr); + void assign(const Variant, NonnullRefPtr>& target, Value, GlobalObject&, bool first_assignment = false, ScopeObject* specific_scope = nullptr); + void assign(const FlyString& target, Value, GlobalObject&, bool first_assignment = false, ScopeObject* specific_scope = nullptr); + void assign(const NonnullRefPtr& target, Value, GlobalObject&, bool first_assignment = false, ScopeObject* specific_scope = nullptr); Reference get_reference(const FlyString& name);