Skip to content

Commit

Permalink
LibJS: Implement LabelledStatement & LabelledEvaluation semantics
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
linusg committed Jan 6, 2022
1 parent 558fd5b commit fc47496
Show file tree
Hide file tree
Showing 2 changed files with 132 additions and 5 deletions.
114 changes: 109 additions & 5 deletions Userland/Libraries/LibJS/AST.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down Expand Up @@ -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
{
Expand Down Expand Up @@ -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);
Expand Down
23 changes: 23 additions & 0 deletions Userland/Libraries/LibJS/AST.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,29 @@ class Statement : public ASTNode {
}
};

// 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;
Expand Down

0 comments on commit fc47496

Please sign in to comment.