diff options
-rw-r--r-- | Libraries/LibJS/AST.cpp | 33 | ||||
-rw-r--r-- | Libraries/LibJS/AST.h | 3 | ||||
-rw-r--r-- | Libraries/LibJS/Lexer.cpp | 3 | ||||
-rw-r--r-- | Libraries/LibJS/Parser.cpp | 21 | ||||
-rw-r--r-- | Libraries/LibJS/Tests/operators/assignment-operators.js | 47 | ||||
-rw-r--r-- | Libraries/LibJS/Token.h | 7 |
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) \ |