tytanic_filter/eval/
mod.rs1use std::collections::BTreeMap;
4use std::fmt::Debug;
5use std::fmt::Display;
6
7use ecow::EcoVec;
8use thiserror::Error;
9use tytanic_utils::fmt::Separators;
10use tytanic_utils::fmt::Term;
11
12use super::ast::Id;
13
14mod func;
15mod set;
16mod value;
17
18pub use self::func::Func;
19pub use self::set::Set;
20pub use self::value::TryFromValue;
21pub use self::value::Type;
22pub use self::value::Value;
23
24pub trait Test: Clone + 'static {
27 fn id(&self) -> &str;
30}
31
32pub trait Eval<T: Test> {
34 fn eval(&self, ctx: &Context<T>) -> Result<Value<T>, Error>;
36}
37
38#[derive(Debug, Clone)]
40pub struct Context<T> {
41 bindings: BTreeMap<Id, Value<T>>,
43}
44
45impl<T> Context<T> {
46 pub fn new() -> Self {
48 Self {
49 bindings: BTreeMap::new(),
50 }
51 }
52}
53
54impl<T> Context<T> {
55 pub fn bind<V: Into<Value<T>>>(&mut self, id: Id, value: V) -> Option<Value<T>> {
58 tracing::trace!(id = %id.as_str(), "binding value into eval context");
59 self.bindings.insert(id, value.into())
60 }
61
62 pub fn resolve<I: AsRef<str>>(&self, id: I) -> Result<Value<T>, Error>
64 where
65 T: Clone,
66 {
67 tracing::trace!(id = %id.as_ref(), "resolving value from eval context");
68 let id = id.as_ref();
69 self.bindings
70 .get(id)
71 .cloned()
72 .ok_or_else(|| Error::UnknownBinding { id: id.into() })
73 }
74
75 pub fn find_similar(&self, id: &str) -> Vec<Id> {
77 self.bindings
78 .keys()
79 .filter(|cand| strsim::jaro(id, cand.as_str()) > 0.7)
80 .cloned()
81 .collect()
82 }
83}
84
85impl<T> Default for Context<T> {
86 fn default() -> Self {
87 Self::new()
88 }
89}
90
91#[derive(Debug, Error)]
93pub enum Error {
94 UnknownBinding {
96 id: String,
98 },
99
100 InvalidArgumentCount {
102 func: String,
104
105 expected: usize,
108
109 is_min: bool,
111
112 found: usize,
114 },
115
116 TypeMismatch {
118 expected: EcoVec<Type>,
120
121 found: Type,
123 },
124
125 Custom(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
127}
128
129impl Display for Error {
130 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131 match self {
132 Error::UnknownBinding { id } => write!(f, "unknown binding: {id}"),
133 Error::InvalidArgumentCount {
134 func,
135 expected,
136 is_min,
137 found,
138 } => {
139 let (found, ex) = (*found, *expected);
140
141 if ex == 0 {
142 write!(
143 f,
144 "function {func} expects no {}, got {}",
145 Term::simple("argument").with(ex),
146 found,
147 )?;
148 } else if *is_min {
149 write!(
150 f,
151 "function {func} expects at least {ex} {}, got {}",
152 Term::simple("argument").with(ex),
153 found,
154 )?;
155 } else {
156 write!(
157 f,
158 "function {func} expects exactly {ex} {}, got {}",
159 Term::simple("argument").with(ex),
160 found,
161 )?;
162 }
163
164 Ok(())
165 }
166 Error::TypeMismatch { expected, found } => write!(
167 f,
168 "expected {}, found <{}>",
169 Separators::comma_or().with(expected.iter().map(|t| format!("<{}>", t.name()))),
170 found.name(),
171 ),
172 Error::Custom(err) => write!(f, "{err}"),
173 }
174 }
175}
176
177#[allow(dead_code)]
179fn assert_traits() {
180 tytanic_utils::assert::send::<Context<()>>();
181 tytanic_utils::assert::sync::<Context<()>>();
182}