use omgkit_core::{AtomData, AtomFlags, BondData, BondFlags, BondOrder, MolBuilder};
use super::bond::{parse_bond_expr, starts_bond_expr};
use super::expr::{AtomExpr, AtomPrim, BondExpr, BondPrim};
use super::parse::parse_atom_expr;
use super::QueryMol;
use crate::error::{ParseError, ParseErrorKind as K, Result};
pub fn parse(input: &str) -> Result<QueryMol> {
Parser::new(input.as_bytes()).run()
}
#[derive(Debug, Clone)]
struct RingOpen {
atom: u32,
expr: Option<BondExpr>,
pos: usize,
seq: u32,
}
#[derive(Debug, Clone)]
struct RingBond {
number: u32,
seq: u32,
begin: u32,
end: u32,
expr: BondExpr,
open_seq: u32,
}
struct Parser<'a> {
src: &'a [u8],
pos: usize,
topology: MolBuilder,
atoms: Vec<AtomExpr>,
bonds: Vec<BondExpr>,
prev: Option<u32>,
branches: Vec<(u32, usize)>,
rings: std::collections::HashMap<u32, RingOpen>,
ring_bonds: Vec<RingBond>,
pending: Option<(BondExpr, usize)>,
next_ring_seq: u32,
ring_at_atom: std::collections::HashMap<u32, Vec<u32>>,
fragment_start: Vec<bool>,
}
impl<'a> Parser<'a> {
fn new(src: &'a [u8]) -> Self {
Self {
src,
pos: 0,
topology: MolBuilder::new(),
atoms: Vec::new(),
bonds: Vec::new(),
prev: None,
branches: Vec::new(),
rings: std::collections::HashMap::new(),
ring_bonds: Vec::new(),
pending: None,
next_ring_seq: 0,
ring_at_atom: std::collections::HashMap::new(),
fragment_start: Vec::new(),
}
}
fn err<T>(&self, kind: K, pos: usize) -> Result<T> {
Err(ParseError::new(kind, pos, self.src))
}
fn peek(&self) -> Option<u8> {
self.src.get(self.pos).copied()
}
fn run(mut self) -> Result<QueryMol> {
if self.src.is_empty() {
return self.err(K::Empty, 0);
}
while let Some(b) = self.peek() {
match b {
b'(' => self.open_branch()?,
b')' => self.close_branch()?,
b'[' => self.bracket_atom()?,
b'.' => {
if let Some((_, p)) = &self.pending {
return self.err(K::DanglingBond, *p);
}
self.pos += 1;
self.prev = None;
}
b'0'..=b'9' => {
let pos = self.pos;
let n = u32::from(b - b'0');
self.pos += 1;
self.ring_closure(n, pos)?;
}
b'%' => {
let pos = self.pos;
self.pos += 1;
let n = self.ring_number()?;
self.ring_closure(n, pos)?;
}
_ if starts_bond_expr(b) => self.bond_symbol()?,
_ => self.organic_atom()?,
}
}
if let Some((_, p)) = &self.pending {
return self.err(K::DanglingBond, *p);
}
if !self.branches.is_empty() {
let pos = self.pos;
return self.err(K::UnbalancedParen, pos);
}
if let Some((&num, open)) = self.rings.iter().min_by_key(|(_, o)| o.pos) {
let pos = open.pos;
return self.err(K::UnclosedRingBond(num), pos);
}
if self.topology.num_atoms() == 0 {
return self.err(K::Empty, 0);
}
let mut ring_bonds = std::mem::take(&mut self.ring_bonds);
ring_bonds.sort_by_key(|r| (r.number, r.seq));
let (pos, src) = (self.pos, self.src);
let mut ring_bond_index: std::collections::HashMap<u32, u32> =
std::collections::HashMap::new();
for rb in ring_bonds {
let mut bd = BondData::new(rb.begin, rb.end, BondOrder::Unspecified);
bd.flags.insert(BondFlags::HAS_QUERY);
let idx = self
.topology
.add_bond_data(bd)
.map_err(|_| ParseError::new(K::RingBondToSelf(rb.number), pos, src))?;
ring_bond_index.insert(rb.open_seq, idx);
self.bonds.push(rb.expr);
}
self.fix_chirality(&ring_bond_index);
let q = QueryMol {
topology: self.topology,
atoms: self.atoms,
bonds: self.bonds,
};
debug_assert!(q.is_consistent(), "查询树与拓扑长度不一致");
Ok(q)
}
fn fix_chirality(&mut self, ring_bond_index: &std::collections::HashMap<u32, u32>) {
for atom in 0..self.topology.num_atoms() as u32 {
let expr = &self.atoms[atom as usize];
let Some(tag) = super::required_chirality(expr) else {
continue;
};
if !tag.is_tetrahedral() {
continue;
}
let ring_here: Vec<u32> = self
.ring_at_atom
.get(&atom)
.map(|seqs| {
seqs.iter()
.filter_map(|s| ring_bond_index.get(s).copied())
.collect()
})
.unwrap_or_default();
let stored: Vec<u32> = self
.topology
.bonds()
.iter()
.enumerate()
.filter(|(_, b)| b.other_end(atom).is_some())
.map(|(i, _)| u32::try_from(i).unwrap_or(u32::MAX))
.collect();
let mut entries: Vec<(u32, Option<u32>)> = vec![(atom, None)];
for (i, b) in self.topology.bonds().iter().enumerate() {
let i = u32::try_from(i).unwrap_or(u32::MAX);
if ring_here.contains(&i) {
continue;
}
if let Some(other) = b.other_end(atom) {
entries.push((other, Some(i)));
}
}
entries.sort_by_key(|e| e.0);
let mut written = Vec::with_capacity(stored.len());
for (_, bond) in entries {
match bond {
None => written.extend(ring_here.iter().copied()),
Some(i) => written.push(i),
}
}
if written.len() != stored.len() {
continue; }
let mut odd = permutation_is_odd(&written, &stored);
if stored.len() == 3
&& needs_h_compensation(
self.fragment_start[atom as usize],
expr,
ring_here.len(),
has_unsaturated_bond(&self.topology, &self.bonds, atom),
)
{
odd = !odd;
}
if odd {
invert_chirality(&mut self.atoms[atom as usize]);
}
}
}
fn open_branch(&mut self) -> Result<()> {
let pos = self.pos;
self.pos += 1;
match self.prev {
Some(a) => {
self.branches.push((a, self.topology.num_atoms()));
Ok(())
}
None => self.err(K::UnbalancedParen, pos),
}
}
fn close_branch(&mut self) -> Result<()> {
let pos = self.pos;
self.pos += 1;
match self.branches.pop() {
Some((atom, n_at_open)) => {
if self.topology.num_atoms() == n_at_open {
return self.err(K::EmptyBranch, pos);
}
if let Some((_, p)) = &self.pending {
return self.err(K::DanglingBond, *p);
}
self.prev = Some(atom);
Ok(())
}
None => self.err(K::UnbalancedParen, pos),
}
}
fn bond_symbol(&mut self) -> Result<()> {
let pos = self.pos;
if self.pending.is_some() {
return self.err(K::DanglingBond, pos);
}
let start = self.pos;
let mut prev = 0u8;
while let Some(b) = self.peek() {
let part_of_expr = starts_bond_expr(b)
|| matches!(b, b'&' | b',' | b';')
|| (b == b'>' && prev == b'-');
if !part_of_expr {
break;
}
prev = b;
self.pos += 1;
}
let expr = parse_bond_expr(&self.src[start..self.pos])
.map_err(|e| ParseError::new(e.kind, start + e.pos, self.src))?;
self.pending = Some((expr, pos));
Ok(())
}
fn organic_atom(&mut self) -> Result<()> {
let pos = self.pos;
let b = self.peek().expect("已 peek");
let two = self.src.get(self.pos + 1).copied();
let (z, len, aromatic) = match (b, two) {
(b'C', Some(b'l')) => (17u8, 2usize, false),
(b'B', Some(b'r')) => (35, 2, false),
(b'B', _) => (5, 1, false),
(b'C', _) => (6, 1, false),
(b'N', _) => (7, 1, false),
(b'O', _) => (8, 1, false),
(b'P', _) => (15, 1, false),
(b'S', _) => (16, 1, false),
(b'F', _) => (9, 1, false),
(b'I', _) => (53, 1, false),
(b'b', _) => (5, 1, true),
(b'c', _) => (6, 1, true),
(b'n', _) => (7, 1, true),
(b'o', _) => (8, 1, true),
(b'p', _) => (15, 1, true),
(b's', _) => (16, 1, true),
(b'a', _) => {
self.pos += 1;
return self.push_atom(AtomExpr::Prim(AtomPrim::Aromatic), pos);
}
(b'A', _) => {
self.pos += 1;
return self.push_atom(AtomExpr::Prim(AtomPrim::Aliphatic), pos);
}
(b'*', _) => {
self.pos += 1;
return self.push_atom(AtomExpr::Prim(AtomPrim::Any), pos);
}
_ => {
return self.err(K::UnknownElement(char::from(b).to_string()), pos);
}
};
self.pos += len;
self.push_atom(
AtomExpr::Prim(AtomPrim::Element {
z,
aromatic: Some(aromatic),
}),
pos,
)
}
fn bracket_atom(&mut self) -> Result<()> {
let open_pos = self.pos;
self.pos += 1;
let start = self.pos;
let mut depth = 0usize;
loop {
match self.peek() {
None => return self.err(K::UnexpectedEnd, self.pos),
Some(b'[') => {
depth += 1;
self.pos += 1;
}
Some(b']') if depth == 0 => break,
Some(b']') => {
depth -= 1;
self.pos += 1;
}
Some(_) => self.pos += 1,
}
}
debug_assert_eq!(depth, 0, "方括号计数没归零");
let inner = &self.src[start..self.pos];
self.pos += 1;
let expr = match hydrogen_special_case(inner) {
Some(e) => e,
None => parse_atom_expr(inner)
.map_err(|e| ParseError::new(e.kind, start + e.pos, self.src))?,
};
self.push_atom(expr, open_pos)
}
fn push_atom(&mut self, expr: AtomExpr, pos: usize) -> Result<()> {
let mut atom = AtomData::new(0);
atom.flags.insert(AtomFlags::HAS_QUERY);
let idx = self.topology.add_atom_data(atom);
self.fragment_start.push(self.prev.is_none());
self.atoms.push(expr);
if let Some(prev) = self.prev {
let expr = self
.pending
.take()
.map_or_else(BondExpr::default_bond, |(e, _)| e);
let mut bd = BondData::new(prev, idx, BondOrder::Unspecified);
bd.flags.insert(BondFlags::HAS_QUERY);
let src = self.src;
self.topology
.add_bond_data(bd)
.map_err(|_| ParseError::new(K::UnexpectedChar('?'), pos, src))?;
self.bonds.push(expr);
} else if let Some((_, p)) = self.pending.take() {
return self.err(K::DanglingBond, p);
}
self.prev = Some(idx);
Ok(())
}
fn ring_number(&mut self) -> Result<u32> {
let pos = self.pos;
let mut v = 0u32;
let mut n = 0;
while n < 2 {
match self.peek() {
Some(d @ b'0'..=b'9') => {
v = v * 10 + u32::from(d - b'0');
self.pos += 1;
n += 1;
}
_ => break,
}
}
if n == 2 {
Ok(v)
} else {
self.err(K::BadBracketAtom("`%` 后需要两位数字"), pos)
}
}
fn ring_closure(&mut self, num: u32, pos: usize) -> Result<()> {
let Some(cur) = self.prev else {
return self.err(K::UnexpectedChar(char::from(self.src[pos])), pos);
};
let pending = self.pending.take().map(|(e, _)| e);
match self.rings.remove(&num) {
Some(open) => {
if open.atom == cur {
return self.err(K::RingBondToSelf(num), pos);
}
if open.expr.is_some() && pending.is_some() && open.expr != pending {
return self.err(K::ConflictingRingBondOrder(num), pos);
}
let (expr, from_open) = match (open.expr, pending) {
(Some(e), _) => (e, true),
(None, Some(e)) => (e, false),
(None, None) => (BondExpr::default_bond(), false),
};
let (begin, end) = if from_open {
(open.atom, cur)
} else {
(cur, open.atom)
};
let seq = u32::try_from(self.ring_bonds.len()).unwrap_or(u32::MAX);
self.ring_at_atom.entry(cur).or_default().push(open.seq);
self.ring_bonds.push(RingBond {
number: num,
seq,
begin,
end,
expr,
open_seq: open.seq,
});
}
None => {
let seq = self.next_ring_seq;
self.next_ring_seq += 1;
self.ring_at_atom.entry(cur).or_default().push(seq);
self.rings.insert(
num,
RingOpen {
atom: cur,
expr: pending,
pos,
seq,
},
);
}
}
Ok(())
}
}
fn hydrogen_special_case(inner: &[u8]) -> Option<AtomExpr> {
let mut i = 0;
let mut parts: Vec<AtomExpr> = Vec::new();
let start = i;
while i < inner.len() && inner[i].is_ascii_digit() {
i += 1;
}
if i > start {
let n: u32 = std::str::from_utf8(&inner[start..i]).ok()?.parse().ok()?;
parts.push(AtomExpr::Prim(AtomPrim::Isotope(u16::try_from(n).ok()?)));
}
if inner.get(i) != Some(&b'H') {
return None;
}
i += 1;
parts.push(AtomExpr::Prim(AtomPrim::Element {
z: 1,
aromatic: None,
}));
if let Some(&c @ (b'+' | b'-')) = inner.get(i) {
i += 1;
let sign: i32 = if c == b'+' { 1 } else { -1 };
let mut n = 1i32;
while inner.get(i) == Some(&c) {
i += 1;
n += 1;
}
if n == 1 {
let s = i;
while i < inner.len() && inner[i].is_ascii_digit() {
i += 1;
}
if i > s {
n = std::str::from_utf8(&inner[s..i]).ok()?.parse().ok()?;
}
}
parts.push(AtomExpr::Prim(AtomPrim::Charge(n * sign)));
}
if inner.get(i) == Some(&b':') {
i += 1;
let s = i;
while i < inner.len() && inner[i].is_ascii_digit() {
i += 1;
}
if i == s {
return None;
}
let n: u32 = std::str::from_utf8(&inner[s..i]).ok()?.parse().ok()?;
parts.push(AtomExpr::Prim(AtomPrim::AtomMap(u16::try_from(n).ok()?)));
}
if i != inner.len() {
return None;
}
Some(if parts.len() == 1 {
parts.pop().expect("非空")
} else {
AtomExpr::And(parts)
})
}
pub(super) fn permutation_is_odd(from: &[u32], to: &[u32]) -> bool {
let mut cur = from.to_vec();
let mut swaps = 0usize;
for i in 0..to.len() {
if cur[i] == to[i] {
continue;
}
let Some(j) = (i + 1..cur.len()).find(|&j| cur[j] == to[i]) else {
return false;
};
cur.swap(i, j);
swaps += 1;
}
swaps % 2 == 1
}
pub(super) fn needs_h_compensation(
is_root: bool,
expr: &AtomExpr,
n_ring: usize,
unsaturated: bool,
) -> bool {
let h = bracket_h_count(expr);
if is_root && h == Some(1) {
return true;
}
h != Some(1) && n_ring == 1 && !unsaturated
}
pub(super) fn has_unsaturated_bond(topology: &MolBuilder, bonds: &[BondExpr], atom: u32) -> bool {
fn multiple(e: &BondExpr) -> bool {
match e {
BondExpr::Prim(p) => matches!(
p,
BondPrim::Double | BondPrim::Triple | BondPrim::Quadruple | BondPrim::Aromatic
),
BondExpr::And(parts) => parts.iter().any(multiple),
_ => false,
}
}
topology
.bonds()
.iter()
.enumerate()
.filter(|(_, b)| b.other_end(atom).is_some())
.any(|(i, _)| bonds.get(i).is_some_and(multiple))
}
pub(super) fn bracket_h_count(expr: &AtomExpr) -> Option<u32> {
match expr {
AtomExpr::Prim(AtomPrim::TotalHs(n)) => Some(*n),
AtomExpr::And(parts) => parts.iter().find_map(bracket_h_count),
_ => None,
}
}
pub(super) fn invert_chirality(expr: &mut AtomExpr) {
match expr {
AtomExpr::Prim(AtomPrim::Chirality(t)) => *t = t.inverted(),
AtomExpr::And(parts) => {
for p in parts {
invert_chirality(p);
}
}
_ => {}
}
}