mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 17:57:35 +00:00
LibJS: Implement parsing and evaluation for AssignmentPatterns
e.g. `[...foo] = bar` can now be evaluated :^)
This commit is contained in:
parent
7fc6cd6b20
commit
1a9518ebe3
7 changed files with 104 additions and 40 deletions
|
@ -1484,11 +1484,18 @@ Value AssignmentExpression::execute(Interpreter& interpreter, GlobalObject& glob
|
|||
{
|
||||
InterpreterNodeScope node_scope { interpreter, *this };
|
||||
|
||||
#define EXECUTE_LHS() \
|
||||
do { \
|
||||
if (auto* ptr = m_lhs.get_pointer<NonnullRefPtr<Expression>>()) { \
|
||||
lhs_result = (*ptr)->execute(interpreter, global_object); \
|
||||
if (interpreter.exception()) \
|
||||
return {}; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define EXECUTE_LHS_AND_RHS() \
|
||||
do { \
|
||||
lhs_result = m_lhs->execute(interpreter, global_object); \
|
||||
if (interpreter.exception()) \
|
||||
return {}; \
|
||||
EXECUTE_LHS(); \
|
||||
rhs_result = m_rhs->execute(interpreter, global_object); \
|
||||
if (interpreter.exception()) \
|
||||
return {}; \
|
||||
|
@ -1548,25 +1555,19 @@ Value AssignmentExpression::execute(Interpreter& interpreter, GlobalObject& glob
|
|||
rhs_result = unsigned_right_shift(global_object, lhs_result, rhs_result);
|
||||
break;
|
||||
case AssignmentOp::AndAssignment:
|
||||
lhs_result = m_lhs->execute(interpreter, global_object);
|
||||
if (interpreter.exception())
|
||||
return {};
|
||||
EXECUTE_LHS();
|
||||
if (!lhs_result.to_boolean())
|
||||
return lhs_result;
|
||||
rhs_result = m_rhs->execute(interpreter, global_object);
|
||||
break;
|
||||
case AssignmentOp::OrAssignment:
|
||||
lhs_result = m_lhs->execute(interpreter, global_object);
|
||||
if (interpreter.exception())
|
||||
return {};
|
||||
EXECUTE_LHS();
|
||||
if (lhs_result.to_boolean())
|
||||
return lhs_result;
|
||||
rhs_result = m_rhs->execute(interpreter, global_object);
|
||||
break;
|
||||
case AssignmentOp::NullishAssignment:
|
||||
lhs_result = m_lhs->execute(interpreter, global_object);
|
||||
if (interpreter.exception())
|
||||
return {};
|
||||
EXECUTE_LHS();
|
||||
if (!lhs_result.is_nullish())
|
||||
return lhs_result;
|
||||
rhs_result = m_rhs->execute(interpreter, global_object);
|
||||
|
@ -1575,26 +1576,42 @@ Value AssignmentExpression::execute(Interpreter& interpreter, GlobalObject& glob
|
|||
if (interpreter.exception())
|
||||
return {};
|
||||
|
||||
auto reference = m_lhs->to_reference(interpreter, global_object);
|
||||
if (interpreter.exception())
|
||||
return {};
|
||||
return m_lhs.visit(
|
||||
[&](NonnullRefPtr<Expression>& lhs) -> JS::Value {
|
||||
auto reference = lhs->to_reference(interpreter, global_object);
|
||||
if (interpreter.exception())
|
||||
return {};
|
||||
|
||||
if (m_op == AssignmentOp::Assignment) {
|
||||
rhs_result = m_rhs->execute(interpreter, global_object);
|
||||
if (interpreter.exception())
|
||||
return {};
|
||||
}
|
||||
if (m_op == AssignmentOp::Assignment) {
|
||||
rhs_result = m_rhs->execute(interpreter, global_object);
|
||||
if (interpreter.exception())
|
||||
return {};
|
||||
}
|
||||
|
||||
if (reference.is_unresolvable()) {
|
||||
interpreter.vm().throw_exception<ReferenceError>(global_object, ErrorType::InvalidLeftHandAssignment);
|
||||
return {};
|
||||
}
|
||||
if (reference.is_unresolvable()) {
|
||||
interpreter.vm().throw_exception<ReferenceError>(global_object, ErrorType::InvalidLeftHandAssignment);
|
||||
return {};
|
||||
}
|
||||
|
||||
reference.put_value(global_object, rhs_result);
|
||||
if (interpreter.exception())
|
||||
return {};
|
||||
reference.put_value(global_object, rhs_result);
|
||||
if (interpreter.exception())
|
||||
return {};
|
||||
|
||||
return rhs_result;
|
||||
return rhs_result;
|
||||
},
|
||||
[&](NonnullRefPtr<BindingPattern>& pattern) -> JS::Value {
|
||||
VERIFY(m_op == AssignmentOp::Assignment);
|
||||
|
||||
rhs_result = m_rhs->execute(interpreter, global_object);
|
||||
if (interpreter.exception())
|
||||
return {};
|
||||
|
||||
interpreter.vm().assign(pattern, rhs_result, global_object);
|
||||
if (interpreter.exception())
|
||||
return {};
|
||||
|
||||
return rhs_result;
|
||||
});
|
||||
}
|
||||
|
||||
Value UpdateExpression::execute(Interpreter& interpreter, GlobalObject& global_object) const
|
||||
|
@ -1693,7 +1710,7 @@ void AssignmentExpression::dump(int indent) const
|
|||
ASTNode::dump(indent);
|
||||
print_indent(indent + 1);
|
||||
outln("{}", op_string);
|
||||
m_lhs->dump(indent + 1);
|
||||
m_lhs.visit([&](auto& lhs) { lhs->dump(indent + 1); });
|
||||
m_rhs->dump(indent + 1);
|
||||
}
|
||||
|
||||
|
@ -2395,5 +2412,4 @@ void ScopeNode::add_hoisted_function(NonnullRefPtr<FunctionDeclaration> hoisted_
|
|||
{
|
||||
m_hoisted_functions.append(hoisted_function);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -998,13 +998,21 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
AssignmentExpression(SourceRange source_range, AssignmentOp op, NonnullRefPtr<BindingPattern> lhs, NonnullRefPtr<Expression> rhs)
|
||||
: Expression(source_range)
|
||||
, m_op(op)
|
||||
, m_lhs(move(lhs))
|
||||
, m_rhs(move(rhs))
|
||||
{
|
||||
}
|
||||
|
||||
virtual Value execute(Interpreter&, GlobalObject&) const override;
|
||||
virtual void dump(int indent) const override;
|
||||
virtual void generate_bytecode(Bytecode::Generator&) const override;
|
||||
|
||||
private:
|
||||
AssignmentOp m_op;
|
||||
NonnullRefPtr<Expression> m_lhs;
|
||||
Variant<NonnullRefPtr<Expression>, NonnullRefPtr<BindingPattern>> m_lhs;
|
||||
NonnullRefPtr<Expression> m_rhs;
|
||||
};
|
||||
|
||||
|
|
|
@ -272,8 +272,10 @@ void Identifier::generate_bytecode(Bytecode::Generator& generator) const
|
|||
|
||||
void AssignmentExpression::generate_bytecode(Bytecode::Generator& generator) const
|
||||
{
|
||||
if (is<Identifier>(*m_lhs)) {
|
||||
auto& identifier = static_cast<Identifier const&>(*m_lhs);
|
||||
// FIXME: Implement this for BindingPatterns too.
|
||||
auto& lhs = m_lhs.get<NonnullRefPtr<Expression>>();
|
||||
if (is<Identifier>(*lhs)) {
|
||||
auto& identifier = static_cast<Identifier const&>(*lhs);
|
||||
|
||||
if (m_op == AssignmentOp::Assignment) {
|
||||
m_rhs->generate_bytecode(generator);
|
||||
|
@ -281,7 +283,7 @@ void AssignmentExpression::generate_bytecode(Bytecode::Generator& generator) con
|
|||
return;
|
||||
}
|
||||
|
||||
m_lhs->generate_bytecode(generator);
|
||||
lhs->generate_bytecode(generator);
|
||||
|
||||
Bytecode::BasicBlock* rhs_block_ptr { nullptr };
|
||||
Bytecode::BasicBlock* end_block_ptr { nullptr };
|
||||
|
@ -377,8 +379,8 @@ void AssignmentExpression::generate_bytecode(Bytecode::Generator& generator) con
|
|||
return;
|
||||
}
|
||||
|
||||
if (is<MemberExpression>(*m_lhs)) {
|
||||
auto& expression = static_cast<MemberExpression const&>(*m_lhs);
|
||||
if (is<MemberExpression>(*lhs)) {
|
||||
auto& expression = static_cast<MemberExpression const&>(*lhs);
|
||||
expression.object().generate_bytecode(generator);
|
||||
auto object_reg = generator.allocate_register();
|
||||
generator.emit<Bytecode::Op::Store>(object_reg);
|
||||
|
|
|
@ -20,7 +20,7 @@ HashMap<char, TokenType> Lexer::s_single_char_tokens;
|
|||
|
||||
Lexer::Lexer(StringView source, StringView filename, size_t line_number, size_t line_column)
|
||||
: m_source(source)
|
||||
, m_current_token(TokenType::Eof, {}, StringView(nullptr), StringView(nullptr), filename, 0, 0)
|
||||
, m_current_token(TokenType::Eof, {}, StringView(nullptr), StringView(nullptr), filename, 0, 0, 0)
|
||||
, m_filename(filename)
|
||||
, m_line_number(line_number)
|
||||
, m_line_column(line_column)
|
||||
|
@ -659,7 +659,8 @@ Token Lexer::next()
|
|||
m_source.substring_view(value_start - 1, m_position - value_start),
|
||||
m_filename,
|
||||
value_start_line_number,
|
||||
value_start_column_number);
|
||||
value_start_column_number,
|
||||
m_position);
|
||||
|
||||
if constexpr (LEXER_DEBUG) {
|
||||
dbgln("------------------------------");
|
||||
|
|
|
@ -1305,6 +1305,36 @@ NonnullRefPtr<AssignmentExpression> Parser::parse_assignment_expression(Assignme
|
|||
|| match(TokenType::DoublePipeEquals)
|
||||
|| match(TokenType::DoubleQuestionMarkEquals));
|
||||
consume();
|
||||
|
||||
if (assignment_op == AssignmentOp::Assignment) {
|
||||
auto synthesize_binding_pattern = [this](Expression const& expression) -> RefPtr<BindingPattern> {
|
||||
// Clear any syntax error that has occurred in the range that 'expression' spans.
|
||||
m_state.errors.remove_all_matching([range = expression.source_range()](auto const& error) {
|
||||
return error.position.has_value() && range.contains(*error.position);
|
||||
});
|
||||
// Make a parser and parse the source for this expression as a binding pattern.
|
||||
auto source = m_state.lexer.source().substring_view(expression.source_range().start.offset - 2, expression.source_range().end.offset - expression.source_range().start.offset);
|
||||
Lexer lexer { source, m_state.lexer.filename(), expression.source_range().start.line, expression.source_range().start.column };
|
||||
Parser parser { lexer };
|
||||
auto result = parser.parse_binding_pattern();
|
||||
if (parser.has_errors()) {
|
||||
for (auto& error : parser.errors())
|
||||
syntax_error(move(error.message), move(error.position));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
if (is<ArrayExpression>(*lhs) || is<ObjectExpression>(*lhs)) {
|
||||
auto binding_pattern = synthesize_binding_pattern(*lhs);
|
||||
if (binding_pattern) {
|
||||
auto rhs = parse_expression(min_precedence, associativity);
|
||||
return create_ast_node<AssignmentExpression>(
|
||||
{ m_state.current_token.filename(), rule_start.position(), position() },
|
||||
assignment_op,
|
||||
binding_pattern.release_nonnull(),
|
||||
move(rhs));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!is<Identifier>(*lhs) && !is<MemberExpression>(*lhs) && !is<CallExpression>(*lhs)) {
|
||||
syntax_error("Invalid left-hand side in assignment");
|
||||
} else if (m_state.strict_mode && is<Identifier>(*lhs)) {
|
||||
|
@ -2398,7 +2428,8 @@ Position Parser::position() const
|
|||
{
|
||||
return {
|
||||
m_state.current_token.line_number(),
|
||||
m_state.current_token.line_column()
|
||||
m_state.current_token.line_column(),
|
||||
m_state.current_token.offset(),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -13,9 +13,12 @@ namespace JS {
|
|||
struct Position {
|
||||
size_t line { 0 };
|
||||
size_t column { 0 };
|
||||
size_t offset { 0 };
|
||||
};
|
||||
|
||||
struct SourceRange {
|
||||
[[nodiscard]] bool contains(Position const& position) const { return position.offset <= end.offset && position.offset >= start.offset; }
|
||||
|
||||
StringView filename;
|
||||
Position start;
|
||||
Position end;
|
||||
|
|
|
@ -161,7 +161,7 @@ enum class TokenCategory {
|
|||
|
||||
class Token {
|
||||
public:
|
||||
Token(TokenType type, String message, StringView trivia, StringView value, StringView filename, size_t line_number, size_t line_column)
|
||||
Token(TokenType type, String message, StringView trivia, StringView value, StringView filename, size_t line_number, size_t line_column, size_t offset)
|
||||
: m_type(type)
|
||||
, m_message(message)
|
||||
, m_trivia(trivia)
|
||||
|
@ -169,6 +169,7 @@ public:
|
|||
, m_filename(filename)
|
||||
, m_line_number(line_number)
|
||||
, m_line_column(line_column)
|
||||
, m_offset(offset)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -184,6 +185,7 @@ public:
|
|||
const StringView& filename() const { return m_filename; }
|
||||
size_t line_number() const { return m_line_number; }
|
||||
size_t line_column() const { return m_line_column; }
|
||||
size_t offset() const { return m_offset; }
|
||||
double double_value() const;
|
||||
bool bool_value() const;
|
||||
|
||||
|
@ -207,6 +209,7 @@ private:
|
|||
StringView m_filename;
|
||||
size_t m_line_number;
|
||||
size_t m_line_column;
|
||||
size_t m_offset;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue