LibJS: Implement logical assignment operators (&&=, ||=, ??=)

TC39 proposal, stage 4 as of 2020-07.
https://tc39.es/proposal-logical-assignment/
This commit is contained in:
Linus Groh 2020-10-05 16:49:43 +01:00 committed by Andreas Kling
parent d8d00d3ac7
commit aa71dae03c
Notes: sideshowbarker 2024-07-19 02:02:06 +09:00
6 changed files with 108 additions and 6 deletions

View file

@ -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);

View file

@ -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 {

View file

@ -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);
} }

View file

@ -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

View file

@ -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() {

View file

@ -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) \