LibJS: Implement logical assignment operators (&&=, ||=, ??=)
TC39 proposal, stage 4 as of 2020-07. https://tc39.es/proposal-logical-assignment/
This commit is contained in:
parent
d8d00d3ac7
commit
aa71dae03c
Notes:
sideshowbarker
2024-07-19 02:02:06 +09:00
Author: https://github.com/linusg Commit: https://github.com/SerenityOS/serenity/commit/aa71dae03c0 Pull-request: https://github.com/SerenityOS/serenity/pull/3695
6 changed files with 108 additions and 6 deletions
|
@ -1261,6 +1261,30 @@ Value AssignmentExpression::execute(Interpreter& interpreter, GlobalObject& glob
|
||||||
EXECUTE_LHS_AND_RHS();
|
EXECUTE_LHS_AND_RHS();
|
||||||
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:
|
||||||
|
lhs_result = m_lhs->execute(interpreter, global_object);
|
||||||
|
if (interpreter.exception())
|
||||||
|
return {};
|
||||||
|
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 {};
|
||||||
|
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 {};
|
||||||
|
if (!lhs_result.is_nullish())
|
||||||
|
return lhs_result;
|
||||||
|
rhs_result = m_rhs->execute(interpreter, global_object);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
if (interpreter.exception())
|
if (interpreter.exception())
|
||||||
return {};
|
return {};
|
||||||
|
@ -1366,6 +1390,15 @@ void AssignmentExpression::dump(int indent) const
|
||||||
case AssignmentOp::UnsignedRightShiftAssignment:
|
case AssignmentOp::UnsignedRightShiftAssignment:
|
||||||
op_string = ">>>=";
|
op_string = ">>>=";
|
||||||
break;
|
break;
|
||||||
|
case AssignmentOp::AndAssignment:
|
||||||
|
op_string = "&&=";
|
||||||
|
break;
|
||||||
|
case AssignmentOp::OrAssignment:
|
||||||
|
op_string = "||=";
|
||||||
|
break;
|
||||||
|
case AssignmentOp::NullishAssignment:
|
||||||
|
op_string = "\?\?=";
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
ASTNode::dump(indent);
|
ASTNode::dump(indent);
|
||||||
|
|
|
@ -816,6 +816,9 @@ enum class AssignmentOp {
|
||||||
LeftShiftAssignment,
|
LeftShiftAssignment,
|
||||||
RightShiftAssignment,
|
RightShiftAssignment,
|
||||||
UnsignedRightShiftAssignment,
|
UnsignedRightShiftAssignment,
|
||||||
|
AndAssignment,
|
||||||
|
OrAssignment,
|
||||||
|
NullishAssignment,
|
||||||
};
|
};
|
||||||
|
|
||||||
class AssignmentExpression final : public Expression {
|
class AssignmentExpression final : public Expression {
|
||||||
|
|
|
@ -89,6 +89,9 @@ Lexer::Lexer(StringView source)
|
||||||
s_three_char_tokens.set("**=", TokenType::DoubleAsteriskEquals);
|
s_three_char_tokens.set("**=", TokenType::DoubleAsteriskEquals);
|
||||||
s_three_char_tokens.set("<<=", TokenType::ShiftLeftEquals);
|
s_three_char_tokens.set("<<=", TokenType::ShiftLeftEquals);
|
||||||
s_three_char_tokens.set(">>=", TokenType::ShiftRightEquals);
|
s_three_char_tokens.set(">>=", TokenType::ShiftRightEquals);
|
||||||
|
s_three_char_tokens.set("&&=", TokenType::DoubleAmpersandEquals);
|
||||||
|
s_three_char_tokens.set("||=", TokenType::DoublePipeEquals);
|
||||||
|
s_three_char_tokens.set("\?\?=", TokenType::DoubleQuestionMarkEquals);
|
||||||
s_three_char_tokens.set(">>>", TokenType::UnsignedShiftRight);
|
s_three_char_tokens.set(">>>", TokenType::UnsignedShiftRight);
|
||||||
s_three_char_tokens.set("...", TokenType::TripleDot);
|
s_three_char_tokens.set("...", TokenType::TripleDot);
|
||||||
}
|
}
|
||||||
|
|
|
@ -165,8 +165,11 @@ private:
|
||||||
{ TokenType::ShiftRightEquals, 3 },
|
{ TokenType::ShiftRightEquals, 3 },
|
||||||
{ TokenType::UnsignedShiftRightEquals, 3 },
|
{ TokenType::UnsignedShiftRightEquals, 3 },
|
||||||
{ TokenType::AmpersandEquals, 3 },
|
{ TokenType::AmpersandEquals, 3 },
|
||||||
{ TokenType::PipeEquals, 3 },
|
|
||||||
{ TokenType::CaretEquals, 3 },
|
{ TokenType::CaretEquals, 3 },
|
||||||
|
{ TokenType::PipeEquals, 3 },
|
||||||
|
{ TokenType::DoubleAmpersandEquals, 3 },
|
||||||
|
{ TokenType::DoublePipeEquals, 3 },
|
||||||
|
{ TokenType::DoubleQuestionMarkEquals, 3 },
|
||||||
|
|
||||||
{ TokenType::Yield, 2 },
|
{ TokenType::Yield, 2 },
|
||||||
|
|
||||||
|
@ -1092,12 +1095,18 @@ NonnullRefPtr<Expression> Parser::parse_secondary_expression(NonnullRefPtr<Expre
|
||||||
case TokenType::DoubleAmpersand:
|
case TokenType::DoubleAmpersand:
|
||||||
consume();
|
consume();
|
||||||
return create_ast_node<LogicalExpression>(LogicalOp::And, move(lhs), parse_expression(min_precedence, associativity));
|
return create_ast_node<LogicalExpression>(LogicalOp::And, move(lhs), parse_expression(min_precedence, associativity));
|
||||||
|
case TokenType::DoubleAmpersandEquals:
|
||||||
|
return parse_assignment_expression(AssignmentOp::AndAssignment, move(lhs), min_precedence, associativity);
|
||||||
case TokenType::DoublePipe:
|
case TokenType::DoublePipe:
|
||||||
consume();
|
consume();
|
||||||
return create_ast_node<LogicalExpression>(LogicalOp::Or, move(lhs), parse_expression(min_precedence, associativity));
|
return create_ast_node<LogicalExpression>(LogicalOp::Or, move(lhs), parse_expression(min_precedence, associativity));
|
||||||
|
case TokenType::DoublePipeEquals:
|
||||||
|
return parse_assignment_expression(AssignmentOp::OrAssignment, move(lhs), min_precedence, associativity);
|
||||||
case TokenType::DoubleQuestionMark:
|
case TokenType::DoubleQuestionMark:
|
||||||
consume();
|
consume();
|
||||||
return create_ast_node<LogicalExpression>(LogicalOp::NullishCoalescing, move(lhs), parse_expression(min_precedence, associativity));
|
return create_ast_node<LogicalExpression>(LogicalOp::NullishCoalescing, move(lhs), parse_expression(min_precedence, associativity));
|
||||||
|
case TokenType::DoubleQuestionMarkEquals:
|
||||||
|
return parse_assignment_expression(AssignmentOp::NullishAssignment, move(lhs), min_precedence, associativity);
|
||||||
case TokenType::QuestionMark:
|
case TokenType::QuestionMark:
|
||||||
return parse_conditional_expression(move(lhs));
|
return parse_conditional_expression(move(lhs));
|
||||||
default:
|
default:
|
||||||
|
@ -1121,7 +1130,10 @@ NonnullRefPtr<AssignmentExpression> Parser::parse_assignment_expression(Assignme
|
||||||
|| match(TokenType::CaretEquals)
|
|| match(TokenType::CaretEquals)
|
||||||
|| match(TokenType::ShiftLeftEquals)
|
|| match(TokenType::ShiftLeftEquals)
|
||||||
|| match(TokenType::ShiftRightEquals)
|
|| match(TokenType::ShiftRightEquals)
|
||||||
|| match(TokenType::UnsignedShiftRightEquals));
|
|| match(TokenType::UnsignedShiftRightEquals)
|
||||||
|
|| match(TokenType::DoubleAmpersandEquals)
|
||||||
|
|| match(TokenType::DoublePipeEquals)
|
||||||
|
|| match(TokenType::DoubleQuestionMarkEquals));
|
||||||
consume();
|
consume();
|
||||||
if (!lhs->is_identifier() && !lhs->is_member_expression() && !lhs->is_call_expression()) {
|
if (!lhs->is_identifier() && !lhs->is_member_expression() && !lhs->is_call_expression()) {
|
||||||
syntax_error("Invalid left-hand side in assignment");
|
syntax_error("Invalid left-hand side in assignment");
|
||||||
|
@ -1705,8 +1717,11 @@ bool Parser::match_secondary_expression(Vector<TokenType> forbidden) const
|
||||||
|| type == TokenType::UnsignedShiftRight
|
|| type == TokenType::UnsignedShiftRight
|
||||||
|| type == TokenType::UnsignedShiftRightEquals
|
|| type == TokenType::UnsignedShiftRightEquals
|
||||||
|| type == TokenType::DoubleAmpersand
|
|| type == TokenType::DoubleAmpersand
|
||||||
|
|| type == TokenType::DoubleAmpersandEquals
|
||||||
|| type == TokenType::DoublePipe
|
|| type == TokenType::DoublePipe
|
||||||
|| type == TokenType::DoubleQuestionMark;
|
|| type == TokenType::DoublePipeEquals
|
||||||
|
|| type == TokenType::DoubleQuestionMark
|
||||||
|
|| type == TokenType::DoubleQuestionMarkEquals;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Parser::match_statement() const
|
bool Parser::match_statement() const
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
let x;
|
let x, o;
|
||||||
|
|
||||||
test("basic functionality", () => {
|
test("basic functionality", () => {
|
||||||
x = 1;
|
x = 1;
|
||||||
|
@ -54,6 +54,48 @@ test("basic functionality", () => {
|
||||||
expect(x).toBe(2);
|
expect(x).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("logical assignment operators", () => {
|
||||||
|
// short circuiting evaluation
|
||||||
|
x = false;
|
||||||
|
expect((x &&= expect.fail())).toBeFalse();
|
||||||
|
|
||||||
|
x = true;
|
||||||
|
expect((x ||= expect.fail())).toBeTrue();
|
||||||
|
|
||||||
|
x = "foo";
|
||||||
|
expect((x ??= expect.fail())).toBe("foo");
|
||||||
|
|
||||||
|
const prepareObject = (shortCircuitValue, assignmentValue) => ({
|
||||||
|
get shortCircuit() {
|
||||||
|
return shortCircuitValue;
|
||||||
|
},
|
||||||
|
set shortCircuit(_) {
|
||||||
|
// assignment will short circuit in all test cases
|
||||||
|
// so its setter must never be called
|
||||||
|
expect().fail();
|
||||||
|
},
|
||||||
|
assignment: assignmentValue,
|
||||||
|
});
|
||||||
|
|
||||||
|
o = prepareObject(false, true);
|
||||||
|
expect((o.shortCircuit &&= "foo")).toBeFalse();
|
||||||
|
expect(o.shortCircuit).toBeFalse();
|
||||||
|
expect((o.assignment &&= "bar")).toBe("bar");
|
||||||
|
expect(o.assignment).toBe("bar");
|
||||||
|
|
||||||
|
o = prepareObject(true, false);
|
||||||
|
expect((o.shortCircuit ||= "foo")).toBeTrue();
|
||||||
|
expect(o.shortCircuit).toBeTrue();
|
||||||
|
expect((o.assignment ||= "bar")).toBe("bar");
|
||||||
|
expect(o.assignment).toBe("bar");
|
||||||
|
|
||||||
|
o = prepareObject("test", null);
|
||||||
|
expect((o.shortCircuit ??= "foo")).toBe("test");
|
||||||
|
expect(o.shortCircuit).toBe("test");
|
||||||
|
expect((o.assignment ??= "bar")).toBe("bar");
|
||||||
|
expect(o.assignment).toBe("bar");
|
||||||
|
});
|
||||||
|
|
||||||
test("evaluation order", () => {
|
test("evaluation order", () => {
|
||||||
for (const op of [
|
for (const op of [
|
||||||
"=",
|
"=",
|
||||||
|
@ -69,6 +111,9 @@ test("evaluation order", () => {
|
||||||
"<<=",
|
"<<=",
|
||||||
">>=",
|
">>=",
|
||||||
">>>=",
|
">>>=",
|
||||||
|
"&&=",
|
||||||
|
"||=",
|
||||||
|
"??=",
|
||||||
]) {
|
]) {
|
||||||
var a = [];
|
var a = [];
|
||||||
function b() {
|
function b() {
|
||||||
|
|
|
@ -36,7 +36,6 @@ namespace JS {
|
||||||
__ENUMERATE_JS_TOKEN(AmpersandEquals, Operator) \
|
__ENUMERATE_JS_TOKEN(AmpersandEquals, Operator) \
|
||||||
__ENUMERATE_JS_TOKEN(Arrow, Operator) \
|
__ENUMERATE_JS_TOKEN(Arrow, Operator) \
|
||||||
__ENUMERATE_JS_TOKEN(Asterisk, Operator) \
|
__ENUMERATE_JS_TOKEN(Asterisk, Operator) \
|
||||||
__ENUMERATE_JS_TOKEN(DoubleAsteriskEquals, Operator) \
|
|
||||||
__ENUMERATE_JS_TOKEN(AsteriskEquals, Operator) \
|
__ENUMERATE_JS_TOKEN(AsteriskEquals, Operator) \
|
||||||
__ENUMERATE_JS_TOKEN(Async, Keyword) \
|
__ENUMERATE_JS_TOKEN(Async, Keyword) \
|
||||||
__ENUMERATE_JS_TOKEN(Await, Keyword) \
|
__ENUMERATE_JS_TOKEN(Await, Keyword) \
|
||||||
|
@ -61,9 +60,13 @@ namespace JS {
|
||||||
__ENUMERATE_JS_TOKEN(Delete, Keyword) \
|
__ENUMERATE_JS_TOKEN(Delete, Keyword) \
|
||||||
__ENUMERATE_JS_TOKEN(Do, ControlKeyword) \
|
__ENUMERATE_JS_TOKEN(Do, ControlKeyword) \
|
||||||
__ENUMERATE_JS_TOKEN(DoubleAmpersand, Operator) \
|
__ENUMERATE_JS_TOKEN(DoubleAmpersand, Operator) \
|
||||||
|
__ENUMERATE_JS_TOKEN(DoubleAmpersandEquals, Operator) \
|
||||||
__ENUMERATE_JS_TOKEN(DoubleAsterisk, Operator) \
|
__ENUMERATE_JS_TOKEN(DoubleAsterisk, Operator) \
|
||||||
|
__ENUMERATE_JS_TOKEN(DoubleAsteriskEquals, Operator) \
|
||||||
__ENUMERATE_JS_TOKEN(DoublePipe, Operator) \
|
__ENUMERATE_JS_TOKEN(DoublePipe, Operator) \
|
||||||
|
__ENUMERATE_JS_TOKEN(DoublePipeEquals, Operator) \
|
||||||
__ENUMERATE_JS_TOKEN(DoubleQuestionMark, Operator) \
|
__ENUMERATE_JS_TOKEN(DoubleQuestionMark, Operator) \
|
||||||
|
__ENUMERATE_JS_TOKEN(DoubleQuestionMarkEquals, Operator) \
|
||||||
__ENUMERATE_JS_TOKEN(Else, ControlKeyword) \
|
__ENUMERATE_JS_TOKEN(Else, ControlKeyword) \
|
||||||
__ENUMERATE_JS_TOKEN(Enum, Keyword) \
|
__ENUMERATE_JS_TOKEN(Enum, Keyword) \
|
||||||
__ENUMERATE_JS_TOKEN(Eof, Invalid) \
|
__ENUMERATE_JS_TOKEN(Eof, Invalid) \
|
||||||
|
@ -113,8 +116,8 @@ namespace JS {
|
||||||
__ENUMERATE_JS_TOKEN(Public, Keyword) \
|
__ENUMERATE_JS_TOKEN(Public, Keyword) \
|
||||||
__ENUMERATE_JS_TOKEN(QuestionMark, Operator) \
|
__ENUMERATE_JS_TOKEN(QuestionMark, Operator) \
|
||||||
__ENUMERATE_JS_TOKEN(QuestionMarkPeriod, Operator) \
|
__ENUMERATE_JS_TOKEN(QuestionMarkPeriod, Operator) \
|
||||||
__ENUMERATE_JS_TOKEN(RegexLiteral, String) \
|
|
||||||
__ENUMERATE_JS_TOKEN(RegexFlags, String) \
|
__ENUMERATE_JS_TOKEN(RegexFlags, String) \
|
||||||
|
__ENUMERATE_JS_TOKEN(RegexLiteral, String) \
|
||||||
__ENUMERATE_JS_TOKEN(Return, ControlKeyword) \
|
__ENUMERATE_JS_TOKEN(Return, ControlKeyword) \
|
||||||
__ENUMERATE_JS_TOKEN(Semicolon, Punctuation) \
|
__ENUMERATE_JS_TOKEN(Semicolon, Punctuation) \
|
||||||
__ENUMERATE_JS_TOKEN(ShiftLeft, Operator) \
|
__ENUMERATE_JS_TOKEN(ShiftLeft, Operator) \
|
||||||
|
|
Loading…
Add table
Reference in a new issue