mirror of
https://github.com/RGBCube/serenity
synced 2025-06-01 12:58:13 +00:00
LibJS/Bytecode: Implement optional chaining
This commit is contained in:
parent
c9088fa6ec
commit
1116ba191a
2 changed files with 148 additions and 54 deletions
|
@ -1975,6 +1975,10 @@ public:
|
|||
virtual Completion execute(Interpreter&) const override;
|
||||
virtual ThrowCompletionOr<JS::Reference> to_reference(Interpreter&) const override;
|
||||
virtual void dump(int indent) const override;
|
||||
virtual Bytecode::CodeGenerationErrorOr<void> generate_bytecode(Bytecode::Generator&) const override;
|
||||
|
||||
Expression const& base() const { return *m_base; }
|
||||
Vector<Reference> const& references() const { return m_references; }
|
||||
|
||||
private:
|
||||
struct ReferenceAndValue {
|
||||
|
|
|
@ -1279,19 +1279,8 @@ Bytecode::CodeGenerationErrorOr<void> VariableDeclaration::generate_bytecode(Byt
|
|||
return {};
|
||||
}
|
||||
|
||||
Bytecode::CodeGenerationErrorOr<void> CallExpression::generate_bytecode(Bytecode::Generator& generator) const
|
||||
static Bytecode::CodeGenerationErrorOr<void> get_base_and_value_from_member_expression(Bytecode::Generator& generator, MemberExpression const& member_expression, Bytecode::Register this_reg)
|
||||
{
|
||||
auto callee_reg = generator.allocate_register();
|
||||
auto this_reg = generator.allocate_register();
|
||||
generator.emit<Bytecode::Op::LoadImmediate>(js_undefined());
|
||||
generator.emit<Bytecode::Op::Store>(this_reg);
|
||||
|
||||
if (is<NewExpression>(this)) {
|
||||
TRY(m_callee->generate_bytecode(generator));
|
||||
generator.emit<Bytecode::Op::Store>(callee_reg);
|
||||
} else if (is<MemberExpression>(*m_callee)) {
|
||||
auto& member_expression = static_cast<MemberExpression const&>(*m_callee);
|
||||
|
||||
// https://tc39.es/ecma262/#sec-super-keyword-runtime-semantics-evaluation
|
||||
if (is<SuperExpression>(member_expression.object())) {
|
||||
// 1. Let env be GetThisEnvironment().
|
||||
|
@ -1345,7 +1334,28 @@ Bytecode::CodeGenerationErrorOr<void> CallExpression::generate_bytecode(Bytecode
|
|||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
static Bytecode::CodeGenerationErrorOr<void> generate_optional_chain(Bytecode::Generator& generator, OptionalChain const& optional_chain, Bytecode::Register current_value_register, Bytecode::Register current_base_register);
|
||||
|
||||
Bytecode::CodeGenerationErrorOr<void> CallExpression::generate_bytecode(Bytecode::Generator& generator) const
|
||||
{
|
||||
auto callee_reg = generator.allocate_register();
|
||||
auto this_reg = generator.allocate_register();
|
||||
generator.emit<Bytecode::Op::LoadImmediate>(js_undefined());
|
||||
generator.emit<Bytecode::Op::Store>(this_reg);
|
||||
|
||||
if (is<NewExpression>(this)) {
|
||||
TRY(m_callee->generate_bytecode(generator));
|
||||
generator.emit<Bytecode::Op::Store>(callee_reg);
|
||||
} else if (is<MemberExpression>(*m_callee)) {
|
||||
auto& member_expression = static_cast<MemberExpression const&>(*m_callee);
|
||||
TRY(get_base_and_value_from_member_expression(generator, member_expression, this_reg));
|
||||
generator.emit<Bytecode::Op::Store>(callee_reg);
|
||||
} else if (is<OptionalChain>(*m_callee)) {
|
||||
auto& optional_chain = static_cast<OptionalChain const&>(*m_callee);
|
||||
TRY(generate_optional_chain(generator, optional_chain, callee_reg, this_reg));
|
||||
} else {
|
||||
// FIXME: this = global object in sloppy mode.
|
||||
TRY(m_callee->generate_bytecode(generator));
|
||||
|
@ -2604,4 +2614,84 @@ Bytecode::CodeGenerationErrorOr<void> ClassFieldInitializerStatement::generate_b
|
|||
return {};
|
||||
}
|
||||
|
||||
static Bytecode::CodeGenerationErrorOr<void> generate_optional_chain(Bytecode::Generator& generator, OptionalChain const& optional_chain, Bytecode::Register current_value_register, Bytecode::Register current_base_register)
|
||||
{
|
||||
if (is<MemberExpression>(optional_chain.base())) {
|
||||
auto& member_expression = static_cast<MemberExpression const&>(optional_chain.base());
|
||||
TRY(get_base_and_value_from_member_expression(generator, member_expression, current_base_register));
|
||||
} else if (is<OptionalChain>(optional_chain.base())) {
|
||||
auto& sub_optional_chain = static_cast<OptionalChain const&>(optional_chain.base());
|
||||
TRY(generate_optional_chain(generator, sub_optional_chain, current_value_register, current_base_register));
|
||||
} else {
|
||||
TRY(optional_chain.base().generate_bytecode(generator));
|
||||
}
|
||||
|
||||
generator.emit<Bytecode::Op::Store>(current_value_register);
|
||||
|
||||
auto& load_undefined_and_jump_to_end_block = generator.make_block();
|
||||
auto& end_block = generator.make_block();
|
||||
|
||||
for (auto& reference : optional_chain.references()) {
|
||||
auto is_optional = reference.visit([](auto& ref) { return ref.mode; }) == OptionalChain::Mode::Optional;
|
||||
if (is_optional) {
|
||||
auto& not_nullish_block = generator.make_block();
|
||||
generator.emit<Bytecode::Op::JumpNullish>(
|
||||
Bytecode::Label { load_undefined_and_jump_to_end_block },
|
||||
Bytecode::Label { not_nullish_block });
|
||||
generator.switch_to_basic_block(not_nullish_block);
|
||||
}
|
||||
|
||||
TRY(reference.visit(
|
||||
[&](OptionalChain::Call const& call) -> Bytecode::CodeGenerationErrorOr<void> {
|
||||
TRY(arguments_to_array_for_call(generator, call.arguments));
|
||||
generator.emit<Bytecode::Op::Call>(Bytecode::Op::Call::CallType::Call, current_value_register, current_base_register);
|
||||
|
||||
generator.emit<Bytecode::Op::Store>(current_value_register);
|
||||
|
||||
generator.emit<Bytecode::Op::LoadImmediate>(js_undefined());
|
||||
generator.emit<Bytecode::Op::Store>(current_base_register);
|
||||
|
||||
generator.emit<Bytecode::Op::Load>(current_value_register);
|
||||
return {};
|
||||
},
|
||||
[&](OptionalChain::ComputedReference const& ref) -> Bytecode::CodeGenerationErrorOr<void> {
|
||||
generator.emit<Bytecode::Op::Store>(current_base_register);
|
||||
TRY(ref.expression->generate_bytecode(generator));
|
||||
generator.emit<Bytecode::Op::GetByValue>(current_base_register);
|
||||
generator.emit<Bytecode::Op::Store>(current_value_register);
|
||||
return {};
|
||||
},
|
||||
[&](OptionalChain::MemberReference const& ref) -> Bytecode::CodeGenerationErrorOr<void> {
|
||||
generator.emit<Bytecode::Op::Store>(current_base_register);
|
||||
generator.emit<Bytecode::Op::GetById>(generator.intern_identifier(ref.identifier->string()));
|
||||
generator.emit<Bytecode::Op::Store>(current_value_register);
|
||||
return {};
|
||||
},
|
||||
[&](OptionalChain::PrivateMemberReference const&) -> Bytecode::CodeGenerationErrorOr<void> {
|
||||
return Bytecode::CodeGenerationError {
|
||||
&optional_chain,
|
||||
"Unimplemented reference: PrivateMemberReference"sv,
|
||||
};
|
||||
}));
|
||||
}
|
||||
|
||||
generator.emit<Bytecode::Op::Jump>(Bytecode::Label { end_block });
|
||||
|
||||
generator.switch_to_basic_block(load_undefined_and_jump_to_end_block);
|
||||
generator.emit<Bytecode::Op::LoadImmediate>(js_undefined());
|
||||
generator.emit<Bytecode::Op::Jump>(Bytecode::Label { end_block });
|
||||
|
||||
generator.switch_to_basic_block(end_block);
|
||||
return {};
|
||||
}
|
||||
|
||||
Bytecode::CodeGenerationErrorOr<void> OptionalChain::generate_bytecode(Bytecode::Generator& generator) const
|
||||
{
|
||||
auto current_base_register = generator.allocate_register();
|
||||
auto current_value_register = generator.allocate_register();
|
||||
generator.emit<Bytecode::Op::LoadImmediate>(js_undefined());
|
||||
generator.emit<Bytecode::Op::Store>(current_base_register);
|
||||
return generate_optional_chain(generator, *this, current_value_register, current_base_register);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue