1use crate::Query;
27use crate::ast::{Expr, GeometricExpr};
28use molframe_core::diagnostic::{Code, Diagnostic};
29use std::collections::{BTreeMap, BTreeSet};
30
31#[derive(Clone, Debug, Default)]
55pub struct QueryAliases {
56 definitions: BTreeMap<Box<str>, Query>,
57}
58
59impl QueryAliases {
60 pub const MAX_DEPTH: usize = 64;
65
66 #[must_use]
68 pub fn new() -> Self {
69 Self::default()
70 }
71
72 #[must_use]
78 pub fn is_valid_name(name: &str) -> bool {
79 crate::parser::is_query_name(name)
80 }
81
82 pub fn define(
92 &mut self,
93 name: impl Into<Box<str>>,
94 query: Query,
95 ) -> Result<Option<Query>, Diagnostic> {
96 let name = name.into();
97 if !Self::is_valid_name(&name) {
98 return Err(Diagnostic::new(Code::E4001)
99 .with_message("a query name is a letter or underscore followed by letters, digits or underscores")
100 .with_context("name", name));
101 }
102 Ok(self.definitions.insert(name, query))
103 }
104
105 pub fn remove(&mut self, name: &str) -> Option<Query> {
107 self.definitions.remove(name)
108 }
109
110 #[must_use]
112 pub fn get(&self, name: &str) -> Option<&Query> {
113 self.definitions.get(name)
114 }
115
116 #[must_use]
118 pub fn contains(&self, name: &str) -> bool {
119 self.definitions.contains_key(name)
120 }
121
122 pub fn names(&self) -> impl Iterator<Item = &str> {
124 self.definitions.keys().map(AsRef::as_ref)
125 }
126
127 #[must_use]
129 pub fn len(&self) -> usize {
130 self.definitions.len()
131 }
132
133 #[must_use]
135 pub fn is_empty(&self) -> bool {
136 self.definitions.is_empty()
137 }
138
139 pub fn resolve(&self, query: &Query) -> Result<Query, Vec<Diagnostic>> {
151 if !has_reference(&query.expr) {
152 return Ok(query.clone());
153 }
154 let mut resolver = Resolver {
155 aliases: self,
156 resolved: BTreeMap::new(),
157 stack: Vec::new(),
158 warnings: query.warnings.clone(),
159 };
160 let expr = resolver
161 .substitute(&query.expr)
162 .map_err(|finding| vec![finding])?;
163 let mut warnings = resolver.warnings;
164 warnings.sort_by_key(Diagnostic::code);
165 let source = crate::print::print(&expr).into_boxed_str();
166 Ok(Query {
167 expr,
168 warnings,
169 source,
170 })
171 }
172}
173
174impl Query {
175 #[must_use]
180 pub fn references(&self) -> Vec<&str> {
181 let mut names = BTreeSet::new();
182 collect_references(&self.expr, &mut names);
183 names.into_iter().collect()
184 }
185}
186
187struct Resolver<'a> {
188 aliases: &'a QueryAliases,
189 resolved: BTreeMap<&'a str, Expr>,
191 stack: Vec<&'a str>,
193 warnings: Vec<Diagnostic>,
194}
195
196impl Resolver<'_> {
197 fn substitute(&mut self, expr: &Expr) -> Result<Expr, Diagnostic> {
198 Ok(match expr {
199 Expr::Group(name) => self.definition(name)?,
200 Expr::And(left, right) => Expr::And(
201 Box::new(self.substitute(left)?),
202 Box::new(self.substitute(right)?),
203 ),
204 Expr::Or(left, right) => Expr::Or(
205 Box::new(self.substitute(left)?),
206 Box::new(self.substitute(right)?),
207 ),
208 Expr::Not(value) => Expr::Not(Box::new(self.substitute(value)?)),
209 Expr::Global(value) => Expr::Global(Box::new(self.substitute(value)?)),
210 Expr::ByResidue(value) => Expr::ByResidue(Box::new(self.substitute(value)?)),
211 Expr::Same { key, target } => Expr::Same {
212 key: key.clone(),
213 target: Box::new(self.substitute(target)?),
214 },
215 Expr::Bonded { depth, target } => Expr::Bonded {
216 depth: *depth,
217 target: Box::new(self.substitute(target)?),
218 },
219 Expr::Geometric(geometric) => Expr::Geometric(self.geometric(geometric)?),
220 leaf => leaf.clone(),
221 })
222 }
223
224 fn geometric(&mut self, expr: &GeometricExpr) -> Result<GeometricExpr, Diagnostic> {
225 let mut resolved = expr.clone();
226 if let Some(target) = geometric_target_mut(&mut resolved) {
227 **target = self.substitute(target)?;
228 }
229 Ok(resolved)
230 }
231
232 fn definition(&mut self, name: &str) -> Result<Expr, Diagnostic> {
233 let Some((key, query)) = self.aliases.definitions.get_key_value(name) else {
234 return Err(Diagnostic::new(Code::E4005).with_context("name", name));
235 };
236 if let Some(expr) = self.resolved.get(key.as_ref()) {
237 return Ok(expr.clone());
238 }
239 if let Some(start) = self.stack.iter().position(|entry| *entry == name) {
240 let mut cycle = self.stack[start..].join(" -> ");
241 cycle.push_str(" -> ");
242 cycle.push_str(name);
243 return Err(Diagnostic::new(Code::E4006).with_context("cycle", cycle));
244 }
245 if self.stack.len() >= QueryAliases::MAX_DEPTH {
246 return Err(Diagnostic::new(Code::E4007).with_context("name", name));
247 }
248 self.stack.push(key);
249 let expr = self.substitute(&query.expr);
250 let _ = self.stack.pop();
251 let expr = expr?;
252 self.warnings.extend(query.warnings.iter().cloned());
253 let _ = self.resolved.insert(key, expr.clone());
254 Ok(expr)
255 }
256}
257
258fn geometric_target_mut(expr: &mut GeometricExpr) -> Option<&mut Box<Expr>> {
259 match expr {
260 GeometricExpr::Within { target, .. }
261 | GeometricExpr::Beyond { target, .. }
262 | GeometricExpr::Around { target, .. }
263 | GeometricExpr::SphereZone { target, .. }
264 | GeometricExpr::SphereLayer { target, .. }
265 | GeometricExpr::IsoLayer { target, .. }
266 | GeometricExpr::CylinderZone { target, .. }
267 | GeometricExpr::CylinderLayer { target, .. } => Some(target),
268 GeometricExpr::Point { .. } => None,
269 }
270}
271
272fn geometric_target(expr: &GeometricExpr) -> Option<&Expr> {
273 match expr {
274 GeometricExpr::Within { target, .. }
275 | GeometricExpr::Beyond { target, .. }
276 | GeometricExpr::Around { target, .. }
277 | GeometricExpr::SphereZone { target, .. }
278 | GeometricExpr::SphereLayer { target, .. }
279 | GeometricExpr::IsoLayer { target, .. }
280 | GeometricExpr::CylinderZone { target, .. }
281 | GeometricExpr::CylinderLayer { target, .. } => Some(target),
282 GeometricExpr::Point { .. } => None,
283 }
284}
285
286fn children<'e>(expr: &'e Expr, visit: &mut impl FnMut(&'e Expr)) {
288 match expr {
289 Expr::And(left, right) | Expr::Or(left, right) => {
290 visit(left);
291 visit(right);
292 }
293 Expr::Not(value) | Expr::Global(value) | Expr::ByResidue(value) => visit(value),
294 Expr::Same { target, .. } | Expr::Bonded { target, .. } => visit(target),
295 Expr::Geometric(geometric) => {
296 if let Some(target) = geometric_target(geometric) {
297 visit(target);
298 }
299 }
300 Expr::All
301 | Expr::None
302 | Expr::Comparison { .. }
303 | Expr::Membership { .. }
304 | Expr::Group(_)
305 | Expr::Atom { .. }
306 | Expr::Macro(_)
307 | Expr::Chirality(_)
308 | Expr::Smarts { .. } => {}
309 }
310}
311
312fn has_reference(expr: &Expr) -> bool {
313 if matches!(expr, Expr::Group(_)) {
314 return true;
315 }
316 let mut found = false;
317 children(expr, &mut |child| found = found || has_reference(child));
318 found
319}
320
321fn collect_references<'e>(expr: &'e Expr, names: &mut BTreeSet<&'e str>) {
322 if let Expr::Group(name) = expr {
323 let _ = names.insert(name);
324 return;
325 }
326 children(expr, &mut |child| collect_references(child, names));
327}
328
329#[cfg(test)]
330#[path = "aliases_tests.rs"]
331mod tests;