use crate::ast::{Column, Expr, GeometricExpr, Macro, Operator};
use std::ops::{BitAnd, BitOr, Not};
#[derive(Clone, PartialEq, Debug)]
pub struct Builder {
expr: Expr,
source: Box<str>,
}
impl Builder {
#[must_use]
pub(crate) fn into_parts(self) -> (Expr, Box<str>) {
(self.expr, self.source)
}
#[must_use]
pub fn source(&self) -> &str {
&self.source
}
fn new(expr: Expr, source: impl Into<Box<str>>) -> Self {
Self {
expr,
source: source.into(),
}
}
}
impl BitAnd for Builder {
type Output = Self;
fn bitand(self, right: Self) -> Self::Output {
let source = format!("({}) and ({})", self.source, right.source);
Self::new(Expr::And(Box::new(self.expr), Box::new(right.expr)), source)
}
}
impl BitOr for Builder {
type Output = Self;
fn bitor(self, right: Self) -> Self::Output {
let source = format!("({}) or ({})", self.source, right.source);
Self::new(Expr::Or(Box::new(self.expr), Box::new(right.expr)), source)
}
}
impl Not for Builder {
type Output = Self;
fn not(self) -> Self::Output {
let source = format!("not ({})", self.source);
Self::new(Expr::Not(Box::new(self.expr)), source)
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct ColumnBuilder(Column, &'static str);
impl ColumnBuilder {
#[must_use]
pub fn eq(self, value: impl Into<Box<str>>) -> Builder {
let value = value.into();
Builder::new(
Expr::Membership {
column: self.0,
values: vec![value.clone()],
},
format!("{} {:?}", self.1, value),
)
}
#[must_use]
pub fn lt(self, value: f64) -> Builder {
self.compare(Operator::Less, value)
}
#[must_use]
pub fn le(self, value: f64) -> Builder {
self.compare(Operator::LessEqual, value)
}
#[must_use]
pub fn gt(self, value: f64) -> Builder {
self.compare(Operator::Greater, value)
}
#[must_use]
pub fn ge(self, value: f64) -> Builder {
self.compare(Operator::GreaterEqual, value)
}
fn compare(self, operator: Operator, value: f64) -> Builder {
let symbol = match operator {
Operator::Less => "<",
Operator::LessEqual => "<=",
Operator::Greater => ">",
Operator::GreaterEqual => ">=",
Operator::Equal => "=",
Operator::NotEqual => "!=",
};
Builder::new(
Expr::Comparison {
column: self.0,
operator,
value,
absolute: false,
},
format!("{} {symbol} {value}", self.1),
)
}
}
pub mod col {
use super::{Builder, Column, ColumnBuilder, Expr, GeometricExpr, Macro};
#[must_use]
pub fn all() -> Builder {
Builder::new(Expr::All, "all")
}
#[must_use]
pub fn none() -> Builder {
Builder::new(Expr::None, "none")
}
#[must_use]
pub fn is_protein() -> Builder {
Builder::new(Expr::Macro(Macro::Protein), "protein")
}
macro_rules! molecular_selector {
($name:ident, $variant:ident, $source:literal, $doc:literal) => {
#[doc = $doc]
#[must_use]
pub fn $name() -> Builder {
Builder::new(Expr::Macro(Macro::$variant), $source)
}
};
}
molecular_selector!(backbone, Backbone, "backbone", "Protein backbone atoms.");
molecular_selector!(
sidechain,
Sidechain,
"sidechain",
"Protein side-chain atoms."
);
molecular_selector!(nucleic, Nucleic, "nucleic", "Nucleic-acid atoms.");
molecular_selector!(
nucleic_backbone,
NucleicBackbone,
"nucleicbackbone",
"Nucleic-acid backbone atoms."
);
molecular_selector!(
nucleic_base,
NucleicBase,
"nucleicbase",
"Nucleic-acid base atoms."
);
molecular_selector!(
nucleic_sugar,
NucleicSugar,
"nucleicsugar",
"Nucleic-acid sugar atoms."
);
molecular_selector!(water, Water, "water", "Water atoms.");
molecular_selector!(ions, Ion, "ion", "Ion atoms.");
molecular_selector!(lipids, Lipid, "lipid", "Lipid atoms.");
molecular_selector!(glycans, Saccharide, "saccharide", "Saccharide atoms.");
molecular_selector!(hetero, Hetero, "hetero", "Heterogeneous atoms.");
molecular_selector!(hydrogen, Hydrogen, "hydrogen", "Hydrogen atoms.");
molecular_selector!(
polar_hydrogen,
PolarHydrogen,
"polar_hydrogen",
"Hydrogen atoms bonded to N, O or S in the full topology. Requires available bonds.
```
use molframe_query::{Query, col};
let query = Query::from_builder(col::polar_hydrogen());
assert_eq!(query.source(), \"polar_hydrogen\");
```"
);
molecular_selector!(
nonpolar_hydrogen,
NonpolarHydrogen,
"nonpolar_hydrogen",
"Hydrogen atoms not bonded to N, O or S, including unbonded H. Requires available bonds.
```
use molframe_query::{Query, col};
let query = Query::from_builder(col::nonpolar_hydrogen());
assert_eq!(query.source(), \"nonpolar_hydrogen\");
```"
);
molecular_selector!(heavy, Heavy, "heavy", "Non-hydrogen atoms.");
molecular_selector!(polymer, Polymer, "polymer", "Polymer atoms.");
molecular_selector!(ligands, Ligand, "ligand", "Non-polymer ligand atoms.");
molecular_selector!(aromatic, Aromatic, "aromatic", "Aromatic atoms.");
molecular_selector!(
helix,
Helix,
"helix",
"All helices, including polyproline-II and deposited other helices."
);
molecular_selector!(strand, Strand, "strand", "Beta-ladder residues only.");
molecular_selector!(sheet, Sheet, "sheet", "Beta strands and isolated bridges.");
molecular_selector!(
alpha_helix,
AlphaHelix,
"alpha_helix",
"Alpha-helix residues."
);
molecular_selector!(
helix_310,
Helix310,
"helix_310",
"Three-ten helix residues."
);
molecular_selector!(pi_helix, PiHelix, "pi_helix", "Pi-helix residues.");
molecular_selector!(
polyproline,
Polyproline,
"polyproline",
"Polyproline-II helix residues."
);
molecular_selector!(bridge, Bridge, "bridge", "Isolated beta-bridge residues.");
molecular_selector!(turn, Turn, "turn", "Hydrogen-bonded turn residues.");
molecular_selector!(bend, Bend, "bend", "Bend residues.");
molecular_selector!(
coil,
Coil,
"coil",
"Assigned coil residues, excluding unknown."
);
molecular_selector!(
plddt_very_high,
PlddtVeryHigh,
"plddt_very_high",
"Atoms whose pLDDT is above 90."
);
molecular_selector!(
plddt_confident,
PlddtConfident,
"plddt_confident",
"Atoms whose pLDDT is above 70 and at most 90."
);
molecular_selector!(
plddt_low,
PlddtLow,
"plddt_low",
"Atoms whose pLDDT is above 50 and at most 70."
);
molecular_selector!(
plddt_very_low,
PlddtVeryLow,
"plddt_very_low",
"Atoms whose pLDDT is at most 50."
);
#[must_use]
pub const fn name() -> ColumnBuilder {
ColumnBuilder(Column::AtomName, "name")
}
#[must_use]
pub const fn resname() -> ColumnBuilder {
ColumnBuilder(Column::ResidueName, "resname")
}
#[must_use]
pub const fn chain() -> ColumnBuilder {
ColumnBuilder(Column::Chain, "chain")
}
#[must_use]
pub const fn bfactor() -> ColumnBuilder {
ColumnBuilder(Column::BFactor, "bfactor")
}
#[must_use]
pub const fn occupancy() -> ColumnBuilder {
ColumnBuilder(Column::Occupancy, "occupancy")
}
#[must_use]
pub const fn x() -> ColumnBuilder {
ColumnBuilder(Column::X, "x")
}
#[must_use]
pub fn within(radius: f32, target: Builder) -> Builder {
let source = format!("within {radius} of ({})", target.source);
Builder::new(
Expr::Geometric(GeometricExpr::Within {
radius,
target: Box::new(target.expr),
}),
source,
)
}
#[must_use]
pub fn by_residue(target: Builder) -> Builder {
let source = format!("byres ({})", target.source);
Builder::new(Expr::ByResidue(Box::new(target.expr)), source)
}
#[must_use]
pub fn residues_within(radius: f32, target: Builder) -> Builder {
by_residue(within(radius, target))
}
}
#[cfg(test)]
#[path = "builder_tests.rs"]
mod tests;