mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 15: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
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue