mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-12-31 23:22:03 +03:00
LibJS: Implement destructuring assignments and function parameters
This commit is contained in:
parent
1123af361d
commit
7a00d6d9c8
Notes:
sideshowbarker
2024-07-18 17:11:58 +09:00
Author: https://github.com/alimpfard Commit: https://github.com/SerenityOS/serenity/commit/7a00d6d9c8e Pull-request: https://github.com/SerenityOS/serenity/pull/7551 Reviewed-by: https://github.com/linusg ✅
@ -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);
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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() });
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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…
Reference in New Issue
Block a user