use super::expr::{BondExpr, BondPrim};
use crate::error::{ParseError, ParseErrorKind as K, Result};
pub fn parse_bond_expr(src: &[u8]) -> Result<BondExpr> {
let mut p = BondParser { src, pos: 0 };
let e = p.low_and()?;
if p.pos != src.len() {
return Err(ParseError::new(
K::BadBracketAtom("键表达式里有无法解析的残余"),
p.pos,
src,
));
}
Ok(e)
}
struct BondParser<'a> {
src: &'a [u8],
pos: usize,
}
impl BondParser<'_> {
fn peek(&self) -> Option<u8> {
self.src.get(self.pos).copied()
}
fn eat(&mut self, b: u8) -> bool {
if self.peek() == Some(b) {
self.pos += 1;
true
} else {
false
}
}
fn low_and(&mut self) -> Result<BondExpr> {
let mut parts = vec![self.or()?];
while self.eat(b';') {
parts.push(self.or()?);
}
Ok(flatten(BondExpr::And, parts))
}
fn or(&mut self) -> Result<BondExpr> {
let mut parts = vec![self.high_and()?];
while self.eat(b',') {
parts.push(self.high_and()?);
}
Ok(flatten(BondExpr::Or, parts))
}
fn high_and(&mut self) -> Result<BondExpr> {
let mut parts = vec![self.unary()?];
loop {
if self.eat(b'&') {
parts.push(self.unary()?);
continue;
}
match self.peek() {
Some(b) if starts_bond_expr(b) => parts.push(self.unary()?),
_ => break,
}
}
Ok(flatten(BondExpr::And, parts))
}
fn unary(&mut self) -> Result<BondExpr> {
if self.eat(b'!') {
return Ok(BondExpr::Not(Box::new(self.unary()?)));
}
Ok(BondExpr::Prim(self.primitive()?))
}
fn primitive(&mut self) -> Result<BondPrim> {
let Some(b) = self.peek() else {
return Err(ParseError::new(
K::BadBracketAtom("键表达式意外结束"),
self.pos,
self.src,
));
};
self.pos += 1;
Ok(match b {
b'~' => BondPrim::Any,
b'-' => {
if self.eat(b'>') {
BondPrim::Dative
} else {
BondPrim::Single
}
}
b'<' => {
if self.eat(b'-') {
BondPrim::DativeReversed
} else {
return Err(ParseError::new(
K::UnexpectedChar('<'),
self.pos - 1,
self.src,
));
}
}
b'=' => BondPrim::Double,
b'#' => BondPrim::Triple,
b'$' => BondPrim::Quadruple,
b':' => BondPrim::Aromatic,
b'@' => BondPrim::InRing,
b'/' => BondPrim::UpRight,
b'\\' => BondPrim::DownRight,
_ => {
return Err(ParseError::new(
K::UnexpectedChar(char::from(b)),
self.pos - 1,
self.src,
))
}
})
}
}
fn flatten(wrap: fn(Vec<BondExpr>) -> BondExpr, mut parts: Vec<BondExpr>) -> BondExpr {
if parts.len() == 1 {
parts.pop().expect("非空")
} else {
wrap(parts)
}
}
#[must_use]
pub fn starts_bond_expr(b: u8) -> bool {
matches!(
b,
b'~' | b'-' | b'=' | b'#' | b'$' | b':' | b'@' | b'/' | b'\\' | b'!' | b'<'
)
}
#[cfg(test)]
mod tests {
use super::*;
fn p(s: &str) -> BondExpr {
parse_bond_expr(s.as_bytes()).unwrap_or_else(|e| panic!("{s}: {}", e.render()))
}
fn prim(x: BondPrim) -> BondExpr {
BondExpr::Prim(x)
}
#[test]
fn single_primitives() {
assert_eq!(p("-"), prim(BondPrim::Single));
assert_eq!(p("="), prim(BondPrim::Double));
assert_eq!(p("#"), prim(BondPrim::Triple));
assert_eq!(p("$"), prim(BondPrim::Quadruple));
assert_eq!(p(":"), prim(BondPrim::Aromatic));
assert_eq!(p("~"), prim(BondPrim::Any));
assert_eq!(p("@"), prim(BondPrim::InRing));
assert_eq!(p("/"), prim(BondPrim::UpRight));
assert_eq!(p("\\"), prim(BondPrim::DownRight));
}
#[test]
fn dative_bonds() {
assert_eq!(p("->"), prim(BondPrim::Dative));
assert_eq!(p("<-"), prim(BondPrim::DativeReversed));
assert!(parse_bond_expr(b"<").is_err());
}
#[test]
fn operator_precedence() {
assert_eq!(
p("-,=;@"),
BondExpr::And(vec![
BondExpr::Or(vec![prim(BondPrim::Single), prim(BondPrim::Double)]),
prim(BondPrim::InRing),
])
);
assert_eq!(
p("-,=&@"),
BondExpr::Or(vec![
prim(BondPrim::Single),
BondExpr::And(vec![prim(BondPrim::Double), prim(BondPrim::InRing)]),
])
);
assert_eq!(
p("!@"),
BondExpr::Not(Box::new(prim(BondPrim::InRing))),
"非环键"
);
}
#[test]
fn juxtaposition_is_conjunction() {
assert_eq!(
p("=!@"),
BondExpr::And(vec![
prim(BondPrim::Double),
BondExpr::Not(Box::new(prim(BondPrim::InRing)))
]),
"双键且非环键"
);
assert_eq!(p("-@"), p("-&@"));
assert_eq!(
p("=#"),
BondExpr::And(vec![prim(BondPrim::Double), prim(BondPrim::Triple)]),
"自相矛盾但语法合法"
);
}
}