use std::collections::{BTreeMap, BTreeSet};
use super::QueryMol;
use crate::error::{ParseError, ParseErrorKind as K, Result};
#[derive(Debug, Clone)]
pub struct Reaction {
pub reactants: Vec<QueryMol>,
pub agents: Vec<QueryMol>,
pub products: Vec<QueryMol>,
}
impl Reaction {
#[must_use]
pub fn map_numbers(&self) -> (BTreeSet<u16>, BTreeSet<u16>) {
let collect = |ts: &[QueryMol]| -> BTreeSet<u16> {
ts.iter()
.flat_map(|t| t.atoms.iter())
.filter_map(super::map_number)
.collect()
};
(collect(&self.reactants), collect(&self.products))
}
#[must_use]
pub fn duplicate_map_numbers(&self) -> Vec<u16> {
let dups = |ts: &[QueryMol]| -> Vec<u16> {
let mut count: BTreeMap<u16, usize> = BTreeMap::new();
for t in ts {
for a in &t.atoms {
if let Some(n) = super::map_number(a) {
*count.entry(n).or_default() += 1;
}
}
}
count
.into_iter()
.filter(|&(_, c)| c > 1)
.map(|(n, _)| n)
.collect()
};
let mut out = dups(&self.reactants);
out.extend(dups(&self.products));
out.sort_unstable();
out.dedup();
out
}
}
pub fn parse_reaction(input: &str) -> Result<Reaction> {
let src = input.as_bytes();
let sections = split_sections(src)?;
let (reactants, agents, products) = match sections.len() {
2 => (sections[0].clone(), Vec::new(), sections[1].clone()),
3 => (
sections[0].clone(),
sections[1].clone(),
sections[2].clone(),
),
n => {
return Err(ParseError::new(
K::BadBracketAtom(if n < 2 {
"反应缺少 `>>`"
} else {
"反应的 `>` 分段超过三段"
}),
0,
src,
))
}
};
let build = |ranges: &[(usize, usize)]| -> Result<Vec<QueryMol>> {
ranges
.iter()
.map(|&(lo, hi)| {
let text = std::str::from_utf8(&src[lo..hi])
.map_err(|_| ParseError::new(K::UnexpectedEnd, lo, src))?;
super::mol::parse(text).map_err(|e| ParseError::new(e.kind, lo + e.pos, src))
})
.collect()
};
Ok(Reaction {
reactants: build(&reactants)?,
agents: build(&agents)?,
products: build(&products)?,
})
}
fn split_sections(src: &[u8]) -> Result<Vec<Vec<(usize, usize)>>> {
let mut sections: Vec<Vec<(usize, usize)>> = Vec::new();
let mut current: Vec<(usize, usize)> = Vec::new();
let (mut brackets, mut parens) = (0i32, 0i32);
let mut start = 0usize;
for i in 0..=src.len() {
let b = src.get(i).copied();
match b {
Some(b'[') => brackets += 1,
Some(b']') => brackets -= 1,
Some(b'(') if brackets == 0 => parens += 1,
Some(b')') if brackets == 0 => parens -= 1,
_ => {}
}
if brackets != 0 || parens != 0 {
continue;
}
let is_sep = matches!(b, Some(b'>')) && (i == 0 || src[i - 1] != b'-');
let is_dot = matches!(b, Some(b'.'));
let is_end = b.is_none();
if is_sep || is_dot || is_end {
if start < i {
current.push(strip_group_parens(src, start, i));
}
start = i + 1;
if is_sep || is_end {
sections.push(std::mem::take(&mut current));
}
}
}
if brackets != 0 {
return Err(ParseError::new(
K::BadBracketAtom("方括号未闭合"),
src.len(),
src,
));
}
if parens != 0 {
return Err(ParseError::new(K::UnbalancedParen, src.len(), src));
}
Ok(sections)
}
fn strip_group_parens(src: &[u8], lo: usize, hi: usize) -> (usize, usize) {
if src.get(lo) != Some(&b'(') || src.get(hi - 1) != Some(&b')') {
return (lo, hi);
}
let (mut depth, mut brackets) = (0i32, 0i32);
for (offset, &c) in src[lo..hi].iter().enumerate() {
match c {
b'[' => brackets += 1,
b']' => brackets -= 1,
b'(' if brackets == 0 => depth += 1,
b')' if brackets == 0 => {
depth -= 1;
if depth == 0 && lo + offset != hi - 1 {
return (lo, hi);
}
}
_ => {}
}
}
(lo + 1, hi - 1)
}
#[cfg(test)]
mod tests {
use super::*;
fn r(s: &str) -> Reaction {
parse_reaction(s).unwrap_or_else(|e| panic!("{s}:\n{}", e.render()))
}
fn shape(s: &str) -> (usize, usize, usize) {
let x = r(s);
(x.reactants.len(), x.agents.len(), x.products.len())
}
#[test]
fn sections_and_templates() {
assert_eq!(shape("[C:1][OH:2]>>[C:1][Cl:2]"), (1, 0, 1));
assert_eq!(shape("[C:1].[N:2]>>[C:1][N:2]"), (2, 0, 1), "`.` 分模板");
assert_eq!(shape("[C:1]>[Pd]>[C:1]Cl"), (1, 1, 1), "中间是试剂");
assert_eq!(shape("[N:1]->[Cu:2]>>[N:1].[Cu:2]"), (1, 0, 2));
}
#[test]
fn grouping_parens_make_one_template() {
let x = r("([C:1].[N:2])>>[C:1][N:2]");
assert_eq!(x.reactants.len(), 1, "括号把两个片段捆成一个模板");
assert_eq!(x.reactants[0].num_atoms(), 2);
let x = r("[C:1](=[O:2])[OH]>>[C:1](=[O:2])Cl");
assert_eq!(x.reactants.len(), 1);
assert_eq!(x.reactants[0].num_atoms(), 3);
}
#[test]
fn dative_arrow_is_not_a_separator() {
let x = r("[N:1]->[Cu:2]>>[N:1].[Cu:2]");
assert_eq!(x.reactants.len(), 1);
assert_eq!(x.reactants[0].num_atoms(), 2, "N 和 Cu 在同一个模板里");
assert_eq!(x.reactants[0].num_bonds(), 1);
assert_eq!(x.products.len(), 2, "产物侧的 `.` 才是分隔符");
}
#[test]
fn separators_inside_brackets_are_ignored() {
let x = r("[$(C.C):1]>>[C:1]");
assert_eq!(x.reactants.len(), 1, "递归 SMARTS 里的 `.` 不分模板");
assert_eq!(x.reactants[0].num_atoms(), 1);
}
#[test]
fn map_numbers() {
let x = r("[C:1][OH:2]>>[C:1][Cl:2]");
let (lhs, rhs) = x.map_numbers();
assert_eq!(lhs, [1, 2].into_iter().collect());
assert_eq!(rhs, [1, 2].into_iter().collect());
assert!(x.duplicate_map_numbers().is_empty());
let x = r("[C:1][OH]>>[C:1]Cl");
let (lhs, rhs) = x.map_numbers();
assert_eq!(lhs, [1].into_iter().collect());
assert_eq!(rhs, [1].into_iter().collect());
let x = r("[C:1][C:1]>>[C:1]");
assert_eq!(x.duplicate_map_numbers(), vec![1]);
}
#[test]
fn wrong_section_count_is_an_error() {
assert!(parse_reaction("CC").is_err(), "没有 `>>`");
assert!(parse_reaction("A>B>C>D").is_err(), "四段");
}
}