1use crate::ast::{Column, Expr, GeometricExpr, Macro, Operator};
4use std::ops::{BitAnd, BitOr, Not};
5
6#[derive(Clone, PartialEq, Debug)]
8pub struct Builder {
9 expr: Expr,
10 source: Box<str>,
11}
12
13impl Builder {
14 #[must_use]
16 pub(crate) fn into_parts(self) -> (Expr, Box<str>) {
17 (self.expr, self.source)
18 }
19
20 #[must_use]
22 pub fn source(&self) -> &str {
23 &self.source
24 }
25
26 fn new(expr: Expr, source: impl Into<Box<str>>) -> Self {
27 Self {
28 expr,
29 source: source.into(),
30 }
31 }
32}
33
34impl BitAnd for Builder {
35 type Output = Self;
36
37 fn bitand(self, right: Self) -> Self::Output {
38 let source = format!("({}) and ({})", self.source, right.source);
39 Self::new(Expr::And(Box::new(self.expr), Box::new(right.expr)), source)
40 }
41}
42
43impl BitOr for Builder {
44 type Output = Self;
45
46 fn bitor(self, right: Self) -> Self::Output {
47 let source = format!("({}) or ({})", self.source, right.source);
48 Self::new(Expr::Or(Box::new(self.expr), Box::new(right.expr)), source)
49 }
50}
51
52impl Not for Builder {
53 type Output = Self;
54
55 fn not(self) -> Self::Output {
56 let source = format!("not ({})", self.source);
57 Self::new(Expr::Not(Box::new(self.expr)), source)
58 }
59}
60
61#[derive(Clone, Copy, PartialEq, Eq, Debug)]
63pub struct ColumnBuilder(Column, &'static str);
64
65impl ColumnBuilder {
66 #[must_use]
68 pub fn eq(self, value: impl Into<Box<str>>) -> Builder {
69 let value = value.into();
70 Builder::new(
71 Expr::Membership {
72 column: self.0,
73 values: vec![value.clone()],
74 },
75 format!("{} {:?}", self.1, value),
76 )
77 }
78
79 #[must_use]
81 pub fn lt(self, value: f64) -> Builder {
82 self.compare(Operator::Less, value)
83 }
84
85 #[must_use]
87 pub fn le(self, value: f64) -> Builder {
88 self.compare(Operator::LessEqual, value)
89 }
90
91 #[must_use]
93 pub fn gt(self, value: f64) -> Builder {
94 self.compare(Operator::Greater, value)
95 }
96
97 #[must_use]
99 pub fn ge(self, value: f64) -> Builder {
100 self.compare(Operator::GreaterEqual, value)
101 }
102
103 fn compare(self, operator: Operator, value: f64) -> Builder {
104 let symbol = match operator {
105 Operator::Less => "<",
106 Operator::LessEqual => "<=",
107 Operator::Greater => ">",
108 Operator::GreaterEqual => ">=",
109 Operator::Equal => "=",
110 Operator::NotEqual => "!=",
111 };
112 Builder::new(
113 Expr::Comparison {
114 column: self.0,
115 operator,
116 value,
117 absolute: false,
118 },
119 format!("{} {symbol} {value}", self.1),
120 )
121 }
122}
123
124pub mod col {
126 use super::{Builder, Column, ColumnBuilder, Expr, GeometricExpr, Macro};
127
128 #[must_use]
130 pub fn all() -> Builder {
131 Builder::new(Expr::All, "all")
132 }
133
134 #[must_use]
136 pub fn none() -> Builder {
137 Builder::new(Expr::None, "none")
138 }
139
140 #[must_use]
142 pub fn is_protein() -> Builder {
143 Builder::new(Expr::Macro(Macro::Protein), "protein")
144 }
145
146 macro_rules! molecular_selector {
147 ($name:ident, $variant:ident, $source:literal, $doc:literal) => {
148 #[doc = $doc]
149 #[must_use]
150 pub fn $name() -> Builder {
151 Builder::new(Expr::Macro(Macro::$variant), $source)
152 }
153 };
154 }
155
156 molecular_selector!(backbone, Backbone, "backbone", "Protein backbone atoms.");
157 molecular_selector!(
158 sidechain,
159 Sidechain,
160 "sidechain",
161 "Protein side-chain atoms."
162 );
163 molecular_selector!(nucleic, Nucleic, "nucleic", "Nucleic-acid atoms.");
164 molecular_selector!(
165 nucleic_backbone,
166 NucleicBackbone,
167 "nucleicbackbone",
168 "Nucleic-acid backbone atoms."
169 );
170 molecular_selector!(
171 nucleic_base,
172 NucleicBase,
173 "nucleicbase",
174 "Nucleic-acid base atoms."
175 );
176 molecular_selector!(
177 nucleic_sugar,
178 NucleicSugar,
179 "nucleicsugar",
180 "Nucleic-acid sugar atoms."
181 );
182 molecular_selector!(water, Water, "water", "Water atoms.");
183 molecular_selector!(ions, Ion, "ion", "Ion atoms.");
184 molecular_selector!(lipids, Lipid, "lipid", "Lipid atoms.");
185 molecular_selector!(glycans, Saccharide, "saccharide", "Saccharide atoms.");
186 molecular_selector!(hetero, Hetero, "hetero", "Heterogeneous atoms.");
187 molecular_selector!(hydrogen, Hydrogen, "hydrogen", "Hydrogen atoms.");
188 molecular_selector!(
189 polar_hydrogen,
190 PolarHydrogen,
191 "polar_hydrogen",
192 "Hydrogen atoms bonded to N, O or S in the full topology. Requires available bonds.
193
194```
195use molframe_query::{Query, col};
196let query = Query::from_builder(col::polar_hydrogen());
197assert_eq!(query.source(), \"polar_hydrogen\");
198```"
199 );
200 molecular_selector!(
201 nonpolar_hydrogen,
202 NonpolarHydrogen,
203 "nonpolar_hydrogen",
204 "Hydrogen atoms not bonded to N, O or S, including unbonded H. Requires available bonds.
205
206```
207use molframe_query::{Query, col};
208let query = Query::from_builder(col::nonpolar_hydrogen());
209assert_eq!(query.source(), \"nonpolar_hydrogen\");
210```"
211 );
212 molecular_selector!(heavy, Heavy, "heavy", "Non-hydrogen atoms.");
213 molecular_selector!(polymer, Polymer, "polymer", "Polymer atoms.");
214 molecular_selector!(ligands, Ligand, "ligand", "Non-polymer ligand atoms.");
215 molecular_selector!(aromatic, Aromatic, "aromatic", "Aromatic atoms.");
216 molecular_selector!(
217 helix,
218 Helix,
219 "helix",
220 "All helices, including polyproline-II and deposited other helices."
221 );
222 molecular_selector!(strand, Strand, "strand", "Beta-ladder residues only.");
223 molecular_selector!(sheet, Sheet, "sheet", "Beta strands and isolated bridges.");
224 molecular_selector!(
225 alpha_helix,
226 AlphaHelix,
227 "alpha_helix",
228 "Alpha-helix residues."
229 );
230 molecular_selector!(
231 helix_310,
232 Helix310,
233 "helix_310",
234 "Three-ten helix residues."
235 );
236 molecular_selector!(pi_helix, PiHelix, "pi_helix", "Pi-helix residues.");
237 molecular_selector!(
238 polyproline,
239 Polyproline,
240 "polyproline",
241 "Polyproline-II helix residues."
242 );
243 molecular_selector!(bridge, Bridge, "bridge", "Isolated beta-bridge residues.");
244 molecular_selector!(turn, Turn, "turn", "Hydrogen-bonded turn residues.");
245 molecular_selector!(bend, Bend, "bend", "Bend residues.");
246 molecular_selector!(
247 coil,
248 Coil,
249 "coil",
250 "Assigned coil residues, excluding unknown."
251 );
252 molecular_selector!(
253 plddt_very_high,
254 PlddtVeryHigh,
255 "plddt_very_high",
256 "Atoms whose pLDDT is above 90."
257 );
258 molecular_selector!(
259 plddt_confident,
260 PlddtConfident,
261 "plddt_confident",
262 "Atoms whose pLDDT is above 70 and at most 90."
263 );
264 molecular_selector!(
265 plddt_low,
266 PlddtLow,
267 "plddt_low",
268 "Atoms whose pLDDT is above 50 and at most 70."
269 );
270 molecular_selector!(
271 plddt_very_low,
272 PlddtVeryLow,
273 "plddt_very_low",
274 "Atoms whose pLDDT is at most 50."
275 );
276
277 #[must_use]
279 pub const fn name() -> ColumnBuilder {
280 ColumnBuilder(Column::AtomName, "name")
281 }
282
283 #[must_use]
285 pub const fn resname() -> ColumnBuilder {
286 ColumnBuilder(Column::ResidueName, "resname")
287 }
288
289 #[must_use]
291 pub const fn chain() -> ColumnBuilder {
292 ColumnBuilder(Column::Chain, "chain")
293 }
294
295 #[must_use]
297 pub const fn bfactor() -> ColumnBuilder {
298 ColumnBuilder(Column::BFactor, "bfactor")
299 }
300
301 #[must_use]
303 pub const fn occupancy() -> ColumnBuilder {
304 ColumnBuilder(Column::Occupancy, "occupancy")
305 }
306
307 #[must_use]
309 pub const fn x() -> ColumnBuilder {
310 ColumnBuilder(Column::X, "x")
311 }
312
313 #[must_use]
315 pub fn within(radius: f32, target: Builder) -> Builder {
316 let source = format!("within {radius} of ({})", target.source);
317 Builder::new(
318 Expr::Geometric(GeometricExpr::Within {
319 radius,
320 target: Box::new(target.expr),
321 }),
322 source,
323 )
324 }
325
326 #[must_use]
328 pub fn by_residue(target: Builder) -> Builder {
329 let source = format!("byres ({})", target.source);
330 Builder::new(Expr::ByResidue(Box::new(target.expr)), source)
331 }
332
333 #[must_use]
335 pub fn residues_within(radius: f32, target: Builder) -> Builder {
336 by_residue(within(radius, target))
337 }
338}
339
340#[cfg(test)]
341#[path = "builder_tests.rs"]
342mod tests;