From 0331f486a6cbe46b4a87ebe01596917983f0c4a1 Mon Sep 17 00:00:00 2001 From: tofpie <75836434+tofpie@users.noreply.github.com> Date: Fri, 1 Jan 2021 16:35:00 +0100 Subject: [PATCH] Implement logical assignment operators (&&= and ||=) (#1018) * Implement logical assignment operators (&&= and ||=) * Add unit tests Co-authored-by: tofpie --- .../syntax/ast/node/operator/bin_op/mod.rs | 14 ++++++++ boa/src/syntax/ast/node/operator/tests.rs | 34 +++++++++++++++++++ boa/src/syntax/ast/op.rs | 26 ++++++++++++++ boa/src/syntax/ast/punctuator.rs | 8 +++++ boa/src/syntax/lexer/operator.rs | 4 +-- boa/src/syntax/lexer/tests.rs | 4 ++- 6 files changed, 87 insertions(+), 3 deletions(-) diff --git a/boa/src/syntax/ast/node/operator/bin_op/mod.rs b/boa/src/syntax/ast/node/operator/bin_op/mod.rs index 118f3ea9c5..ba0edecba2 100644 --- a/boa/src/syntax/ast/node/operator/bin_op/mod.rs +++ b/boa/src/syntax/ast/node/operator/bin_op/mod.rs @@ -71,6 +71,20 @@ impl BinOp { AssignOp::Shl => x.shl(&y.run(context)?, context), AssignOp::Shr => x.shr(&y.run(context)?, context), AssignOp::Ushr => x.ushr(&y.run(context)?, context), + AssignOp::BoolAnd => { + if x.to_boolean() { + Ok(y.run(context)?) + } else { + Ok(x) + } + } + AssignOp::BoolOr => { + if x.to_boolean() { + Ok(x) + } else { + Ok(y.run(context)?) + } + } AssignOp::Coalesce => { if x.is_null_or_undefined() { Ok(y.run(context)?) diff --git a/boa/src/syntax/ast/node/operator/tests.rs b/boa/src/syntax/ast/node/operator/tests.rs index 9196896878..3fbc80086a 100644 --- a/boa/src/syntax/ast/node/operator/tests.rs +++ b/boa/src/syntax/ast/node/operator/tests.rs @@ -79,3 +79,37 @@ fn logical_nullish_assignment() { assert_eq!(&exec(scenario), "20"); } + +#[test] +fn logical_assignment() { + let scenario = r#" + let a = false; + a &&= 10; + a; + "#; + + assert_eq!(&exec(scenario), "false"); + + let scenario = r#" + let a = 20; + a &&= 10; + a; + "#; + + assert_eq!(&exec(scenario), "10"); + + let scenario = r#" + let a = null; + a ||= 10; + a; + "#; + + assert_eq!(&exec(scenario), "10"); + let scenario = r#" + let a = 20; + a ||= 10; + a; + "#; + + assert_eq!(&exec(scenario), "20"); +} diff --git a/boa/src/syntax/ast/op.rs b/boa/src/syntax/ast/op.rs index 7f28da6a3a..d52c7ce8d3 100644 --- a/boa/src/syntax/ast/op.rs +++ b/boa/src/syntax/ast/op.rs @@ -965,6 +965,30 @@ pub enum AssignOp { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Unsigned_right_shift_assignment Ushr, + /// The logical and assignment operator only assigns if the target variable is truthy. + /// + /// Syntax: `x &&= y` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#prod-AssignmentExpression + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_AND_assignment + BoolAnd, + + /// The logical or assignment operator only assigns if the target variable is falsy. + /// + /// Syntax: `x ||= y` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#prod-AssignmentExpression + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_OR_assignment + BoolOr, + /// The logical nullish assignment operator only assigns if the target variable is nullish (null or undefined). /// /// Syntax: `x ??= y` @@ -1000,6 +1024,8 @@ impl Display for AssignOp { Self::Shl => "<<=", Self::Shr => ">>=", Self::Ushr => ">>>=", + Self::BoolAnd => "&&=", + Self::BoolOr => "||=", Self::Coalesce => "??=", } ) diff --git a/boa/src/syntax/ast/punctuator.rs b/boa/src/syntax/ast/punctuator.rs index 3b4c74a6e9..06f225bd9e 100644 --- a/boa/src/syntax/ast/punctuator.rs +++ b/boa/src/syntax/ast/punctuator.rs @@ -35,6 +35,10 @@ pub enum Punctuator { AssignAdd, /// `&=` AssignAnd, + /// `&&=` + AssignBoolAnd, + /// `||=` + AssignBoolOr, /// `??=`, AssignCoalesce, /// `/=` @@ -141,6 +145,8 @@ impl Punctuator { match self { Self::AssignAdd => Some(BinOp::Assign(AssignOp::Add)), Self::AssignAnd => Some(BinOp::Assign(AssignOp::And)), + Self::AssignBoolAnd => Some(BinOp::Assign(AssignOp::BoolAnd)), + Self::AssignBoolOr => Some(BinOp::Assign(AssignOp::BoolOr)), Self::AssignCoalesce => Some(BinOp::Assign(AssignOp::Coalesce)), Self::AssignDiv => Some(BinOp::Assign(AssignOp::Div)), Self::AssignLeftSh => Some(BinOp::Assign(AssignOp::Shl)), @@ -200,6 +206,8 @@ impl Display for Punctuator { Self::Assign => "=", Self::AssignAdd => "+=", Self::AssignAnd => "&=", + Self::AssignBoolAnd => "&&=", + Self::AssignBoolOr => "||=", Self::AssignCoalesce => "??=", Self::AssignDiv => "/=", Self::AssignLeftSh => "<<=", diff --git a/boa/src/syntax/lexer/operator.rs b/boa/src/syntax/lexer/operator.rs index 7de7750029..0be8d94a42 100644 --- a/boa/src/syntax/lexer/operator.rs +++ b/boa/src/syntax/lexer/operator.rs @@ -119,10 +119,10 @@ impl Tokenizer for Operator { Ok(Punctuator::Mod) ), b'|' => op!(cursor, start_pos, Ok(Punctuator::AssignOr), Ok(Punctuator::Or), { - Some(b'|') => Ok(Punctuator::BoolOr) + Some(b'|') => vop!(cursor, Ok(Punctuator::AssignBoolOr), Ok(Punctuator::BoolOr)) }), b'&' => op!(cursor, start_pos, Ok(Punctuator::AssignAnd), Ok(Punctuator::And), { - Some(b'&') => Ok(Punctuator::BoolAnd) + Some(b'&') => vop!(cursor, Ok(Punctuator::AssignBoolAnd), Ok(Punctuator::BoolAnd)) }), b'?' => match cursor.peek()? { Some(b'?') => { diff --git a/boa/src/syntax/lexer/tests.rs b/boa/src/syntax/lexer/tests.rs index ebffe9e5b3..d392621db1 100644 --- a/boa/src/syntax/lexer/tests.rs +++ b/boa/src/syntax/lexer/tests.rs @@ -109,7 +109,7 @@ fn check_punctuators() { // https://tc39.es/ecma262/#sec-punctuators let s = "{ ( ) [ ] . ... ; , < > <= >= == != === !== \ + - * % -- << >> >>> & | ^ ! ~ && || ? : \ - = += -= *= &= **= ++ ** <<= >>= >>>= &= |= ^= => ?? ??="; + = += -= *= &= **= ++ ** <<= >>= >>>= &= |= ^= => ?? ??= &&= ||="; let mut lexer = Lexer::new(s.as_bytes()); let expected = [ @@ -164,6 +164,8 @@ fn check_punctuators() { TokenKind::Punctuator(Punctuator::Arrow), TokenKind::Punctuator(Punctuator::Coalesce), TokenKind::Punctuator(Punctuator::AssignCoalesce), + TokenKind::Punctuator(Punctuator::AssignBoolAnd), + TokenKind::Punctuator(Punctuator::AssignBoolOr), ]; expect_tokens(&mut lexer, &expected);