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

LibJS: Implement destructuring assignments and function parameters

This commit is contained in:
Ali Mohammad Pur 2021-05-29 16:03:19 +04:30 committed by Ali Mohammad Pur
parent 1123af361d
commit 7a00d6d9c8
9 changed files with 533 additions and 83 deletions

View file

@ -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,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<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;
}
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<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;
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());
}
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()) {