1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-24 20:47:43 +00:00

LibJS: Restructure and fully implement BindingPatterns

This commit is contained in:
Matthew Olsson 2021-06-12 18:04:28 -07:00 committed by Andreas Kling
parent 10372b8118
commit ce04c2259f
9 changed files with 244 additions and 169 deletions

View file

@ -1133,29 +1133,41 @@ void BindingPattern::dump(int indent) const
{ {
print_indent(indent); print_indent(indent);
outln("BindingPattern {}", kind == Kind::Array ? "Array" : "Object"); outln("BindingPattern {}", kind == Kind::Array ? "Array" : "Object");
print_indent(++indent);
outln("(Properties)"); for (auto& entry : entries) {
for (auto& property : properties) {
print_indent(indent + 1); print_indent(indent + 1);
outln("(Identifier)"); outln("(Property)");
if (property.name) {
property.name->dump(indent + 2); if (kind == Kind::Object) {
} else {
print_indent(indent + 2); print_indent(indent + 2);
outln("(None)"); outln("(Identifier)");
if (entry.name.has<NonnullRefPtr<Identifier>>()) {
entry.name.get<NonnullRefPtr<Identifier>>()->dump(indent + 3);
} else {
entry.name.get<NonnullRefPtr<Expression>>()->dump(indent + 3);
}
} else if (entry.is_elision()) {
print_indent(indent + 2);
outln("(Elision)");
continue;
} }
print_indent(indent + 1); print_indent(indent + 2);
outln("(Pattern)"); outln("(Pattern{})", entry.is_rest ? " rest=true" : "");
if (property.pattern) { if (entry.alias.has<NonnullRefPtr<Identifier>>()) {
property.pattern->dump(indent + 2); entry.alias.get<NonnullRefPtr<Identifier>>()->dump(indent + 3);
} else if (entry.alias.has<NonnullRefPtr<BindingPattern>>()) {
entry.alias.get<NonnullRefPtr<BindingPattern>>()->dump(indent + 3);
} else { } else {
print_indent(indent + 2); print_indent(indent + 3);
outln("(None)"); outln("<empty>");
} }
print_indent(indent + 1); if (entry.initializer) {
outln("(Is Rest = {})", property.is_rest); print_indent(indent + 2);
outln("(Initializer)");
entry.initializer->dump(indent + 3);
}
} }
} }

View file

@ -215,12 +215,15 @@ public:
}; };
struct BindingPattern : RefCounted<BindingPattern> { struct BindingPattern : RefCounted<BindingPattern> {
struct BindingProperty { // This covers both BindingProperty and BindingElement, hence the more generic name
RefPtr<Identifier> name; struct BindingEntry {
RefPtr<Identifier> alias; // If this entry represents a BindingElement, then name will be Empty
RefPtr<BindingPattern> pattern; Variant<NonnullRefPtr<Identifier>, NonnullRefPtr<Expression>, Empty> name { Empty {} };
RefPtr<Expression> initializer; Variant<NonnullRefPtr<Identifier>, NonnullRefPtr<BindingPattern>, Empty> alias { Empty {} };
RefPtr<Expression> initializer {};
bool is_rest { false }; bool is_rest { false };
bool is_elision() const { return name.has<Empty>() && alias.has<Empty>(); }
}; };
enum class Kind { enum class Kind {
@ -229,10 +232,11 @@ struct BindingPattern : RefCounted<BindingPattern> {
}; };
void dump(int indent) const; void dump(int indent) const;
template<typename C>
void for_each_assigned_name(C&& callback) const;
Vector<BindingProperty> properties; template<typename C>
void for_each_bound_name(C&& callback) const;
Vector<BindingEntry> entries;
Kind kind { Kind::Object }; Kind kind { Kind::Object };
}; };
@ -1398,14 +1402,15 @@ public:
}; };
template<typename C> template<typename C>
void BindingPattern::for_each_assigned_name(C&& callback) const void BindingPattern::for_each_bound_name(C&& callback) const
{ {
for (auto& property : properties) { for (auto& entry : entries) {
if (property.name) { auto& alias = entry.alias;
callback(property.name->string()); if (alias.has<NonnullRefPtr<Identifier>>()) {
continue; callback(alias.get<NonnullRefPtr<Identifier>>()->string());
} else if (alias.has<NonnullRefPtr<BindingPattern>>()) {
alias.get<NonnullRefPtr<BindingPattern>>()->for_each_bound_name(forward<C>(callback));
} }
property.pattern->template for_each_assigned_name(forward<C>(callback));
} }
} }

View file

@ -43,7 +43,7 @@ void ScopeNode::generate_bytecode(Bytecode::Generator& generator) const
generator.emit<Bytecode::Op::PutById>(Bytecode::Register::global_object(), generator.intern_string(id->string())); generator.emit<Bytecode::Op::PutById>(Bytecode::Register::global_object(), generator.intern_string(id->string()));
}, },
[&](const NonnullRefPtr<BindingPattern>& binding) { [&](const NonnullRefPtr<BindingPattern>& binding) {
binding->for_each_assigned_name([&](const auto& name) { binding->for_each_bound_name([&](const auto& name) {
generator.emit<Bytecode::Op::LoadImmediate>(js_undefined()); generator.emit<Bytecode::Op::LoadImmediate>(js_undefined());
generator.emit<Bytecode::Op::PutById>(Bytecode::Register::global_object(), generator.intern_string(name)); generator.emit<Bytecode::Op::PutById>(Bytecode::Register::global_object(), generator.intern_string(name));
}); });
@ -54,7 +54,7 @@ void ScopeNode::generate_bytecode(Bytecode::Generator& generator) const
scope_variables_with_declaration_kind.set((size_t)generator.intern_string(id->string()).value(), { js_undefined(), declaration.declaration_kind() }); scope_variables_with_declaration_kind.set((size_t)generator.intern_string(id->string()).value(), { js_undefined(), declaration.declaration_kind() });
}, },
[&](const NonnullRefPtr<BindingPattern>& binding) { [&](const NonnullRefPtr<BindingPattern>& binding) {
binding->for_each_assigned_name([&](const auto& name) { binding->for_each_bound_name([&](const auto& name) {
scope_variables_with_declaration_kind.set((size_t)generator.intern_string(name).value(), { js_undefined(), declaration.declaration_kind() }); scope_variables_with_declaration_kind.set((size_t)generator.intern_string(name).value(), { js_undefined(), declaration.declaration_kind() });
}); });
}); });

View file

@ -108,7 +108,7 @@ void Interpreter::enter_scope(const ScopeNode& scope_node, ScopeType scope_type,
global_object.put(id->string(), js_undefined()); global_object.put(id->string(), js_undefined());
}, },
[&](const NonnullRefPtr<BindingPattern>& binding) { [&](const NonnullRefPtr<BindingPattern>& binding) {
binding->for_each_assigned_name([&](const auto& name) { binding->for_each_bound_name([&](const auto& name) {
global_object.put(name, js_undefined()); global_object.put(name, js_undefined());
}); });
}); });
@ -120,7 +120,7 @@ void Interpreter::enter_scope(const ScopeNode& scope_node, ScopeType scope_type,
scope_variables_with_declaration_kind.set(id->string(), { js_undefined(), declaration.declaration_kind() }); scope_variables_with_declaration_kind.set(id->string(), { js_undefined(), declaration.declaration_kind() });
}, },
[&](const NonnullRefPtr<BindingPattern>& binding) { [&](const NonnullRefPtr<BindingPattern>& binding) {
binding->for_each_assigned_name([&](const auto& name) { binding->for_each_bound_name([&](const auto& name) {
scope_variables_with_declaration_kind.set(name, { js_undefined(), declaration.declaration_kind() }); scope_variables_with_declaration_kind.set(name, { js_undefined(), declaration.declaration_kind() });
}); });
}); });

View file

@ -1248,6 +1248,15 @@ NonnullRefPtr<AssignmentExpression> Parser::parse_assignment_expression(Assignme
return create_ast_node<AssignmentExpression>({ m_parser_state.m_current_token.filename(), rule_start.position(), position() }, assignment_op, move(lhs), move(rhs)); return create_ast_node<AssignmentExpression>({ m_parser_state.m_current_token.filename(), rule_start.position(), position() }, assignment_op, move(lhs), move(rhs));
} }
NonnullRefPtr<Identifier> Parser::parse_identifier()
{
auto identifier_start = position();
auto token = consume(TokenType::Identifier);
return create_ast_node<Identifier>(
{ m_parser_state.m_current_token.filename(), identifier_start, position() },
token.value());
}
NonnullRefPtr<CallExpression> Parser::parse_call_expression(NonnullRefPtr<Expression> lhs) NonnullRefPtr<CallExpression> Parser::parse_call_expression(NonnullRefPtr<Expression> lhs)
{ {
auto rule_start = push_start(); auto rule_start = push_start();
@ -1522,36 +1531,28 @@ RefPtr<BindingPattern> Parser::parse_binding_pattern()
{ {
auto rule_start = push_start(); auto rule_start = push_start();
auto pattern_ptr = adopt_ref(*new BindingPattern);
auto& pattern = *pattern_ptr;
TokenType closing_token; TokenType closing_token;
auto allow_named_property = false; bool is_object = true;
auto elide_extra_commas = false;
auto allow_nested_pattern = false;
if (match(TokenType::BracketOpen)) { if (match(TokenType::BracketOpen)) {
consume(); consume();
pattern.kind = BindingPattern::Kind::Array;
closing_token = TokenType::BracketClose; closing_token = TokenType::BracketClose;
elide_extra_commas = true; is_object = false;
allow_nested_pattern = true;
} else if (match(TokenType::CurlyOpen)) { } else if (match(TokenType::CurlyOpen)) {
consume(); consume();
pattern.kind = BindingPattern::Kind::Object;
closing_token = TokenType::CurlyClose; closing_token = TokenType::CurlyClose;
allow_named_property = true;
} else { } else {
return {}; return {};
} }
while (!match(closing_token)) { Vector<BindingPattern::BindingEntry> entries;
if (elide_extra_commas && match(TokenType::Comma))
consume();
ScopeGuard consume_commas { [&] { while (!match(closing_token)) {
if (match(TokenType::Comma)) if (!is_object && match(TokenType::Comma)) {
consume(); consume();
} }; entries.append(BindingPattern::BindingEntry {});
continue;
}
auto is_rest = false; auto is_rest = false;
@ -1560,89 +1561,88 @@ RefPtr<BindingPattern> Parser::parse_binding_pattern()
is_rest = true; is_rest = true;
} }
if (match(TokenType::Identifier)) { decltype(BindingPattern::BindingEntry::name) name = Empty {};
auto identifier_start = position(); decltype(BindingPattern::BindingEntry::alias) alias = Empty {};
auto token = consume(TokenType::Identifier); RefPtr<Expression> initializer = {};
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)) { if (is_object) {
if (match(TokenType::Identifier)) {
name = parse_identifier();
} else if (match(TokenType::BracketOpen)) {
consume(); consume();
if (!match(TokenType::Identifier)) { name = parse_expression(0);
syntax_error("Expected a binding pattern as the value of a named element in destructuring object"); consume(TokenType::BracketOpen);
break;
} 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");
break;
} else { } else {
RefPtr<Expression> initializer; syntax_error("Expected identifier or computed property name");
if (match(TokenType::Equals)) { return {};
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; if (!is_rest && match(TokenType::Colon)) {
consume();
if (match(TokenType::CurlyOpen) || match(TokenType::BracketOpen)) {
auto binding_pattern = parse_binding_pattern();
if (!binding_pattern)
return {};
alias = binding_pattern.release_nonnull();
} else if (match_identifier_name()) {
alias = parse_identifier();
} else {
syntax_error("Expected identifier or binding pattern");
return {};
}
}
} else {
if (match(TokenType::Identifier)) {
// BindingElement must always have an Empty name field
alias = parse_identifier();
} else if (match(TokenType::BracketOpen) || match(TokenType::CurlyOpen)) {
auto pattern = parse_binding_pattern();
if (!pattern) {
syntax_error("Expected binding pattern");
return {};
}
alias = pattern.release_nonnull();
} else {
syntax_error("Expected identifier or binding pattern");
return {};
}
} }
break; if (match(TokenType::Equals)) {
if (is_rest) {
syntax_error("Unexpected initializer after rest element");
return {};
}
consume();
initializer = parse_expression(2);
if (!initializer) {
syntax_error("Expected initialization expression");
return {};
}
}
entries.append(BindingPattern::BindingEntry { move(name), move(alias), move(initializer), is_rest });
if (match(TokenType::Comma)) {
if (is_rest) {
syntax_error("Rest element may not be followed by a comma");
return {};
}
consume();
}
} }
while (elide_extra_commas && match(TokenType::Comma)) while (!is_object && match(TokenType::Comma))
consume(); consume();
consume(closing_token); consume(closing_token);
auto kind = is_object ? BindingPattern::Kind::Object : BindingPattern::Kind::Array;
auto pattern = adopt_ref(*new BindingPattern);
pattern->entries = move(entries);
pattern->kind = kind;
return pattern; return pattern;
} }

View file

@ -86,6 +86,7 @@ public:
NonnullRefPtr<YieldExpression> parse_yield_expression(); NonnullRefPtr<YieldExpression> parse_yield_expression();
NonnullRefPtr<Expression> parse_property_key(); NonnullRefPtr<Expression> parse_property_key();
NonnullRefPtr<AssignmentExpression> parse_assignment_expression(AssignmentOp, NonnullRefPtr<Expression> lhs, int min_precedence, Associativity); NonnullRefPtr<AssignmentExpression> parse_assignment_expression(AssignmentOp, NonnullRefPtr<Expression> lhs, int min_precedence, Associativity);
NonnullRefPtr<Identifier> parse_identifier();
RefPtr<FunctionExpression> try_parse_arrow_function_expression(bool expect_parens); RefPtr<FunctionExpression> try_parse_arrow_function_expression(bool expect_parens);
RefPtr<Statement> try_parse_labelled_statement(); RefPtr<Statement> try_parse_labelled_statement();

View file

@ -87,6 +87,8 @@ public:
} }
} }
ALWAYS_INLINE Type type() const { return m_type; }
bool is_valid() const { return m_type != Type::Invalid; } bool is_valid() const { return m_type != Type::Invalid; }
bool is_number() const bool is_number() const
{ {
@ -176,6 +178,35 @@ private:
u32 m_number { 0 }; u32 m_number { 0 };
}; };
struct PropertyNameTraits : public Traits<PropertyName> {
static unsigned hash(PropertyName const& name)
{
VERIFY(name.is_valid());
if (name.is_string())
return name.as_string().hash();
if (name.is_number())
return int_hash(name.as_number());
return ptr_hash(name.as_symbol());
}
static bool equals(PropertyName const& a, PropertyName const& b)
{
if (a.type() != b.type())
return false;
switch (a.type()) {
case PropertyName::Type::Number:
return a.as_number() == b.as_number();
case PropertyName::Type::String:
return a.as_string() == b.as_string();
case PropertyName::Type::Symbol:
return a.as_symbol() == b.as_symbol();
default:
VERIFY_NOT_REACHED();
}
}
};
} }
namespace AK { namespace AK {

View file

@ -100,7 +100,7 @@ LexicalEnvironment* ScriptFunction::create_environment()
parameter.binding.visit( parameter.binding.visit(
[&](const FlyString& name) { variables.set(name, { js_undefined(), DeclarationKind::Var }); }, [&](const FlyString& name) { variables.set(name, { js_undefined(), DeclarationKind::Var }); },
[&](const NonnullRefPtr<BindingPattern>& binding) { [&](const NonnullRefPtr<BindingPattern>& binding) {
binding->for_each_assigned_name([&](const auto& name) { binding->for_each_bound_name([&](const auto& name) {
variables.set(name, { js_undefined(), DeclarationKind::Var }); variables.set(name, { js_undefined(), DeclarationKind::Var });
}); });
}); });
@ -114,7 +114,7 @@ LexicalEnvironment* ScriptFunction::create_environment()
variables.set(id->string(), { js_undefined(), declaration.declaration_kind() }); variables.set(id->string(), { js_undefined(), declaration.declaration_kind() });
}, },
[&](const NonnullRefPtr<BindingPattern>& binding) { [&](const NonnullRefPtr<BindingPattern>& binding) {
binding->for_each_assigned_name([&](const auto& name) { binding->for_each_bound_name([&](const auto& name) {
variables.set(name, { js_undefined(), declaration.declaration_kind() }); variables.set(name, { js_undefined(), declaration.declaration_kind() });
}); });
}); });

View file

@ -208,18 +208,15 @@ void VM::assign(const NonnullRefPtr<BindingPattern>& target, Value value, Global
if (!iterator) if (!iterator)
return; return;
size_t index = 0; for (size_t i = 0; i < binding.entries.size(); i++) {
while (true) {
if (exception()) if (exception())
return; return;
if (index >= binding.properties.size()) auto& entry = binding.entries[i];
break;
auto pattern_property = binding.properties[index]; if (entry.is_rest) {
++index; VERIFY(i == binding.entries.size() - 1);
if (pattern_property.is_rest) {
auto* array = Array::create(global_object); auto* array = Array::create(global_object);
for (;;) { for (;;) {
auto next_object = iterator_next(*iterator); auto next_object = iterator_next(*iterator);
@ -240,7 +237,7 @@ void VM::assign(const NonnullRefPtr<BindingPattern>& target, Value value, Global
array->indexed_properties().append(next_value); array->indexed_properties().append(next_value);
} }
value = array; value = array;
} else { } else if (iterator) {
auto next_object = iterator_next(*iterator); auto next_object = iterator_next(*iterator);
if (!next_object) if (!next_object)
return; return;
@ -249,65 +246,83 @@ void VM::assign(const NonnullRefPtr<BindingPattern>& target, Value value, Global
if (exception()) if (exception())
return; return;
if (!done_property.is_empty() && done_property.to_boolean()) if (!done_property.is_empty() && done_property.to_boolean()) {
break; iterator = nullptr;
value = js_undefined();
} else {
value = next_object->get(names.value);
if (exception())
return;
}
} else {
value = js_undefined();
}
value = next_object->get(names.value); if (value.is_undefined() && entry.initializer) {
value = entry.initializer->execute(interpreter(), global_object);
if (exception()) if (exception())
return; return;
} }
if (value.is_undefined() && pattern_property.initializer) entry.alias.visit(
value = pattern_property.initializer->execute(interpreter(), global_object); [&](Empty) {},
[&](NonnullRefPtr<Identifier> const& identifier) {
set_variable(identifier->string(), value, global_object, first_assignment, specific_scope);
},
[&](NonnullRefPtr<BindingPattern> const& pattern) {
assign(pattern, value, global_object, first_assignment, specific_scope);
});
if (exception()) if (entry.is_rest)
return; break;
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; break;
} }
case BindingPattern::Kind::Object: { case BindingPattern::Kind::Object: {
auto object = value.to_object(global_object); auto object = value.to_object(global_object);
HashTable<FlyString> seen_names; HashTable<PropertyName, PropertyNameTraits> seen_names;
for (auto& property : binding.properties) { for (auto& property : binding.entries) {
VERIFY(!property.pattern); VERIFY(!property.is_elision());
PropertyName assignment_name;
JS::Value value_to_assign; JS::Value value_to_assign;
if (property.is_rest) { if (property.is_rest) {
auto* rest_object = Object::create(global_object, nullptr); VERIFY(property.name.has<NonnullRefPtr<Identifier>>());
for (auto& property : object->shape().property_table()) { assignment_name = property.name.get<NonnullRefPtr<Identifier>>()->string();
if (!property.value.attributes.has_enumerable())
auto* rest_object = Object::create(global_object, global_object.object_prototype());
for (auto& object_property : object->shape().property_table()) {
if (!object_property.value.attributes.has_enumerable())
continue; continue;
if (seen_names.contains(property.key.to_display_string())) if (seen_names.contains(object_property.key.to_display_string()))
continue; continue;
rest_object->put(property.key, object->get(property.key)); rest_object->put(object_property.key, object->get(object_property.key));
if (exception()) if (exception())
return; return;
} }
value_to_assign = rest_object; value_to_assign = rest_object;
} else { } else {
value_to_assign = object->get(property.name->string()); property.name.visit(
[&](Empty) { VERIFY_NOT_REACHED(); },
[&](NonnullRefPtr<Identifier> const& identifier) {
assignment_name = identifier->string();
},
[&](NonnullRefPtr<Expression> const& expression) {
auto result = expression->execute(interpreter(), global_object);
if (exception())
return;
assignment_name = result.to_property_key(global_object);
});
if (exception())
break;
value_to_assign = object->get(assignment_name);
} }
seen_names.set(property.name->string()); seen_names.set(assignment_name);
if (exception())
break;
auto assignment_name = property.name->string();
if (property.alias)
assignment_name = property.alias->string();
if (value_to_assign.is_empty()) if (value_to_assign.is_empty())
value_to_assign = js_undefined(); value_to_assign = js_undefined();
@ -318,7 +333,18 @@ void VM::assign(const NonnullRefPtr<BindingPattern>& target, Value value, Global
if (exception()) if (exception())
break; break;
set_variable(assignment_name, value_to_assign, global_object, first_assignment, specific_scope); property.alias.visit(
[&](Empty) {
set_variable(assignment_name.to_string(), value_to_assign, global_object, first_assignment, specific_scope);
},
[&](NonnullRefPtr<Identifier> const& identifier) {
VERIFY(!property.is_rest);
set_variable(identifier->string(), value_to_assign, global_object, first_assignment, specific_scope);
},
[&](NonnullRefPtr<BindingPattern> const& pattern) {
VERIFY(!property.is_rest);
assign(pattern, value_to_assign, global_object, first_assignment, specific_scope);
});
if (property.is_rest) if (property.is_rest)
break; break;