LibJS: Implement LabelledStatement & LabelledEvaluation semantics

Instead of slapping 0..N labels on a statement like the current
LabelableStatement does, we need the spec's LabelledStatement for a
proper implementation of control flow using only completions - it always
has one label and one labelled statement - what appears to be 'multiple
labels' on the same statement is actually nested LabelledStatements.

Note that this is unused yet and will fully replace LabelableStatement
in the next commit.
This commit is contained in:
Linus Groh 2022-01-05 18:54:25 +01:00
parent 558fd5b166
commit fc474966bc
2 changed files with 132 additions and 5 deletions

View file

@ -66,6 +66,11 @@ String ASTNode::class_name() const
return demangle(typeid(*this).name()).substring(4);
}
static void print_indent(int indent)
{
out("{}", String::repeated(' ', indent * 2));
}
static void update_function_name(Value value, FlyString const& name)
{
if (!value.is_function())
@ -104,6 +109,110 @@ Completion ScopeNode::evaluate_statements(Interpreter& interpreter, GlobalObject
return completion;
}
// 14.13.4 Runtime Semantics: LabelledEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-labelledevaluation
// BreakableStatement : IterationStatement
static Completion labelled_evaluation(Interpreter& interpreter, GlobalObject& global_object, IterationStatement const& statement, Vector<FlyString> const& label_set)
{
// 1. Let stmtResult be LoopEvaluation of IterationStatement with argument labelSet.
auto result = statement.loop_evaluation(interpreter, global_object, label_set);
// 2. If stmtResult.[[Type]] is break, then
if (result.type() == Completion::Type::Break) {
// a. If stmtResult.[[Target]] is empty, then
if (!result.target().has_value()) {
// i. If stmtResult.[[Value]] is empty, set stmtResult to NormalCompletion(undefined).
// ii. Else, set stmtResult to NormalCompletion(stmtResult.[[Value]]).
result = normal_completion(result.value().value_or(js_undefined()));
}
}
// 3. Return Completion(stmtResult).
return result;
}
// 14.13.4 Runtime Semantics: LabelledEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-labelledevaluation
// BreakableStatement : SwitchStatement
static Completion labelled_evaluation(Interpreter& interpreter, GlobalObject& global_object, SwitchStatement const& statement, Vector<FlyString> const&)
{
// 1. Let stmtResult be the result of evaluating SwitchStatement.
auto result = statement.execute_impl(interpreter, global_object);
// 2. If stmtResult.[[Type]] is break, then
if (result.type() == Completion::Type::Break) {
// a. If stmtResult.[[Target]] is empty, then
if (!result.target().has_value()) {
// i. If stmtResult.[[Value]] is empty, set stmtResult to NormalCompletion(undefined).
// ii. Else, set stmtResult to NormalCompletion(stmtResult.[[Value]]).
result = normal_completion(result.value().value_or(js_undefined()));
}
}
// 3. Return Completion(stmtResult).
return result;
}
// 14.13.4 Runtime Semantics: LabelledEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-labelledevaluation
// LabelledStatement : LabelIdentifier : LabelledItem
static Completion labelled_evaluation(Interpreter& interpreter, GlobalObject& global_object, LabelledStatement const& statement, Vector<FlyString> const& label_set)
{
auto const& labelled_item = *statement.labelled_item();
// 1. Let label be the StringValue of LabelIdentifier.
auto const& label = statement.label();
// 2. Let newLabelSet be the list-concatenation of labelSet and « label ».
// Optimization: Avoid vector copy if possible.
Optional<Vector<FlyString>> new_label_set;
if (is<IterationStatement>(labelled_item) || is<SwitchStatement>(labelled_item) || is<LabelledStatement>(labelled_item)) {
new_label_set = label_set;
new_label_set->append(label);
}
// 3. Let stmtResult be LabelledEvaluation of LabelledItem with argument newLabelSet.
Completion result;
if (is<IterationStatement>(labelled_item))
result = labelled_evaluation(interpreter, global_object, static_cast<IterationStatement const&>(labelled_item), *new_label_set);
else if (is<SwitchStatement>(labelled_item))
result = labelled_evaluation(interpreter, global_object, static_cast<SwitchStatement const&>(labelled_item), *new_label_set);
else if (is<LabelledStatement>(labelled_item))
result = labelled_evaluation(interpreter, global_object, static_cast<LabelledStatement const&>(labelled_item), *new_label_set);
else
result = labelled_item.execute(interpreter, global_object);
// 4. If stmtResult.[[Type]] is break and SameValue(stmtResult.[[Target]], label) is true, then
if (result.type() == Completion::Type::Break && result.target() == label) {
// a. Set stmtResult to NormalCompletion(stmtResult.[[Value]]).
result = normal_completion(result.value());
}
// 5. Return Completion(stmtResult).
return result;
}
// 14.13.3 Runtime Semantics: Evaluation, https://tc39.es/ecma262/#sec-labelled-statements-runtime-semantics-evaluation
Completion LabelledStatement::execute(Interpreter& interpreter, GlobalObject& global_object) const
{
InterpreterNodeScope node_scope { interpreter, *this };
// 1. Let newLabelSet be a new empty List.
// 2. Return LabelledEvaluation of this LabelledStatement with argument newLabelSet.
return labelled_evaluation(interpreter, global_object, *this, {});
}
void LabelledStatement::dump(int indent) const
{
ASTNode::dump(indent);
print_indent(indent + 1);
outln("(Label)");
print_indent(indent + 2);
outln("\"{}\"", m_label);
print_indent(indent + 1);
outln("(Labelled item)");
m_labelled_item->dump(indent + 2);
}
// 10.2.1.3 Runtime Semantics: EvaluateBody, https://tc39.es/ecma262/#sec-runtime-semantics-evaluatebody
Completion FunctionBody::execute(Interpreter& interpreter, GlobalObject& global_object) const
{
@ -1764,11 +1873,6 @@ ThrowCompletionOr<Value> ClassDeclaration::binding_class_declaration_evaluation(
return value;
}
static void print_indent(int indent)
{
out("{}", String::repeated(' ', indent * 2));
}
void ASTNode::dump(int indent) const
{
print_indent(indent);

View file

@ -86,6 +86,29 @@ public:
}
};
// 14.13 Labelled Statements, https://tc39.es/ecma262/#sec-labelled-statements
class LabelledStatement : public Statement {
public:
LabelledStatement(SourceRange source_range, FlyString label, NonnullRefPtr<Statement> labelled_item)
: Statement(source_range)
, m_label(move(label))
, m_labelled_item(move(labelled_item))
{
}
virtual Completion execute(Interpreter&, GlobalObject&) const override;
virtual void dump(int indent) const override;
FlyString const& label() const { return m_label; }
FlyString& label() { return m_label; }
NonnullRefPtr<Statement> const& labelled_item() const { return m_labelled_item; }
NonnullRefPtr<Statement>& labelled_item() { return m_labelled_item; }
private:
FlyString m_label;
NonnullRefPtr<Statement> m_labelled_item;
};
class LabelableStatement : public Statement {
public:
using Statement::Statement;