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();
|
||||
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 {};
|
||||
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())
|
||||
return {};
|
||||
|
@ -1366,6 +1390,15 @@ void AssignmentExpression::dump(int indent) const
|
|||
case AssignmentOp::UnsignedRightShiftAssignment:
|
||||
op_string = ">>>=";
|
||||
break;
|
||||
case AssignmentOp::AndAssignment:
|
||||
op_string = "&&=";
|
||||
break;
|
||||
case AssignmentOp::OrAssignment:
|
||||
op_string = "||=";
|
||||
break;
|
||||
case AssignmentOp::NullishAssignment:
|
||||
op_string = "\?\?=";
|
||||
break;
|
||||
}
|
||||
|
||||
ASTNode::dump(indent);
|
||||
|
|
|
@ -816,6 +816,9 @@ enum class AssignmentOp {
|
|||
LeftShiftAssignment,
|
||||
RightShiftAssignment,
|
||||
UnsignedRightShiftAssignment,
|
||||
AndAssignment,
|
||||
OrAssignment,
|
||||
NullishAssignment,
|
||||
};
|
||||
|
||||
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::ShiftLeftEquals);
|
||||
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::TripleDot);
|
||||
}
|
||||
|
|
|
@ -165,8 +165,11 @@ private:
|
|||
{ TokenType::ShiftRightEquals, 3 },
|
||||
{ TokenType::UnsignedShiftRightEquals, 3 },
|
||||
{ TokenType::AmpersandEquals, 3 },
|
||||
{ TokenType::PipeEquals, 3 },
|
||||
{ TokenType::CaretEquals, 3 },
|
||||
{ TokenType::PipeEquals, 3 },
|
||||
{ TokenType::DoubleAmpersandEquals, 3 },
|
||||
{ TokenType::DoublePipeEquals, 3 },
|
||||
{ TokenType::DoubleQuestionMarkEquals, 3 },
|
||||
|
||||
{ TokenType::Yield, 2 },
|
||||
|
||||
|
@ -1092,12 +1095,18 @@ NonnullRefPtr<Expression> Parser::parse_secondary_expression(NonnullRefPtr<Expre
|
|||
case TokenType::DoubleAmpersand:
|
||||
consume();
|
||||
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:
|
||||
consume();
|
||||
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:
|
||||
consume();
|
||||
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:
|
||||
return parse_conditional_expression(move(lhs));
|
||||
default:
|
||||
|
@ -1121,7 +1130,10 @@ NonnullRefPtr<AssignmentExpression> Parser::parse_assignment_expression(Assignme
|
|||
|| match(TokenType::CaretEquals)
|
||||
|| match(TokenType::ShiftLeftEquals)
|
||||
|| match(TokenType::ShiftRightEquals)
|
||||
|| match(TokenType::UnsignedShiftRightEquals));
|
||||
|| match(TokenType::UnsignedShiftRightEquals)
|
||||
|| match(TokenType::DoubleAmpersandEquals)
|
||||
|| match(TokenType::DoublePipeEquals)
|
||||
|| match(TokenType::DoubleQuestionMarkEquals));
|
||||
consume();
|
||||
if (!lhs->is_identifier() && !lhs->is_member_expression() && !lhs->is_call_expression()) {
|
||||
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::UnsignedShiftRightEquals
|
||||
|| type == TokenType::DoubleAmpersand
|
||||
|| type == TokenType::DoubleAmpersandEquals
|
||||
|| type == TokenType::DoublePipe
|
||||
|| type == TokenType::DoubleQuestionMark;
|
||||
|| type == TokenType::DoublePipeEquals
|
||||
|| type == TokenType::DoubleQuestionMark
|
||||
|| type == TokenType::DoubleQuestionMarkEquals;
|
||||
}
|
||||
|
||||
bool Parser::match_statement() const
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
let x;
|
||||
let x, o;
|
||||
|
||||
test("basic functionality", () => {
|
||||
x = 1;
|
||||
|
@ -54,6 +54,48 @@ test("basic functionality", () => {
|
|||
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", () => {
|
||||
for (const op of [
|
||||
"=",
|
||||
|
@ -69,6 +111,9 @@ test("evaluation order", () => {
|
|||
"<<=",
|
||||
">>=",
|
||||
">>>=",
|
||||
"&&=",
|
||||
"||=",
|
||||
"??=",
|
||||
]) {
|
||||
var a = [];
|
||||
function b() {
|
||||
|
|
|
@ -36,7 +36,6 @@ namespace JS {
|
|||
__ENUMERATE_JS_TOKEN(AmpersandEquals, Operator) \
|
||||
__ENUMERATE_JS_TOKEN(Arrow, Operator) \
|
||||
__ENUMERATE_JS_TOKEN(Asterisk, Operator) \
|
||||
__ENUMERATE_JS_TOKEN(DoubleAsteriskEquals, Operator) \
|
||||
__ENUMERATE_JS_TOKEN(AsteriskEquals, Operator) \
|
||||
__ENUMERATE_JS_TOKEN(Async, Keyword) \
|
||||
__ENUMERATE_JS_TOKEN(Await, Keyword) \
|
||||
|
@ -61,9 +60,13 @@ namespace JS {
|
|||
__ENUMERATE_JS_TOKEN(Delete, Keyword) \
|
||||
__ENUMERATE_JS_TOKEN(Do, ControlKeyword) \
|
||||
__ENUMERATE_JS_TOKEN(DoubleAmpersand, Operator) \
|
||||
__ENUMERATE_JS_TOKEN(DoubleAmpersandEquals, Operator) \
|
||||
__ENUMERATE_JS_TOKEN(DoubleAsterisk, Operator) \
|
||||
__ENUMERATE_JS_TOKEN(DoubleAsteriskEquals, Operator) \
|
||||
__ENUMERATE_JS_TOKEN(DoublePipe, Operator) \
|
||||
__ENUMERATE_JS_TOKEN(DoublePipeEquals, Operator) \
|
||||
__ENUMERATE_JS_TOKEN(DoubleQuestionMark, Operator) \
|
||||
__ENUMERATE_JS_TOKEN(DoubleQuestionMarkEquals, Operator) \
|
||||
__ENUMERATE_JS_TOKEN(Else, ControlKeyword) \
|
||||
__ENUMERATE_JS_TOKEN(Enum, Keyword) \
|
||||
__ENUMERATE_JS_TOKEN(Eof, Invalid) \
|
||||
|
@ -113,8 +116,8 @@ namespace JS {
|
|||
__ENUMERATE_JS_TOKEN(Public, Keyword) \
|
||||
__ENUMERATE_JS_TOKEN(QuestionMark, Operator) \
|
||||
__ENUMERATE_JS_TOKEN(QuestionMarkPeriod, Operator) \
|
||||
__ENUMERATE_JS_TOKEN(RegexLiteral, String) \
|
||||
__ENUMERATE_JS_TOKEN(RegexFlags, String) \
|
||||
__ENUMERATE_JS_TOKEN(RegexLiteral, String) \
|
||||
__ENUMERATE_JS_TOKEN(Return, ControlKeyword) \
|
||||
__ENUMERATE_JS_TOKEN(Semicolon, Punctuation) \
|
||||
__ENUMERATE_JS_TOKEN(ShiftLeft, Operator) \
|
||||
|
|
Loading…
Add table
Reference in a new issue