molprint_core/smarts/ast.rs
1use crate::mol::atom::Element;
2
3/// Primitive atom predicates for SMARTS matching.
4#[derive(Debug, Clone, PartialEq)]
5pub enum AtomPrimitive {
6 /// Match any atom: `*`.
7 Any,
8 /// Match by atomic number: `#n`.
9 AtomicNum(u8),
10 /// Match a specific element as aliphatic (uppercase symbol, e.g. `C`, `N`).
11 ElementAliphatic(Element),
12 /// Match a specific element as aromatic (lowercase symbol, e.g. `c`, `n`).
13 ElementAromatic(Element),
14 /// Match any aromatic atom: `a`.
15 Aromatic,
16 /// Match any aliphatic atom: `A`.
17 Aliphatic,
18 /// Match total H count: `H` (= 1) or `Hn`.
19 HCount(u8),
20 /// Match formal charge: `+n` or `-n`.
21 Charge(i8),
22 /// Atom is in any ring: `R`. `Some(n)` = in ring of size n.
23 Ring(Option<usize>),
24 /// Atom is not in any ring: `R0`.
25 NotInRing,
26 /// Heavy-atom degree: `Dn`.
27 Degree(u8),
28}
29
30/// Atom expression tree supporting logical composition.
31#[derive(Debug, Clone, PartialEq)]
32pub enum AtomExpr {
33 /// A single primitive predicate.
34 Primitive(AtomPrimitive),
35 /// Logical NOT.
36 Not(Box<AtomExpr>),
37 /// Logical AND (both explicit `&` and implicit adjacency inside `[…]`).
38 And(Box<AtomExpr>, Box<AtomExpr>),
39 /// Logical OR (`,`).
40 Or(Box<AtomExpr>, Box<AtomExpr>),
41}
42
43/// SMARTS bond predicate.
44#[derive(Debug, Clone, PartialEq)]
45pub enum SmartsBond {
46 /// `-` single bond.
47 Single,
48 /// `=` double bond.
49 Double,
50 /// `#` triple bond.
51 Triple,
52 /// `:` aromatic bond.
53 Aromatic,
54 /// `~` any bond.
55 Any,
56 /// No explicit bond token (matches single or aromatic, context-dependent).
57 Unspecified,
58}
59
60/// A single atom slot in a SMARTS pattern.
61#[derive(Debug, Clone)]
62pub struct SmartsAtom {
63 /// Predicate that a molecule atom must satisfy to match this slot.
64 pub expr: AtomExpr,
65}
66
67/// A compiled SMARTS pattern.
68#[derive(Debug, Clone, Default)]
69pub struct SmartsPattern {
70 /// Atoms in the order they appear in the SMARTS string.
71 pub atoms: Vec<SmartsAtom>,
72 /// Edges: `(from_idx, to_idx, bond_predicate)`.
73 pub edges: Vec<(usize, usize, SmartsBond)>,
74}
75
76impl SmartsPattern {
77 /// Create an empty pattern.
78 pub fn new() -> Self {
79 Self::default()
80 }
81
82 /// Number of atom slots.
83 pub fn num_atoms(&self) -> usize {
84 self.atoms.len()
85 }
86
87 /// Neighbours of pattern atom `idx`: iterator of `(neighbour_idx, bond_predicate)`.
88 pub fn neighbors(&self, idx: usize) -> impl Iterator<Item = (usize, &SmartsBond)> {
89 self.edges.iter().filter_map(move |(a, b, bond)| {
90 if *a == idx {
91 Some((*b, bond))
92 } else if *b == idx {
93 Some((*a, bond))
94 } else {
95 None
96 }
97 })
98 }
99}