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:
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 };
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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("------------------------------");
|
||||||
|
|
|
@ -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(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue