summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinus Groh <mail@linusgroh.de>2020-10-05 16:49:43 +0100
committerAndreas Kling <kling@serenityos.org>2020-10-05 17:57:26 +0200
commitaa71dae03c0995f55304df59fe6a3b0e46bc7fba (patch)
treeda83c001189920ffbd97690a6c4d652675f6bd58
parentd8d00d3ac7ed73fc25f53a0a5a093b25f243242c (diff)
downloadserenity-aa71dae03c0995f55304df59fe6a3b0e46bc7fba.zip
LibJS: Implement logical assignment operators (&&=, ||=, ??=)
TC39 proposal, stage 4 as of 2020-07. https://tc39.es/proposal-logical-assignment/
-rw-r--r--Libraries/LibJS/AST.cpp33
-rw-r--r--Libraries/LibJS/AST.h3
-rw-r--r--Libraries/LibJS/Lexer.cpp3
-rw-r--r--Libraries/LibJS/Parser.cpp21
-rw-r--r--Libraries/LibJS/Tests/operators/assignment-operators.js47
-rw-r--r--Libraries/LibJS/Token.h7
6 files changed, 108 insertions, 6 deletions
diff --git a/Libraries/LibJS/AST.cpp b/Libraries/LibJS/AST.cpp
index 44253bbf17..784d254332 100644
--- a/Libraries/LibJS/AST.cpp
+++ b/Libraries/LibJS/AST.cpp
@@ -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);
diff --git a/Libraries/LibJS/AST.h b/Libraries/LibJS/AST.h
index cdefaf6f78..355377880f 100644
--- a/Libraries/LibJS/AST.h
+++ b/Libraries/LibJS/AST.h
@@ -816,6 +816,9 @@ enum class AssignmentOp {
LeftShiftAssignment,
RightShiftAssignment,
UnsignedRightShiftAssignment,
+ AndAssignment,
+ OrAssignment,
+ NullishAssignment,
};
class AssignmentExpression final : public Expression {
diff --git a/Libraries/LibJS/Lexer.cpp b/Libraries/LibJS/Lexer.cpp
index d032a71093..fb9760aab9 100644
--- a/Libraries/LibJS/Lexer.cpp
+++ b/Libraries/LibJS/Lexer.cpp
@@ -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);
}
diff --git a/Libraries/LibJS/Parser.cpp b/Libraries/LibJS/Parser.cpp
index deec9c0536..6e1980b9d4 100644
--- a/Libraries/LibJS/Parser.cpp
+++ b/Libraries/LibJS/Parser.cpp
@@ -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
diff --git a/Libraries/LibJS/Tests/operators/assignment-operators.js b/Libraries/LibJS/Tests/operators/assignment-operators.js
index 1078e4202a..3ab2d01a5b 100644
--- a/Libraries/LibJS/Tests/operators/assignment-operators.js
+++ b/Libraries/LibJS/Tests/operators/assignment-operators.js
@@ -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() {
diff --git a/Libraries/LibJS/Token.h b/Libraries/LibJS/Token.h
index 7fcd818071..fcbbfb4a06 100644
--- a/Libraries/LibJS/Token.h
+++ b/Libraries/LibJS/Token.h
@@ -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) \