Jelajahi Sumber

LibJS: Implement destructuring assignments and function parameters

Ali Mohammad Pur 4 tahun lalu
induk
melakukan
7a00d6d9c8

+ 60 - 18
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<BlockStatement> wrapper)
+static Variant<NonnullRefPtr<Identifier>, NonnullRefPtr<BindingPattern>> variable_from_for_declaration(Interpreter& interpreter, GlobalObject& global_object, const ASTNode& node, RefPtr<BlockStatement> wrapper)
 {
-    FlyString variable_name;
     if (is<VariableDeclaration>(node)) {
         auto& variable_declaration = static_cast<const VariableDeclaration&>(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<Identifier>(node)) {
-        variable_name = static_cast<const Identifier&>(node).string();
-    } else {
-        VERIFY_NOT_REACHED();
+        return variable_declaration.declarations().first().target();
+    }
+
+    if (is<Identifier>(node)) {
+        return NonnullRefPtr(static_cast<const Identifier&>(node));
     }
-    return variable_name;
+
+    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<BlockStatement> 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<BlockStatement> 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<ClassExpression>(*init))
-                update_function_name(initalizer_result, variable_name);
-            interpreter.vm().set_variable(variable_name, initalizer_result, global_object, true);
+            declarator.target().visit(
+                [&](const NonnullRefPtr<Identifier>& id) {
+                    auto variable_name = id->string();
+                    if (is<ClassExpression>(*init))
+                        update_function_name(initalizer_result, variable_name);
+                    interpreter.vm().set_variable(variable_name, initalizer_result, global_object, true);
+                },
+                [&](const NonnullRefPtr<BindingPattern>& 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);
 }

+ 50 - 6
Userland/Libraries/LibJS/AST.h

@@ -10,8 +10,10 @@
 #include <AK/FlyString.h>
 #include <AK/HashMap.h>
 #include <AK/NonnullRefPtrVector.h>
+#include <AK/OwnPtr.h>
 #include <AK/RefPtr.h>
 #include <AK/String.h>
+#include <AK/Variant.h>
 #include <AK/Vector.h>
 #include <LibJS/Forward.h>
 #include <LibJS/Runtime/PropertyName.h>
@@ -22,6 +24,7 @@ namespace JS {
 
 class VariableDeclaration;
 class FunctionDeclaration;
+class Identifier;
 
 template<class T, class... Args>
 static inline NonnullRefPtr<T>
@@ -185,10 +188,32 @@ public:
     Value execute(Interpreter&, GlobalObject&) const override { return {}; }
 };
 
+struct BindingPattern : RefCounted<BindingPattern> {
+    struct BindingProperty {
+        RefPtr<Identifier> name;
+        RefPtr<Identifier> alias;
+        RefPtr<BindingPattern> pattern;
+        RefPtr<Expression> initializer;
+        bool is_rest { false };
+    };
+
+    enum class Kind {
+        Array,
+        Object,
+    };
+
+    void dump(int indent) const;
+    template<typename C>
+    void for_each_assigned_name(C&& callback) const;
+
+    Vector<BindingProperty> properties;
+    Kind kind { Kind::Object };
+};
+
 class FunctionNode {
 public:
     struct Parameter {
-        FlyString name;
+        Variant<FlyString, NonnullRefPtr<BindingPattern>> binding;
         RefPtr<Expression> default_value;
         bool is_rest { false };
     };
@@ -907,25 +932,32 @@ class VariableDeclarator final : public ASTNode {
 public:
     VariableDeclarator(SourceRange source_range, NonnullRefPtr<Identifier> id)
         : ASTNode(move(source_range))
-        , m_id(move(id))
+        , m_target(move(id))
     {
     }
 
-    VariableDeclarator(SourceRange source_range, NonnullRefPtr<Identifier> id, RefPtr<Expression> init)
+    VariableDeclarator(SourceRange source_range, NonnullRefPtr<Identifier> target, RefPtr<Expression> 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<Identifier>, NonnullRefPtr<BindingPattern>> target, RefPtr<Expression> 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<Identifier> m_id;
+    Variant<NonnullRefPtr<Identifier>, NonnullRefPtr<BindingPattern>> m_target;
     RefPtr<Expression> m_init;
 };
 
@@ -1269,4 +1301,16 @@ public:
     virtual Value execute(Interpreter&, GlobalObject&) const override;
 };
 
+template<typename C>
+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<C>(callback));
+    }
+}
+
 }

+ 18 - 2
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<Program>(scope_node)) {
-                global_object.put(declarator.id().string(), js_undefined());
+                declarator.target().visit(
+                    [&](const NonnullRefPtr<Identifier>& id) {
+                        global_object.put(id->string(), js_undefined());
+                    },
+                    [&](const NonnullRefPtr<BindingPattern>& 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<Identifier>& id) {
+                        scope_variables_with_declaration_kind.set(id->string(), { js_undefined(), declaration.declaration_kind() });
+                    },
+                    [&](const NonnullRefPtr<BindingPattern>& binding) {
+                        binding->for_each_assigned_name([&](const auto& name) {
+                            scope_variables_with_declaration_kind.set(name, { js_undefined(), declaration.declaration_kind() });
+                        });
+                    });
             }
         }
     }

+ 179 - 19
Userland/Libraries/LibJS/Parser.cpp

@@ -359,7 +359,7 @@ RefPtr<FunctionExpression> 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<FunctionExpression> 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<ClassExpression> Parser::parse_class_expression(bool expect_class_
             constructor_body->append(create_ast_node<ExpressionStatement>({ 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<FunctionExpression>({ m_parser_state.m_current_token.filename(), rule_start.position(), position() }, class_name, move(constructor_body), Vector { FunctionNode::Parameter { "args", nullptr, true } }, 0, NonnullRefPtrVector<VariableDeclaration>(), true);
+            constructor = create_ast_node<FunctionExpression>({ 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<VariableDeclaration>(), true);
         } else {
             constructor = create_ast_node<FunctionExpression>({ m_parser_state.m_current_token.filename(), rule_start.position(), position() }, class_name, move(constructor_body), Vector<FunctionNode::Parameter> {}, 0, NonnullRefPtrVector<VariableDeclaration>(), true);
         }
@@ -1344,7 +1344,7 @@ NonnullRefPtr<FunctionNodeType> 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<FunctionNodeType> Parser::parse_function_node(u8 parse_options)
     return create_ast_node<FunctionNodeType>({ m_parser_state.m_current_token.filename(), rule_start.position(), position() }, name, move(body), move(parameters), function_length, NonnullRefPtrVector<VariableDeclaration>(), is_strict);
 }
 
-Vector<FunctionNode::Parameter> Parser::parse_function_parameters(int& function_length, u8 parse_options)
+Vector<FunctionNode::Parameter> 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<FunctionNode::Parameter> Parser::parse_function_parameters(int& function_
 
     Vector<FunctionNode::Parameter> parameters;
 
-    auto consume_and_validate_identifier = [&]() -> Token {
+    auto consume_identifier_or_binding_pattern = [&]() -> Variant<FlyString, NonnullRefPtr<BindingPattern>> {
+        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<FlyString>(); !ptr || parameter_name != *ptr)
                 continue;
             String message;
             if (parse_options & FunctionNodeParseOptions::IsArrowFunction)
@@ -1391,23 +1394,22 @@ Vector<FunctionNode::Parameter> 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<Expression> default_value;
         if (match(TokenType::Equals)) {
             consume();
@@ -1415,16 +1417,144 @@ Vector<FunctionNode::Parameter> 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<BindingPattern> 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<Identifier>(
+                { 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<Identifier>(
+                        { 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<Expression> 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<Expression> 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<VariableDeclaration> Parser::parse_variable_declaration(bool for_loop_variable_declaration)
 {
     auto rule_start = push_start();
@@ -1447,19 +1577,49 @@ NonnullRefPtr<VariableDeclaration> Parser::parse_variable_declaration(bool for_l
 
     NonnullRefPtrVector<VariableDeclarator> declarations;
     for (;;) {
-        auto id = consume(TokenType::Identifier).value();
+        Variant<NonnullRefPtr<Identifier>, NonnullRefPtr<BindingPattern>, Empty> target { Empty() };
+        if (match(TokenType::Identifier)) {
+            target = create_ast_node<Identifier>(
+                { 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<Empty>()) {
+            syntax_error("Expected an identifer or a binding pattern");
+            if (match(TokenType::Comma)) {
+                consume();
+                continue;
+            }
+            break;
+        }
+
         RefPtr<Expression> 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<BindingPattern>()) {
+            syntax_error("Missing initializer in destructuring assignment");
         }
-        auto identifier = create_ast_node<Identifier>({ m_parser_state.m_current_token.filename(), rule_start.position(), position() }, move(id));
-        if (init && is<FunctionExpression>(*init)) {
-            static_cast<FunctionExpression&>(*init).set_name_if_possible(id);
+
+        if (init && is<FunctionExpression>(*init) && target.has<NonnullRefPtr<Identifier>>()) {
+            static_cast<FunctionExpression&>(*init).set_name_if_possible(target.get<NonnullRefPtr<Identifier>>()->string());
         }
-        declarations.append(create_ast_node<VariableDeclarator>({ m_parser_state.m_current_token.filename(), rule_start.position(), position() }, move(identifier), move(init)));
+
+        declarations.append(create_ast_node<VariableDeclarator>(
+            { m_parser_state.m_current_token.filename(), rule_start.position(), position() },
+            move(target).downcast<NonnullRefPtr<Identifier>, NonnullRefPtr<BindingPattern>>(),
+            move(init)));
+
         if (match(TokenType::Comma)) {
             consume();
             continue;

+ 2 - 1
Userland/Libraries/LibJS/Parser.h

@@ -40,7 +40,8 @@ public:
 
     template<typename FunctionNodeType>
     NonnullRefPtr<FunctionNodeType> parse_function_node(u8 parse_options = FunctionNodeParseOptions::CheckForFunctionAndName);
-    Vector<FunctionNode::Parameter> parse_function_parameters(int& function_length, u8 parse_options = 0);
+    Vector<FunctionNode::Parameter> parse_formal_parameters(int& function_length, u8 parse_options = 0);
+    RefPtr<BindingPattern> parse_binding_pattern();
 
     NonnullRefPtr<Declaration> parse_declaration();
     NonnullRefPtr<Statement> parse_statement();

+ 10 - 7
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<FlyString>()) {
+                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();

+ 40 - 19
Userland/Libraries/LibJS/Runtime/ScriptFunction.cpp

@@ -71,13 +71,27 @@ LexicalEnvironment* ScriptFunction::create_environment()
 {
     HashMap<FlyString, Variable> 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<BindingPattern>& binding) {
+                binding->for_each_assigned_name([&](const auto& name) {
+                    variables.set(name, { js_undefined(), DeclarationKind::Var });
+                });
+            });
     }
 
     if (is<ScopeNode>(body())) {
         for (auto& declaration : static_cast<const ScopeNode&>(body()).variables()) {
             for (auto& declarator : declaration.declarations()) {
-                variables.set(declarator.id().string(), { js_undefined(), declaration.declaration_kind() });
+                declarator.target().visit(
+                    [&](const NonnullRefPtr<Identifier>& id) {
+                        variables.set(id->string(), { js_undefined(), declaration.declaration_kind() });
+                    },
+                    [&](const NonnullRefPtr<BindingPattern>& 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);

+ 164 - 8
Userland/Libraries/LibJS/Runtime/VM.cpp

@@ -12,6 +12,7 @@
 #include <LibJS/Runtime/Array.h>
 #include <LibJS/Runtime/Error.h>
 #include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/IteratorOperations.h>
 #include <LibJS/Runtime/NativeFunction.h>
 #include <LibJS/Runtime/PromiseReaction.h>
 #include <LibJS/Runtime/Reference.h>
@@ -129,24 +130,179 @@ 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<Variable> 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<TypeError>(global_object, ErrorType::InvalidAssignToConst);
-                    return;
+                specific_scope = scope;
+                break;
+            }
+        }
+    }
+
+    if (specific_scope && possible_match.has_value()) {
+        if (!first_assignment && possible_match.value().declaration_kind == DeclarationKind::Const) {
+            throw_exception<TypeError>(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<Identifier>, NonnullRefPtr<BindingPattern>>& target, Value value, GlobalObject& global_object, bool first_assignment, ScopeObject* specific_scope)
+{
+    if (auto id_ptr = target.get_pointer<NonnullRefPtr<Identifier>>())
+        return assign((*id_ptr)->string(), move(value), global_object, first_assignment, specific_scope);
+
+    assign(target.get<NonnullRefPtr<BindingPattern>>(), move(value), global_object, first_assignment, specific_scope);
+}
+
+void VM::assign(const NonnullRefPtr<BindingPattern>& 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;
 
-                scope->put_to_scope(name, { value, possible_match.value().declaration_kind });
+                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<FlyString> 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());
+            }
 
-    global_object.put(name, value);
+            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)

+ 8 - 1
Userland/Libraries/LibJS/Runtime/VM.h

@@ -12,6 +12,7 @@
 #include <AK/HashMap.h>
 #include <AK/RefCounted.h>
 #include <AK/StackInfo.h>
+#include <AK/Variant.h>
 #include <LibJS/Heap/Heap.h>
 #include <LibJS/Runtime/CommonPropertyNames.h>
 #include <LibJS/Runtime/Error.h>
@@ -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<Identifier>, NonnullRefPtr<BindingPattern>>& 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<BindingPattern>& target, Value, GlobalObject&, bool first_assignment = false, ScopeObject* specific_scope = nullptr);
 
     Reference get_reference(const FlyString& name);