From 09021ee10397cbb79f54f76a34362da0a94c5c5f Mon Sep 17 00:00:00 2001 From: Tobie Morgan Hitchcock Date: Fri, 2 Dec 2022 21:48:10 +0000 Subject: [PATCH] Add Null Coalescing Operator and Ternary Conditional Operator Closes #1471 Closes #1439 --- lib/src/fnc/operate.rs | 74 ++++++++++++++++++++++++++++++++++++++ lib/src/sql/expression.rs | 12 +++++++ lib/src/sql/operator.rs | 34 +++++++++++------- lib/src/sql/value/value.rs | 2 +- 4 files changed, 108 insertions(+), 14 deletions(-) diff --git a/lib/src/fnc/operate.rs b/lib/src/fnc/operate.rs index d86e2740..6ac4365d 100644 --- a/lib/src/fnc/operate.rs +++ b/lib/src/fnc/operate.rs @@ -19,6 +19,20 @@ pub fn and(a: Value, b: Value) -> Result { }) } +pub fn tco(a: Value, b: Value) -> Result { + Ok(match a.is_truthy() { + true => a, + false => b, + }) +} + +pub fn nco(a: Value, b: Value) -> Result { + Ok(match a.is_some() { + true => a, + false => b, + }) +} + pub fn add(a: Value, b: Value) -> Result { Ok(a.add(b)) } @@ -200,6 +214,66 @@ mod tests { assert_eq!("0", format!("{}", out)); } + #[test] + fn tco_true() { + let one = Value::from(1); + let two = Value::from(2); + let res = tco(one, two); + assert!(res.is_ok()); + let out = res.unwrap(); + assert_eq!("1", format!("{}", out)); + } + + #[test] + fn tco_false_one() { + let one = Value::from(0); + let two = Value::from(1); + let res = tco(one, two); + assert!(res.is_ok()); + let out = res.unwrap(); + assert_eq!("1", format!("{}", out)); + } + + #[test] + fn tco_false_two() { + let one = Value::from(1); + let two = Value::from(0); + let res = tco(one, two); + assert!(res.is_ok()); + let out = res.unwrap(); + assert_eq!("1", format!("{}", out)); + } + + #[test] + fn nco_true() { + let one = Value::from(1); + let two = Value::from(2); + let res = nco(one, two); + assert!(res.is_ok()); + let out = res.unwrap(); + assert_eq!("1", format!("{}", out)); + } + + #[test] + fn nco_false_one() { + let one = Value::None; + let two = Value::from(1); + let res = nco(one, two); + assert!(res.is_ok()); + let out = res.unwrap(); + assert_eq!("1", format!("{}", out)); + } + + #[test] + fn nco_false_two() { + let one = Value::from(1); + let two = Value::None; + let res = nco(one, two); + assert!(res.is_ok()); + let out = res.unwrap(); + assert_eq!("1", format!("{}", out)); + } + #[test] fn add_basic() { let one = Value::from(5); diff --git a/lib/src/sql/expression.rs b/lib/src/sql/expression.rs index d9c0af37..41a882d1 100644 --- a/lib/src/sql/expression.rs +++ b/lib/src/sql/expression.rs @@ -76,12 +76,24 @@ impl Expression { return Ok(l); } } + Operator::Tco => { + if let true = l.is_truthy() { + return Ok(l); + } + } + Operator::Nco => { + if let true = l.is_some() { + return Ok(l); + } + } _ => {} // Continue } let r = self.r.compute(ctx, opt, txn, doc).await?; match self.o { Operator::Or => fnc::operate::or(l, r), Operator::And => fnc::operate::and(l, r), + Operator::Tco => fnc::operate::tco(l, r), + Operator::Nco => fnc::operate::nco(l, r), Operator::Add => fnc::operate::add(l, r), Operator::Sub => fnc::operate::sub(l, r), Operator::Mul => fnc::operate::mul(l, r), diff --git a/lib/src/sql/operator.rs b/lib/src/sql/operator.rs index b68b784e..3e479b68 100644 --- a/lib/src/sql/operator.rs +++ b/lib/src/sql/operator.rs @@ -11,8 +11,11 @@ use std::fmt; #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)] pub enum Operator { + // Or, // || And, // && + Tco, // ?: Ternary conditional operator + Nco, // ?? Null coalescing operator // Add, // + Sub, // - @@ -21,9 +24,8 @@ pub enum Operator { Inc, // += Dec, // -= // - Exact, // == - // Equal, // = + Exact, // == NotEqual, // != AllEqual, // *= AnyEqual, // ?= @@ -65,11 +67,13 @@ impl Operator { match self { Operator::Or => 1, Operator::And => 2, - Operator::Sub => 4, - Operator::Add => 5, - Operator::Mul => 6, - Operator::Div => 7, - _ => 3, + Operator::Tco => 3, + Operator::Nco => 4, + Operator::Sub => 6, + Operator::Add => 7, + Operator::Mul => 8, + Operator::Div => 9, + _ => 5, } } } @@ -79,14 +83,16 @@ impl fmt::Display for Operator { f.write_str(match self { Self::Or => "OR", Self::And => "AND", + Self::Tco => "?:", + Self::Nco => "??", Self::Add => "+", Self::Sub => "-", Self::Mul => "*", Self::Div => "/", Self::Inc => "+=", Self::Dec => "-=", - Self::Exact => "==", Self::Equal => "=", + Self::Exact => "==", Self::NotEqual => "!=", Self::AllEqual => "*=", Self::AnyEqual => "?=", @@ -129,6 +135,12 @@ pub fn operator(i: &str) -> IResult<&str, Operator> { pub fn symbols(i: &str) -> IResult<&str, Operator> { let (i, _) = mightbespace(i)?; let (i, v) = alt(( + alt(( + map(tag("||"), |_| Operator::Or), + map(tag("&&"), |_| Operator::And), + map(tag("?:"), |_| Operator::Tco), + map(tag("??"), |_| Operator::Nco), + )), alt(( map(tag("=="), |_| Operator::Exact), map(tag("!="), |_| Operator::NotEqual), @@ -178,12 +190,8 @@ pub fn phrases(i: &str) -> IResult<&str, Operator> { let (i, _) = shouldbespace(i)?; let (i, v) = alt(( alt(( - map(tag_no_case("&&"), |_| Operator::And), - map(tag_no_case("AND"), |_| Operator::And), - map(tag_no_case("||"), |_| Operator::Or), map(tag_no_case("OR"), |_| Operator::Or), - )), - alt(( + map(tag_no_case("AND"), |_| Operator::And), map(tag_no_case("IS NOT"), |_| Operator::NotEqual), map(tag_no_case("IS"), |_| Operator::Equal), )), diff --git a/lib/src/sql/value/value.rs b/lib/src/sql/value/value.rs index cd751daa..aae2559f 100644 --- a/lib/src/sql/value/value.rs +++ b/lib/src/sql/value/value.rs @@ -610,7 +610,7 @@ impl Value { } pub fn is_some(&self) -> bool { - !self.is_none() + !self.is_none() && !self.is_null() } pub fn is_true(&self) -> bool {