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
Notes: sideshowbarker 2024-07-18 17:11:58 +09:00
9 changed files with 533 additions and 83 deletions

View File

@ -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();
}
return variable_name;
if (is<Identifier>(node)) {
return NonnullRefPtr(static_cast<const Identifier&>(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<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);
}

View File

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

View File

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

View File

@ -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;

View File

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

View File

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

View File

@ -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);

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()) {

View File

@ -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);