1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 15:37:46 +00:00

LibJS: Implement parsing and evaluation for AssignmentPatterns

e.g. `[...foo] = bar` can now be evaluated :^)
This commit is contained in:
Ali Mohammad Pur 2021-07-11 01:16:17 +04:30 committed by Linus Groh
parent 7fc6cd6b20
commit 1a9518ebe3
7 changed files with 104 additions and 40 deletions

View file

@ -1484,11 +1484,18 @@ Value AssignmentExpression::execute(Interpreter& interpreter, GlobalObject& glob
{ {
InterpreterNodeScope node_scope { interpreter, *this }; 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() \ #define EXECUTE_LHS_AND_RHS() \
do { \ do { \
lhs_result = m_lhs->execute(interpreter, global_object); \ EXECUTE_LHS(); \
if (interpreter.exception()) \
return {}; \
rhs_result = m_rhs->execute(interpreter, global_object); \ rhs_result = m_rhs->execute(interpreter, global_object); \
if (interpreter.exception()) \ if (interpreter.exception()) \
return {}; \ return {}; \
@ -1548,25 +1555,19 @@ Value AssignmentExpression::execute(Interpreter& interpreter, GlobalObject& glob
rhs_result = unsigned_right_shift(global_object, lhs_result, rhs_result); rhs_result = unsigned_right_shift(global_object, lhs_result, rhs_result);
break; break;
case AssignmentOp::AndAssignment: case AssignmentOp::AndAssignment:
lhs_result = m_lhs->execute(interpreter, global_object); EXECUTE_LHS();
if (interpreter.exception())
return {};
if (!lhs_result.to_boolean()) if (!lhs_result.to_boolean())
return lhs_result; return lhs_result;
rhs_result = m_rhs->execute(interpreter, global_object); rhs_result = m_rhs->execute(interpreter, global_object);
break; break;
case AssignmentOp::OrAssignment: case AssignmentOp::OrAssignment:
lhs_result = m_lhs->execute(interpreter, global_object); EXECUTE_LHS();
if (interpreter.exception())
return {};
if (lhs_result.to_boolean()) if (lhs_result.to_boolean())
return lhs_result; return lhs_result;
rhs_result = m_rhs->execute(interpreter, global_object); rhs_result = m_rhs->execute(interpreter, global_object);
break; break;
case AssignmentOp::NullishAssignment: case AssignmentOp::NullishAssignment:
lhs_result = m_lhs->execute(interpreter, global_object); EXECUTE_LHS();
if (interpreter.exception())
return {};
if (!lhs_result.is_nullish()) if (!lhs_result.is_nullish())
return lhs_result; return lhs_result;
rhs_result = m_rhs->execute(interpreter, global_object); rhs_result = m_rhs->execute(interpreter, global_object);
@ -1575,26 +1576,42 @@ Value AssignmentExpression::execute(Interpreter& interpreter, GlobalObject& glob
if (interpreter.exception()) if (interpreter.exception())
return {}; return {};
auto reference = m_lhs->to_reference(interpreter, global_object); return m_lhs.visit(
if (interpreter.exception()) [&](NonnullRefPtr<Expression>& lhs) -> JS::Value {
return {}; auto reference = lhs->to_reference(interpreter, global_object);
if (interpreter.exception())
return {};
if (m_op == AssignmentOp::Assignment) { if (m_op == AssignmentOp::Assignment) {
rhs_result = m_rhs->execute(interpreter, global_object); rhs_result = m_rhs->execute(interpreter, global_object);
if (interpreter.exception()) if (interpreter.exception())
return {}; return {};
} }
if (reference.is_unresolvable()) { if (reference.is_unresolvable()) {
interpreter.vm().throw_exception<ReferenceError>(global_object, ErrorType::InvalidLeftHandAssignment); interpreter.vm().throw_exception<ReferenceError>(global_object, ErrorType::InvalidLeftHandAssignment);
return {}; return {};
} }
reference.put_value(global_object, rhs_result); reference.put_value(global_object, rhs_result);
if (interpreter.exception()) if (interpreter.exception())
return {}; 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 Value UpdateExpression::execute(Interpreter& interpreter, GlobalObject& global_object) const
@ -1693,7 +1710,7 @@ void AssignmentExpression::dump(int indent) const
ASTNode::dump(indent); ASTNode::dump(indent);
print_indent(indent + 1); print_indent(indent + 1);
outln("{}", op_string); outln("{}", op_string);
m_lhs->dump(indent + 1); m_lhs.visit([&](auto& lhs) { lhs->dump(indent + 1); });
m_rhs->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); m_hoisted_functions.append(hoisted_function);
} }
} }

View file

@ -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 Value execute(Interpreter&, GlobalObject&) const override;
virtual void dump(int indent) const override; virtual void dump(int indent) const override;
virtual void generate_bytecode(Bytecode::Generator&) const override; virtual void generate_bytecode(Bytecode::Generator&) const override;
private: private:
AssignmentOp m_op; AssignmentOp m_op;
NonnullRefPtr<Expression> m_lhs; Variant<NonnullRefPtr<Expression>, NonnullRefPtr<BindingPattern>> m_lhs;
NonnullRefPtr<Expression> m_rhs; NonnullRefPtr<Expression> m_rhs;
}; };

View file

@ -272,8 +272,10 @@ void Identifier::generate_bytecode(Bytecode::Generator& generator) const
void AssignmentExpression::generate_bytecode(Bytecode::Generator& generator) const void AssignmentExpression::generate_bytecode(Bytecode::Generator& generator) const
{ {
if (is<Identifier>(*m_lhs)) { // FIXME: Implement this for BindingPatterns too.
auto& identifier = static_cast<Identifier const&>(*m_lhs); auto& lhs = m_lhs.get<NonnullRefPtr<Expression>>();
if (is<Identifier>(*lhs)) {
auto& identifier = static_cast<Identifier const&>(*lhs);
if (m_op == AssignmentOp::Assignment) { if (m_op == AssignmentOp::Assignment) {
m_rhs->generate_bytecode(generator); m_rhs->generate_bytecode(generator);
@ -281,7 +283,7 @@ void AssignmentExpression::generate_bytecode(Bytecode::Generator& generator) con
return; return;
} }
m_lhs->generate_bytecode(generator); lhs->generate_bytecode(generator);
Bytecode::BasicBlock* rhs_block_ptr { nullptr }; Bytecode::BasicBlock* rhs_block_ptr { nullptr };
Bytecode::BasicBlock* end_block_ptr { nullptr }; Bytecode::BasicBlock* end_block_ptr { nullptr };
@ -377,8 +379,8 @@ void AssignmentExpression::generate_bytecode(Bytecode::Generator& generator) con
return; return;
} }
if (is<MemberExpression>(*m_lhs)) { if (is<MemberExpression>(*lhs)) {
auto& expression = static_cast<MemberExpression const&>(*m_lhs); auto& expression = static_cast<MemberExpression const&>(*lhs);
expression.object().generate_bytecode(generator); expression.object().generate_bytecode(generator);
auto object_reg = generator.allocate_register(); auto object_reg = generator.allocate_register();
generator.emit<Bytecode::Op::Store>(object_reg); generator.emit<Bytecode::Op::Store>(object_reg);

View file

@ -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) Lexer::Lexer(StringView source, StringView filename, size_t line_number, size_t line_column)
: m_source(source) : 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_filename(filename)
, m_line_number(line_number) , m_line_number(line_number)
, m_line_column(line_column) , m_line_column(line_column)
@ -659,7 +659,8 @@ Token Lexer::next()
m_source.substring_view(value_start - 1, m_position - value_start), m_source.substring_view(value_start - 1, m_position - value_start),
m_filename, m_filename,
value_start_line_number, value_start_line_number,
value_start_column_number); value_start_column_number,
m_position);
if constexpr (LEXER_DEBUG) { if constexpr (LEXER_DEBUG) {
dbgln("------------------------------"); dbgln("------------------------------");

View file

@ -1305,6 +1305,36 @@ NonnullRefPtr<AssignmentExpression> Parser::parse_assignment_expression(Assignme
|| match(TokenType::DoublePipeEquals) || match(TokenType::DoublePipeEquals)
|| match(TokenType::DoubleQuestionMarkEquals)); || match(TokenType::DoubleQuestionMarkEquals));
consume(); 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)) { if (!is<Identifier>(*lhs) && !is<MemberExpression>(*lhs) && !is<CallExpression>(*lhs)) {
syntax_error("Invalid left-hand side in assignment"); syntax_error("Invalid left-hand side in assignment");
} else if (m_state.strict_mode && is<Identifier>(*lhs)) { } else if (m_state.strict_mode && is<Identifier>(*lhs)) {
@ -2398,7 +2428,8 @@ Position Parser::position() const
{ {
return { return {
m_state.current_token.line_number(), m_state.current_token.line_number(),
m_state.current_token.line_column() m_state.current_token.line_column(),
m_state.current_token.offset(),
}; };
} }

View file

@ -13,9 +13,12 @@ namespace JS {
struct Position { struct Position {
size_t line { 0 }; size_t line { 0 };
size_t column { 0 }; size_t column { 0 };
size_t offset { 0 };
}; };
struct SourceRange { struct SourceRange {
[[nodiscard]] bool contains(Position const& position) const { return position.offset <= end.offset && position.offset >= start.offset; }
StringView filename; StringView filename;
Position start; Position start;
Position end; Position end;

View file

@ -161,7 +161,7 @@ enum class TokenCategory {
class Token { class Token {
public: 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_type(type)
, m_message(message) , m_message(message)
, m_trivia(trivia) , m_trivia(trivia)
@ -169,6 +169,7 @@ public:
, m_filename(filename) , m_filename(filename)
, m_line_number(line_number) , m_line_number(line_number)
, m_line_column(line_column) , m_line_column(line_column)
, m_offset(offset)
{ {
} }
@ -184,6 +185,7 @@ public:
const StringView& filename() const { return m_filename; } const StringView& filename() const { return m_filename; }
size_t line_number() const { return m_line_number; } size_t line_number() const { return m_line_number; }
size_t line_column() const { return m_line_column; } size_t line_column() const { return m_line_column; }
size_t offset() const { return m_offset; }
double double_value() const; double double_value() const;
bool bool_value() const; bool bool_value() const;
@ -207,6 +209,7 @@ private:
StringView m_filename; StringView m_filename;
size_t m_line_number; size_t m_line_number;
size_t m_line_column; size_t m_line_column;
size_t m_offset;
}; };
} }