Skip to main content

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}