ソースを参照

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

TC39 proposal, stage 4 as of 2020-07.
https://tc39.es/proposal-logical-assignment/
Linus Groh 4 年 前
コミット
aa71dae03c

+ 33 - 0
Libraries/LibJS/AST.cpp

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

+ 3 - 0
Libraries/LibJS/AST.h

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

+ 3 - 0
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::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);
     }
     }

+ 18 - 3
Libraries/LibJS/Parser.cpp

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

+ 46 - 1
Libraries/LibJS/Tests/operators/assignment-operators.js

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

+ 5 - 2
Libraries/LibJS/Token.h

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