mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 12:12:45 +00:00 
			
		
		
		
	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
					
				
					 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
		Add a link
		
	
		Reference in a new issue
	
	 Linus Groh
						Linus Groh