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:
parent
1123af361d
commit
7a00d6d9c8
9 changed files with 533 additions and 83 deletions
|
@ -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()) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue