mod coerce;
mod eval;
mod format;
mod funcs;
mod lexer;
pub(crate) mod like;
mod parser;
use crate::schema::FieldValue;
#[derive(Debug, Clone, PartialEq)]
pub enum Expression {
Literal(FieldValue),
Field {
table: Option<String>,
name: String,
},
Unary(UnaryOp, Box<Expression>),
Binary(BinaryOp, Box<Expression>, Box<Expression>),
Compare(CompareOp, Box<Expression>, Box<Expression>),
Between(Box<Expression>, Box<Expression>, Box<Expression>),
InList(Box<Expression>, Vec<Expression>),
IsNull {
operand: Box<Expression>,
negated: bool,
},
Call {
name: String,
args: Vec<Expression>,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UnaryOp {
Negate,
Not,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BinaryOp {
Add,
Subtract,
Multiply,
Divide,
IntegerDivide,
Modulo,
Power,
Concat,
And,
Or,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CompareOp {
Equal,
NotEqual,
Less,
LessEqual,
Greater,
GreaterEqual,
Like,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ExprError {
Lex {
position: usize,
detail: String,
},
Parse {
position: usize,
detail: String,
},
Runtime {
detail: String,
},
}
impl std::fmt::Display for ExprError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Lex { position, detail } => {
write!(f, "lexical error at byte {position}: {detail}")
}
Self::Parse { position, detail } => {
write!(f, "parse error at byte {position}: {detail}")
}
Self::Runtime { detail } => write!(f, "{detail}"),
}
}
}
impl std::error::Error for ExprError {}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Scope {
entries: Vec<(Option<String>, String, FieldValue)>,
}
impl Scope {
pub fn new() -> Self {
Self::default()
}
pub fn set(&mut self, name: impl Into<String>, value: FieldValue) -> &mut Self {
self.entries.push((None, name.into(), value));
self
}
pub fn set_field(
&mut self,
table: impl Into<String>,
name: impl Into<String>,
value: FieldValue,
) -> &mut Self {
self.entries.push((Some(table.into()), name.into(), value));
self
}
pub(crate) fn resolve(&self, table: Option<&str>, name: &str) -> Option<&FieldValue> {
let want = name.to_lowercase();
self.entries
.iter()
.filter(|(entry_table, entry_name, _)| {
entry_name.to_lowercase() == want
&& match (table, entry_table.as_deref()) {
(Some(qualified), Some(entry)) => {
qualified.to_lowercase() == entry.to_lowercase()
}
(Some(_), None) => false,
(None, _) => true,
}
})
.map(|(_, _, value)| value)
.next()
}
}
pub fn parse(source: &str) -> Result<Expression, ExprError> {
parser::parse(source)
}
pub fn field_ord(a: &FieldValue, b: &FieldValue) -> Result<std::cmp::Ordering, ExprError> {
coerce::ord_cmp(a, b)
}
pub fn numeric_view(value: &FieldValue) -> Result<f64, ExprError> {
Ok(f64::from(coerce::to_number(value)?))
}
impl Expression {
pub fn evaluate(&self, scope: &Scope) -> Result<FieldValue, ExprError> {
eval::evaluate(self, scope)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn scope_resolves_qualified_before_bare_entries() {
let mut scope = Scope::new();
scope.set("Qty", FieldValue::Integer(1));
scope.set_field("Order Details", "Qty", FieldValue::Integer(2));
assert_eq!(
scope.resolve(None, "qty"),
Some(&FieldValue::Integer(1)),
"bare lookup follows insertion order"
);
assert_eq!(
scope.resolve(Some("order details"), "QTY"),
Some(&FieldValue::Integer(2))
);
assert_eq!(scope.resolve(Some("Other"), "Qty"), None);
assert_eq!(scope.resolve(None, "Ghost"), None);
}
#[test]
fn errors_render_their_positions() {
let lex = ExprError::Lex {
position: 3,
detail: "boom".into(),
};
let parse = ExprError::Parse {
position: 7,
detail: "bang".into(),
};
assert_eq!(lex.to_string(), "lexical error at byte 3: boom");
assert_eq!(parse.to_string(), "parse error at byte 7: bang");
}
}