tytanic_filter/eval/mod.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
//! Test set evaluation.
use std::collections::BTreeMap;
use std::fmt::{Debug, Display};
use ecow::EcoVec;
use thiserror::Error;
use tytanic_utils::fmt::{Separators, Term};
use super::ast::Id;
mod func;
mod set;
mod value;
pub use self::func::Func;
pub use self::set::Set;
pub use self::value::{TryFromValue, Type, Value};
/// A marker trait for tests, this is automatically implemented for all clonable
/// static types.
pub trait Test: Clone + 'static {
/// The id of a test, this is used for matching on tests using test sets
/// created from pattern literals.
fn id(&self) -> &str;
}
/// A trait for expressions to be evaluated and matched.
pub trait Eval<T: Test> {
/// Evaluates this expression to a value.
fn eval(&self, ctx: &Context<T>) -> Result<Value<T>, Error>;
}
/// An evaluation context used to retrieve bindings in test set expressions.
#[derive(Debug, Clone)]
pub struct Context<T> {
/// The bindings available for evaluation.
bindings: BTreeMap<Id, Value<T>>,
}
impl<T> Context<T> {
/// Create a new evaluation context with no bindings.
pub fn new() -> Self {
Self {
bindings: BTreeMap::new(),
}
}
}
impl<T> Context<T> {
/// Inserts a new binding, possibly overriding an old one, returns the old
/// binding if there was one.
pub fn bind<V: Into<Value<T>>>(&mut self, id: Id, value: V) -> Option<Value<T>> {
self.bindings.insert(id, value.into())
}
/// Resolves a binding with the given identifier.
pub fn resolve<I: AsRef<str>>(&self, id: I) -> Result<Value<T>, Error>
where
T: Clone,
{
let id = id.as_ref();
self.bindings
.get(id)
.cloned()
.ok_or_else(|| Error::UnknownBinding { id: id.into() })
}
/// Find similar bindings to the given identifier.
pub fn find_similar(&self, id: &str) -> Vec<Id> {
self.bindings
.keys()
.filter(|cand| strsim::jaro(id, cand.as_str()) > 0.7)
.cloned()
.collect()
}
}
impl<T> Default for Context<T> {
fn default() -> Self {
Self::new()
}
}
/// An error that occurs when a test set expression is evaluated.
#[derive(Debug, Error)]
pub enum Error {
/// The requested binding could not be found.
UnknownBinding {
/// The given identifier.
id: String,
},
/// A function received an incorrect argument count.
InvalidArgumentCount {
/// The identifier of the function.
func: String,
/// The minimum or exact expected number of arguments, interpretation
/// depends on `is_min`.
expected: usize,
/// Whether the expected number is the minimum and allows more arguments.
is_min: bool,
/// The number of arguments passed.
found: usize,
},
/// An invalid type was used in an expression.
TypeMismatch {
/// The expected types.
expected: EcoVec<Type>,
/// The given type.
found: Type,
},
/// A custom error type.
Custom(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
}
impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::UnknownBinding { id } => write!(f, "unknown binding: {id}"),
Error::InvalidArgumentCount {
func,
expected,
is_min,
found,
} => {
let (found, ex) = (*found, *expected);
if ex == 0 {
write!(
f,
"function {func} expects no {}, got {}",
Term::simple("argument").with(ex),
found,
)?;
} else if *is_min {
write!(
f,
"function {func} expects at least {ex} {}, got {}",
Term::simple("argument").with(ex),
found,
)?;
} else {
write!(
f,
"function {func} expects exactly {ex} {}, got {}",
Term::simple("argument").with(ex),
found,
)?;
}
Ok(())
}
Error::TypeMismatch { expected, found } => write!(
f,
"expected {}, found <{}>",
Separators::comma_or().with(expected.iter().map(|t| format!("<{}>", t.name()))),
found.name(),
),
Error::Custom(err) => write!(f, "{err}"),
}
}
}