surrealpatch/lib/src/sql/operator.rs
2023-08-17 19:03:46 +01:00

292 lines
7.8 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use crate::idx::ft::MatchRef;
use crate::sql::comment::mightbespace;
use crate::sql::comment::shouldbespace;
use crate::sql::error::IResult;
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::bytes::complete::tag_no_case;
use nom::character::complete::char;
use nom::character::complete::u8 as uint8;
use nom::combinator::{map, opt};
use revision::revisioned;
use serde::{Deserialize, Serialize};
use std::fmt;
use std::fmt::Write;
/// Binary operators.
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)]
#[revisioned(revision = 1)]
pub enum Operator {
//
Neg, // -
Not, // !
//
Or, // ||
And, // &&
Tco, // ?: Ternary conditional operator
Nco, // ?? Null coalescing operator
//
Add, // +
Sub, // -
Mul, // *
Div, // /
Pow, // **
Inc, // +=
Dec, // -=
Ext, // +?=
//
Equal, // =
Exact, // ==
NotEqual, // !=
AllEqual, // *=
AnyEqual, // ?=
//
Like, // ~
NotLike, // !~
AllLike, // *~
AnyLike, // ?~
Matches(Option<MatchRef>), // @{ref}@
//
LessThan, // <
LessThanOrEqual, // <=
MoreThan, // >
MoreThanOrEqual, // >=
//
Contain, // ∋
NotContain, // ∌
ContainAll, // ⊇
ContainAny, // ⊃
ContainNone, // ⊅
Inside, // ∈
NotInside, // ∉
AllInside, // ⊆
AnyInside, // ⊂
NoneInside, // ⊄
//
Outside,
Intersects,
}
impl Default for Operator {
fn default() -> Self {
Self::Equal
}
}
impl Operator {
#[inline]
pub fn precedence(&self) -> u8 {
match self {
Self::Or => 1,
Self::And => 2,
Self::Tco => 3,
Self::Nco => 4,
Self::Sub => 6,
Self::Add => 7,
Self::Mul => 8,
Self::Div => 9,
_ => 5,
}
}
}
impl fmt::Display for Operator {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Neg => f.write_str("-"),
Self::Not => f.write_str("!"),
Self::Or => f.write_str("OR"),
Self::And => f.write_str("AND"),
Self::Tco => f.write_str("?:"),
Self::Nco => f.write_str("??"),
Self::Add => f.write_str("+"),
Self::Sub => f.write_char('-'),
Self::Mul => f.write_char('*'),
Self::Div => f.write_char('/'),
Self::Pow => f.write_str("**"),
Self::Inc => f.write_str("+="),
Self::Dec => f.write_str("-="),
Self::Ext => f.write_str("+?="),
Self::Equal => f.write_char('='),
Self::Exact => f.write_str("=="),
Self::NotEqual => f.write_str("!="),
Self::AllEqual => f.write_str("*="),
Self::AnyEqual => f.write_str("?="),
Self::Like => f.write_char('~'),
Self::NotLike => f.write_str("!~"),
Self::AllLike => f.write_str("*~"),
Self::AnyLike => f.write_str("?~"),
Self::LessThan => f.write_char('<'),
Self::LessThanOrEqual => f.write_str("<="),
Self::MoreThan => f.write_char('>'),
Self::MoreThanOrEqual => f.write_str(">="),
Self::Contain => f.write_str("CONTAINS"),
Self::NotContain => f.write_str("CONTAINSNOT"),
Self::ContainAll => f.write_str("CONTAINSALL"),
Self::ContainAny => f.write_str("CONTAINSANY"),
Self::ContainNone => f.write_str("CONTAINSNONE"),
Self::Inside => f.write_str("INSIDE"),
Self::NotInside => f.write_str("NOTINSIDE"),
Self::AllInside => f.write_str("ALLINSIDE"),
Self::AnyInside => f.write_str("ANYINSIDE"),
Self::NoneInside => f.write_str("NONEINSIDE"),
Self::Outside => f.write_str("OUTSIDE"),
Self::Intersects => f.write_str("INTERSECTS"),
Self::Matches(reference) => {
if let Some(r) = reference {
write!(f, "@{}@", r)
} else {
f.write_str("@@")
}
}
}
}
}
pub fn assigner(i: &str) -> IResult<&str, Operator> {
alt((
map(char('='), |_| Operator::Equal),
map(tag("+="), |_| Operator::Inc),
map(tag("-="), |_| Operator::Dec),
map(tag("+?="), |_| Operator::Ext),
))(i)
}
pub fn unary(i: &str) -> IResult<&str, Operator> {
unary_symbols(i)
}
pub fn unary_symbols(i: &str) -> IResult<&str, Operator> {
let (i, _) = mightbespace(i)?;
let (i, v) =
alt((alt((map(tag("-"), |_| Operator::Neg), map(tag("!"), |_| Operator::Not))),))(i)?;
let (i, _) = mightbespace(i)?;
Ok((i, v))
}
pub fn binary(i: &str) -> IResult<&str, Operator> {
alt((binary_symbols, binary_phrases))(i)
}
pub fn binary_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),
map(tag("*="), |_| Operator::AllEqual),
map(tag("?="), |_| Operator::AnyEqual),
map(char('='), |_| Operator::Equal),
)),
alt((
map(tag("!~"), |_| Operator::NotLike),
map(tag("*~"), |_| Operator::AllLike),
map(tag("?~"), |_| Operator::AnyLike),
map(char('~'), |_| Operator::Like),
matches,
)),
alt((
map(tag("<="), |_| Operator::LessThanOrEqual),
map(char('<'), |_| Operator::LessThan),
map(tag(">="), |_| Operator::MoreThanOrEqual),
map(char('>'), |_| Operator::MoreThan),
)),
alt((
map(tag("**"), |_| Operator::Pow),
map(char('+'), |_| Operator::Add),
map(char('-'), |_| Operator::Sub),
map(char('*'), |_| Operator::Mul),
map(char('×'), |_| Operator::Mul),
map(char('∙'), |_| Operator::Mul),
map(char('/'), |_| Operator::Div),
map(char('÷'), |_| Operator::Div),
)),
alt((
map(char('∋'), |_| Operator::Contain),
map(char('∌'), |_| Operator::NotContain),
map(char('∈'), |_| Operator::Inside),
map(char('∉'), |_| Operator::NotInside),
map(char('⊇'), |_| Operator::ContainAll),
map(char('⊃'), |_| Operator::ContainAny),
map(char('⊅'), |_| Operator::ContainNone),
map(char('⊆'), |_| Operator::AllInside),
map(char('⊂'), |_| Operator::AnyInside),
map(char('⊄'), |_| Operator::NoneInside),
)),
))(i)?;
let (i, _) = mightbespace(i)?;
Ok((i, v))
}
pub fn binary_phrases(i: &str) -> IResult<&str, Operator> {
let (i, _) = shouldbespace(i)?;
let (i, v) = alt((
alt((
map(tag_no_case("OR"), |_| Operator::Or),
map(tag_no_case("AND"), |_| Operator::And),
map(tag_no_case("IS NOT"), |_| Operator::NotEqual),
map(tag_no_case("IS"), |_| Operator::Equal),
)),
alt((
map(tag_no_case("CONTAINSALL"), |_| Operator::ContainAll),
map(tag_no_case("CONTAINSANY"), |_| Operator::ContainAny),
map(tag_no_case("CONTAINSNONE"), |_| Operator::ContainNone),
map(tag_no_case("CONTAINSNOT"), |_| Operator::NotContain),
map(tag_no_case("CONTAINS"), |_| Operator::Contain),
map(tag_no_case("ALLINSIDE"), |_| Operator::AllInside),
map(tag_no_case("ANYINSIDE"), |_| Operator::AnyInside),
map(tag_no_case("NONEINSIDE"), |_| Operator::NoneInside),
map(tag_no_case("NOTINSIDE"), |_| Operator::NotInside),
map(tag_no_case("INSIDE"), |_| Operator::Inside),
map(tag_no_case("OUTSIDE"), |_| Operator::Outside),
map(tag_no_case("INTERSECTS"), |_| Operator::Intersects),
map(tag_no_case("NOT IN"), |_| Operator::NotInside),
map(tag_no_case("IN"), |_| Operator::Inside),
)),
))(i)?;
let (i, _) = shouldbespace(i)?;
Ok((i, v))
}
pub fn matches(i: &str) -> IResult<&str, Operator> {
let (i, _) = char('@')(i)?;
// let (i, reference) = opt(|i| uint8(i))(i)?;
let (i, reference) = opt(uint8)(i)?;
let (i, _) = char('@')(i)?;
Ok((i, Operator::Matches(reference)))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn matches_without_reference() {
let res = matches("@@");
assert!(res.is_ok());
let out = res.unwrap().1;
assert_eq!("@@", format!("{}", out));
assert_eq!(out, Operator::Matches(None));
}
#[test]
fn matches_with_reference() {
let res = matches("@12@");
assert!(res.is_ok());
let out = res.unwrap().1;
assert_eq!("@12@", format!("{}", out));
assert_eq!(out, Operator::Matches(Some(12u8)));
}
#[test]
fn matches_with_invalid_reference() {
let res = matches("@256@");
assert!(res.is_err());
}
}