Browse Source

Implement coalescing (?? and ??=) (#1013)

* Implement coalescing (?? and ??=)

* Add unit tests

Co-authored-by: tofpie <tofpie@users.noreply.github.com>
pull/1015/head
tofpie 4 years ago committed by GitHub
parent
commit
23bc476a94
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 47
      boa/src/syntax/ast/node/operator/bin_op/mod.rs
  2. 19
      boa/src/syntax/ast/node/operator/tests.rs
  3. 27
      boa/src/syntax/ast/op.rs
  4. 8
      boa/src/syntax/ast/punctuator.rs
  5. 6
      boa/src/syntax/lexer/mod.rs
  6. 16
      boa/src/syntax/lexer/operator.rs
  7. 4
      boa/src/syntax/lexer/tests.rs
  8. 5
      boa/src/syntax/parser/expression/assignment/conditional.rs
  9. 151
      boa/src/syntax/parser/expression/mod.rs
  10. 49
      boa/src/syntax/parser/expression/tests.rs

47
boa/src/syntax/ast/node/operator/bin_op/mod.rs

@ -57,20 +57,27 @@ impl BinOp {
} }
/// Runs the assignment operators. /// Runs the assignment operators.
fn run_assign(op: AssignOp, x: Value, y: Value, context: &mut Context) -> Result<Value> { fn run_assign(op: AssignOp, x: Value, y: &Node, context: &mut Context) -> Result<Value> {
match op { match op {
AssignOp::Add => x.add(&y, context), AssignOp::Add => x.add(&y.run(context)?, context),
AssignOp::Sub => x.sub(&y, context), AssignOp::Sub => x.sub(&y.run(context)?, context),
AssignOp::Mul => x.mul(&y, context), AssignOp::Mul => x.mul(&y.run(context)?, context),
AssignOp::Exp => x.pow(&y, context), AssignOp::Exp => x.pow(&y.run(context)?, context),
AssignOp::Div => x.div(&y, context), AssignOp::Div => x.div(&y.run(context)?, context),
AssignOp::Mod => x.rem(&y, context), AssignOp::Mod => x.rem(&y.run(context)?, context),
AssignOp::And => x.bitand(&y, context), AssignOp::And => x.bitand(&y.run(context)?, context),
AssignOp::Or => x.bitor(&y, context), AssignOp::Or => x.bitor(&y.run(context)?, context),
AssignOp::Xor => x.bitxor(&y, context), AssignOp::Xor => x.bitxor(&y.run(context)?, context),
AssignOp::Shl => x.shl(&y, context), AssignOp::Shl => x.shl(&y.run(context)?, context),
AssignOp::Shr => x.shr(&y, context), AssignOp::Shr => x.shr(&y.run(context)?, context),
AssignOp::Ushr => x.ushr(&y, context), AssignOp::Ushr => x.ushr(&y.run(context)?, context),
AssignOp::Coalesce => {
if x.is_null_or_undefined() {
Ok(y.run(context)?)
} else {
Ok(x)
}
}
} }
} }
} }
@ -167,6 +174,14 @@ impl Executable for BinOp {
self.rhs().run(context)? self.rhs().run(context)?
} }
} }
LogOp::Coalesce => {
let left = self.lhs.run(context)?;
if left.is_null_or_undefined() {
self.rhs().run(context)?
} else {
left
}
}
}), }),
op::BinOp::Assign(op) => match self.lhs() { op::BinOp::Assign(op) => match self.lhs() {
Node::Identifier(ref name) => { Node::Identifier(ref name) => {
@ -176,8 +191,7 @@ impl Executable for BinOp {
.get_binding_value(name.as_ref()) .get_binding_value(name.as_ref())
.map_err(|e| e.to_error(context))?; .map_err(|e| e.to_error(context))?;
let v_b = self.rhs().run(context)?; let value = Self::run_assign(op, v_a, self.rhs(), context)?;
let value = Self::run_assign(op, v_a, v_b, context)?;
context context
.realm_mut() .realm_mut()
.environment .environment
@ -188,8 +202,7 @@ impl Executable for BinOp {
Node::GetConstField(ref get_const_field) => { Node::GetConstField(ref get_const_field) => {
let v_r_a = get_const_field.obj().run(context)?; let v_r_a = get_const_field.obj().run(context)?;
let v_a = v_r_a.get_field(get_const_field.field()); let v_a = v_r_a.get_field(get_const_field.field());
let v_b = self.rhs().run(context)?; let value = Self::run_assign(op, v_a, self.rhs(), context)?;
let value = Self::run_assign(op, v_a, v_b, context)?;
v_r_a.set_field(get_const_field.field(), value.clone()); v_r_a.set_field(get_const_field.field(), value.clone());
Ok(value) Ok(value)
} }

19
boa/src/syntax/ast/node/operator/tests.rs

@ -60,3 +60,22 @@ fn instanceofoperator_rhs_not_callable() {
"\"TypeError: right-hand side of 'instanceof' is not callable\"" "\"TypeError: right-hand side of 'instanceof' is not callable\""
); );
} }
#[test]
fn logical_nullish_assignment() {
let scenario = r#"
let a = undefined;
a ??= 10;
a;
"#;
assert_eq!(&exec(scenario), "10");
let scenario = r#"
let a = 20;
a ??= 10;
a;
"#;
assert_eq!(&exec(scenario), "20");
}

27
boa/src/syntax/ast/op.rs

@ -683,6 +683,19 @@ pub enum LogOp {
/// [spec]: https://tc39.es/ecma262/#prod-LogicalORExpression /// [spec]: https://tc39.es/ecma262/#prod-LogicalORExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_Operators#Logical_OR /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_Operators#Logical_OR
Or, Or,
/// The nullish coalescing operator is a logical operator that returns the second operand
/// when its first operand is null or undefined, and otherwise returns its first operand.
///
/// Syntax: `x ?? y`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#prod-CoalesceExpression
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_operator
Coalesce,
} }
impl Display for LogOp { impl Display for LogOp {
@ -693,6 +706,7 @@ impl Display for LogOp {
match *self { match *self {
Self::And => "&&", Self::And => "&&",
Self::Or => "||", Self::Or => "||",
Self::Coalesce => "??",
} }
) )
} }
@ -950,6 +964,18 @@ pub enum AssignOp {
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentOperator /// [spec]: https://tc39.es/ecma262/#prod-AssignmentOperator
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Unsigned_right_shift_assignment /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Unsigned_right_shift_assignment
Ushr, Ushr,
/// The logical nullish assignment operator only assigns if the target variable is nullish (null or undefined).
///
/// 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_nullish_assignment
Coalesce,
} }
unsafe impl Trace for AssignOp { unsafe impl Trace for AssignOp {
@ -974,6 +1000,7 @@ impl Display for AssignOp {
Self::Shl => "<<=", Self::Shl => "<<=",
Self::Shr => ">>=", Self::Shr => ">>=",
Self::Ushr => ">>>=", Self::Ushr => ">>>=",
Self::Coalesce => "??=",
} }
) )
} }

8
boa/src/syntax/ast/punctuator.rs

@ -35,6 +35,8 @@ pub enum Punctuator {
AssignAdd, AssignAdd,
/// `&=` /// `&=`
AssignAnd, AssignAnd,
/// `??=`,
AssignCoalesce,
/// `/=` /// `/=`
AssignDiv, AssignDiv,
/// `<<=` /// `<<=`
@ -65,6 +67,8 @@ pub enum Punctuator {
CloseBracket, CloseBracket,
/// `)` /// `)`
CloseParen, CloseParen,
/// `??`
Coalesce,
/// `:` /// `:`
Colon, Colon,
/// `,` /// `,`
@ -137,6 +141,7 @@ impl Punctuator {
match self { match self {
Self::AssignAdd => Some(BinOp::Assign(AssignOp::Add)), Self::AssignAdd => Some(BinOp::Assign(AssignOp::Add)),
Self::AssignAnd => Some(BinOp::Assign(AssignOp::And)), Self::AssignAnd => Some(BinOp::Assign(AssignOp::And)),
Self::AssignCoalesce => Some(BinOp::Assign(AssignOp::Coalesce)),
Self::AssignDiv => Some(BinOp::Assign(AssignOp::Div)), Self::AssignDiv => Some(BinOp::Assign(AssignOp::Div)),
Self::AssignLeftSh => Some(BinOp::Assign(AssignOp::Shl)), Self::AssignLeftSh => Some(BinOp::Assign(AssignOp::Shl)),
Self::AssignMod => Some(BinOp::Assign(AssignOp::Mod)), Self::AssignMod => Some(BinOp::Assign(AssignOp::Mod)),
@ -157,6 +162,7 @@ impl Punctuator {
Self::Xor => Some(BinOp::Bit(BitOp::Xor)), Self::Xor => Some(BinOp::Bit(BitOp::Xor)),
Self::BoolAnd => Some(BinOp::Log(LogOp::And)), Self::BoolAnd => Some(BinOp::Log(LogOp::And)),
Self::BoolOr => Some(BinOp::Log(LogOp::Or)), Self::BoolOr => Some(BinOp::Log(LogOp::Or)),
Self::Coalesce => Some(BinOp::Log(LogOp::Coalesce)),
Self::Eq => Some(BinOp::Comp(CompOp::Equal)), Self::Eq => Some(BinOp::Comp(CompOp::Equal)),
Self::NotEq => Some(BinOp::Comp(CompOp::NotEqual)), Self::NotEq => Some(BinOp::Comp(CompOp::NotEqual)),
Self::StrictEq => Some(BinOp::Comp(CompOp::StrictEqual)), Self::StrictEq => Some(BinOp::Comp(CompOp::StrictEqual)),
@ -194,6 +200,7 @@ impl Display for Punctuator {
Self::Assign => "=", Self::Assign => "=",
Self::AssignAdd => "+=", Self::AssignAdd => "+=",
Self::AssignAnd => "&=", Self::AssignAnd => "&=",
Self::AssignCoalesce => "??=",
Self::AssignDiv => "/=", Self::AssignDiv => "/=",
Self::AssignLeftSh => "<<=", Self::AssignLeftSh => "<<=",
Self::AssignMod => "%=", Self::AssignMod => "%=",
@ -206,6 +213,7 @@ impl Display for Punctuator {
Self::AssignXor => "^=", Self::AssignXor => "^=",
Self::BoolAnd => "&&", Self::BoolAnd => "&&",
Self::BoolOr => "||", Self::BoolOr => "||",
Self::Coalesce => "??",
Self::CloseBlock => "}", Self::CloseBlock => "}",
Self::CloseBracket => "]", Self::CloseBracket => "]",
Self::CloseParen => ")", Self::CloseParen => ")",

6
boa/src/syntax/lexer/mod.rs

@ -248,12 +248,8 @@ impl<R> Lexer<R> {
Punctuator::CloseBracket.into(), Punctuator::CloseBracket.into(),
Span::new(start, self.cursor.pos()), Span::new(start, self.cursor.pos()),
)), )),
'?' => Ok(Token::new(
Punctuator::Question.into(),
Span::new(start, self.cursor.pos()),
)),
'/' => self.lex_slash_token(start), '/' => self.lex_slash_token(start),
'=' | '*' | '+' | '-' | '%' | '|' | '&' | '^' | '<' | '>' | '!' | '~' => { '=' | '*' | '+' | '-' | '%' | '|' | '&' | '^' | '<' | '>' | '!' | '~' | '?' => {
Operator::new(next_ch as u8).lex(&mut self.cursor, start) Operator::new(next_ch as u8).lex(&mut self.cursor, start)
} }
_ => { _ => {

16
boa/src/syntax/lexer/operator.rs

@ -1,6 +1,7 @@
//! This module implements lexing for operators (+, - etc.) used in the JavaScript programing language. //! This module implements lexing for operators (+, - etc.) used in the JavaScript programing language.
use super::{Cursor, Error, Tokenizer}; use super::{Cursor, Error, Tokenizer};
use crate::syntax::lexer::TokenKind;
use crate::{ use crate::{
profiler::BoaProfiler, profiler::BoaProfiler,
syntax::{ syntax::{
@ -123,6 +124,21 @@ impl<R> Tokenizer<R> for Operator {
b'&' => op!(cursor, start_pos, Ok(Punctuator::AssignAnd), Ok(Punctuator::And), { b'&' => op!(cursor, start_pos, Ok(Punctuator::AssignAnd), Ok(Punctuator::And), {
Some(b'&') => Ok(Punctuator::BoolAnd) Some(b'&') => Ok(Punctuator::BoolAnd)
}), }),
b'?' => match cursor.peek()? {
Some(b'?') => {
let _ = cursor.next_byte()?.expect("? vanished");
op!(
cursor,
start_pos,
Ok(Punctuator::AssignCoalesce),
Ok(Punctuator::Coalesce)
)
}
_ => Ok(Token::new(
TokenKind::Punctuator(Punctuator::Question),
Span::new(start_pos, cursor.pos()),
)),
},
b'^' => op!( b'^' => op!(
cursor, cursor,
start_pos, start_pos,

4
boa/src/syntax/lexer/tests.rs

@ -109,7 +109,7 @@ fn check_punctuators() {
// https://tc39.es/ecma262/#sec-punctuators // https://tc39.es/ecma262/#sec-punctuators
let s = "{ ( ) [ ] . ... ; , < > <= >= == != === !== \ let s = "{ ( ) [ ] . ... ; , < > <= >= == != === !== \
+ - * % -- << >> >>> & | ^ ! ~ && || ? : \ + - * % -- << >> >>> & | ^ ! ~ && || ? : \
= += -= *= &= **= ++ ** <<= >>= >>>= &= |= ^= =>"; = += -= *= &= **= ++ ** <<= >>= >>>= &= |= ^= => ?? ??=";
let mut lexer = Lexer::new(s.as_bytes()); let mut lexer = Lexer::new(s.as_bytes());
let expected = [ let expected = [
@ -162,6 +162,8 @@ fn check_punctuators() {
TokenKind::Punctuator(Punctuator::AssignOr), TokenKind::Punctuator(Punctuator::AssignOr),
TokenKind::Punctuator(Punctuator::AssignXor), TokenKind::Punctuator(Punctuator::AssignXor),
TokenKind::Punctuator(Punctuator::Arrow), TokenKind::Punctuator(Punctuator::Arrow),
TokenKind::Punctuator(Punctuator::Coalesce),
TokenKind::Punctuator(Punctuator::AssignCoalesce),
]; ];
expect_tokens(&mut lexer, &expected); expect_tokens(&mut lexer, &expected);

5
boa/src/syntax/parser/expression/assignment/conditional.rs

@ -12,7 +12,7 @@ use crate::{
syntax::{ syntax::{
ast::{node::ConditionalOp, Node, Punctuator}, ast::{node::ConditionalOp, Node, Punctuator},
parser::{ parser::{
expression::{AssignmentExpression, LogicalORExpression}, expression::{AssignmentExpression, ShortCircuitExpression},
AllowAwait, AllowIn, AllowYield, Cursor, ParseResult, TokenParser, AllowAwait, AllowIn, AllowYield, Cursor, ParseResult, TokenParser,
}, },
}, },
@ -65,8 +65,7 @@ where
fn parse(self, cursor: &mut Cursor<R>) -> ParseResult { fn parse(self, cursor: &mut Cursor<R>) -> ParseResult {
let _timer = BoaProfiler::global().start_event("ConditionalExpression", "Parsing"); let _timer = BoaProfiler::global().start_event("ConditionalExpression", "Parsing");
// TODO: coalesce expression let lhs = ShortCircuitExpression::new(self.allow_in, self.allow_yield, self.allow_await)
let lhs = LogicalORExpression::new(self.allow_in, self.allow_yield, self.allow_await)
.parse(cursor)?; .parse(cursor)?;
if let Some(tok) = cursor.peek(0)? { if let Some(tok) = cursor.peek(0)? {

151
boa/src/syntax/parser/expression/mod.rs

@ -20,15 +20,19 @@ pub(in crate::syntax::parser) mod await_expr;
use self::assignment::ExponentiationExpression; use self::assignment::ExponentiationExpression;
pub(super) use self::{assignment::AssignmentExpression, primary::Initializer}; pub(super) use self::{assignment::AssignmentExpression, primary::Initializer};
use super::{AllowAwait, AllowIn, AllowYield, Cursor, ParseResult, TokenParser}; use super::{AllowAwait, AllowIn, AllowYield, Cursor, ParseResult, TokenParser};
use crate::syntax::lexer::{InputElement, TokenKind};
use crate::{ use crate::{
profiler::BoaProfiler, profiler::BoaProfiler,
syntax::ast::{ syntax::{
node::{BinOp, Node}, ast::op::LogOp,
Keyword, Punctuator, ast::{
node::{BinOp, Node},
Keyword, Punctuator,
},
lexer::{InputElement, TokenKind},
parser::ParseError,
}, },
}; };
use std::io::Read; use std::io::Read;
// For use in the expression! macro to allow for both Punctuator and Keyword parameters. // For use in the expression! macro to allow for both Punctuator and Keyword parameters.
@ -142,23 +146,31 @@ expression!(
None::<InputElement> None::<InputElement>
); );
/// Parses a logical `OR` expression. /// Parses a logical expression expression.
/// ///
/// More information: /// More information:
/// - [MDN documentation][mdn] /// - [MDN documentation][mdn]
/// - [ECMAScript specification][spec] /// - [ECMAScript specification][spec]
/// ///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_Operators#Logical_OR_2 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_Operators
/// [spec]: https://tc39.es/ecma262/#prod-LogicalORExpression /// [spec]: https://tc39.es/ecma262/#prod-ShortCircuitExpression
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
struct LogicalORExpression { struct ShortCircuitExpression {
allow_in: AllowIn, allow_in: AllowIn,
allow_yield: AllowYield, allow_yield: AllowYield,
allow_await: AllowAwait, allow_await: AllowAwait,
previous: PreviousExpr,
} }
impl LogicalORExpression { #[derive(Debug, Clone, Copy, PartialEq)]
/// Creates a new `LogicalORExpression` parser. enum PreviousExpr {
None,
Logical,
Coalesce,
}
impl ShortCircuitExpression {
/// Creates a new `ShortCircuitExpression` parser.
pub(super) fn new<I, Y, A>(allow_in: I, allow_yield: Y, allow_await: A) -> Self pub(super) fn new<I, Y, A>(allow_in: I, allow_yield: Y, allow_await: A) -> Self
where where
I: Into<AllowIn>, I: Into<AllowIn>,
@ -169,36 +181,16 @@ impl LogicalORExpression {
allow_in: allow_in.into(), allow_in: allow_in.into(),
allow_yield: allow_yield.into(), allow_yield: allow_yield.into(),
allow_await: allow_await.into(), allow_await: allow_await.into(),
previous: PreviousExpr::None,
} }
} }
}
expression!(
LogicalORExpression,
LogicalANDExpression,
[Punctuator::BoolOr],
[allow_in, allow_yield, allow_await],
None::<InputElement>
);
/// Parses a logical `AND` expression.
///
/// More information:
/// - [MDN documentation][mdn]
/// - [ECMAScript specification][spec]
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_Operators#Logical_AND_2
/// [spec]: https://tc39.es/ecma262/#prod-LogicalANDExpression
#[derive(Debug, Clone, Copy)]
struct LogicalANDExpression {
allow_in: AllowIn,
allow_yield: AllowYield,
allow_await: AllowAwait,
}
impl LogicalANDExpression { fn with_previous<I, Y, A>(
/// Creates a new `LogicalANDExpression` parser. allow_in: I,
pub(super) fn new<I, Y, A>(allow_in: I, allow_yield: Y, allow_await: A) -> Self allow_yield: Y,
allow_await: A,
previous: PreviousExpr,
) -> Self
where where
I: Into<AllowIn>, I: Into<AllowIn>,
Y: Into<AllowYield>, Y: Into<AllowYield>,
@ -208,17 +200,86 @@ impl LogicalANDExpression {
allow_in: allow_in.into(), allow_in: allow_in.into(),
allow_yield: allow_yield.into(), allow_yield: allow_yield.into(),
allow_await: allow_await.into(), allow_await: allow_await.into(),
previous,
} }
} }
} }
expression!( impl<R> TokenParser<R> for ShortCircuitExpression
LogicalANDExpression, where
BitwiseORExpression, R: Read,
[Punctuator::BoolAnd], {
[allow_in, allow_yield, allow_await], type Output = Node;
None::<InputElement>
); fn parse(self, cursor: &mut Cursor<R>) -> ParseResult {
let _timer = BoaProfiler::global().start_event("ShortCircuitExpression", "Parsing");
let mut current_node =
BitwiseORExpression::new(self.allow_in, self.allow_yield, self.allow_await)
.parse(cursor)?;
let mut previous = self.previous;
while let Some(tok) = cursor.peek(0)? {
match tok.kind() {
TokenKind::Punctuator(Punctuator::BoolAnd) => {
if previous == PreviousExpr::Coalesce {
return Err(ParseError::expected(
[TokenKind::Punctuator(Punctuator::Coalesce)],
tok.clone(),
"logical expression (cannot use '??' without parentheses within '||' or '&&')",
));
}
let _ = cursor.next()?.expect("'&&' expected");
previous = PreviousExpr::Logical;
let rhs =
BitwiseORExpression::new(self.allow_in, self.allow_yield, self.allow_await)
.parse(cursor)?;
current_node = BinOp::new(LogOp::And, current_node, rhs).into();
}
TokenKind::Punctuator(Punctuator::BoolOr) => {
if previous == PreviousExpr::Coalesce {
return Err(ParseError::expected(
[TokenKind::Punctuator(Punctuator::Coalesce)],
tok.clone(),
"logical expression (cannot use '??' without parentheses within '||' or '&&')",
));
}
let _ = cursor.next()?.expect("'||' expected");
previous = PreviousExpr::Logical;
let rhs = ShortCircuitExpression::with_previous(
self.allow_in,
self.allow_yield,
self.allow_await,
PreviousExpr::Logical,
)
.parse(cursor)?;
current_node = BinOp::new(LogOp::Or, current_node, rhs).into();
}
TokenKind::Punctuator(Punctuator::Coalesce) => {
if previous == PreviousExpr::Logical {
return Err(ParseError::expected(
[
TokenKind::Punctuator(Punctuator::BoolAnd),
TokenKind::Punctuator(Punctuator::BoolOr),
],
tok.clone(),
"cannot use '??' unparenthesized within '||' or '&&'",
));
}
let _ = cursor.next()?.expect("'??' expected");
previous = PreviousExpr::Coalesce;
let rhs =
BitwiseORExpression::new(self.allow_in, self.allow_yield, self.allow_await)
.parse(cursor)?;
current_node = BinOp::new(LogOp::Coalesce, current_node, rhs).into();
}
_ => break,
}
}
Ok(current_node)
}
}
/// Parses a bitwise `OR` expression. /// Parses a bitwise `OR` expression.
/// ///

49
boa/src/syntax/parser/expression/tests.rs

@ -1,10 +1,10 @@
use crate::syntax::{ use crate::syntax::{
ast::op::{AssignOp, BitOp, CompOp, NumOp}, ast::op::{AssignOp, BitOp, CompOp, LogOp, NumOp},
ast::{ ast::{
node::{BinOp, Identifier}, node::{BinOp, Identifier},
Const, Const,
}, },
parser::tests::check_parser, parser::tests::{check_invalid, check_parser},
}; };
/// Checks numeric operations /// Checks numeric operations
@ -191,6 +191,15 @@ fn check_assign_operations() {
) )
.into()], .into()],
); );
check_parser(
"a ??= b",
vec![BinOp::new(
AssignOp::Coalesce,
Identifier::from("a"),
Identifier::from("b"),
)
.into()],
);
} }
#[test] #[test]
@ -236,3 +245,39 @@ fn check_relational_operations() {
vec![BinOp::new(CompOp::In, Identifier::from("p"), Identifier::from("o")).into()], vec![BinOp::new(CompOp::In, Identifier::from("p"), Identifier::from("o")).into()],
); );
} }
#[test]
fn check_logical_expressions() {
check_parser(
"a && b || c && d || e",
vec![BinOp::new(
LogOp::Or,
BinOp::new(LogOp::And, Identifier::from("a"), Identifier::from("b")),
BinOp::new(
LogOp::Or,
BinOp::new(LogOp::And, Identifier::from("c"), Identifier::from("d")),
Identifier::from("e"),
),
)
.into()],
);
check_parser(
"a ?? b ?? c",
vec![BinOp::new(
LogOp::Coalesce,
BinOp::new(
LogOp::Coalesce,
Identifier::from("a"),
Identifier::from("b"),
),
Identifier::from("c"),
)
.into()],
);
check_invalid("a ?? b && c");
check_invalid("a && b ?? c");
check_invalid("a ?? b || c");
check_invalid("a || b ?? c");
}

Loading…
Cancel
Save