purr 0.9.0

Primitives for reading and writing the SMILES language in Rust.
Documentation
use std::convert::TryInto;

use crate::feature::{ AtomKind, VirtualHydrogen, Number };
use super::{
    scanner::Scanner,
    read_symbol,
    read_charge,
    read_configuration,
    missing_character,
    Error
};

pub fn read_bracket(scanner: &mut Scanner) -> Result<Option<AtomKind>, Error> {
    if let Some('[') = scanner.peek() {
        scanner.pop();
    } else {
        return Ok(None);
    }

    let isotope = read_isotope(scanner)?;
    let symbol = read_symbol(scanner)?;
    let configuration = read_configuration(scanner)?;
    let hcount = read_hcount(scanner)?;
    let charge = read_charge(scanner)?;
    let map = read_map(scanner)?;

    match scanner.peek() {
        Some(']') => {
            scanner.pop();
            
            Ok(Some(AtomKind::Bracket {
                isotope, symbol, configuration, hcount, charge, map
            }))
        },
        None => Err(Error::EndOfLine),
        _ => Err(Error::Character(scanner.cursor()))
    }
}

fn read_hcount(
    scanner: &mut Scanner
) -> Result<Option<VirtualHydrogen>, Error> {
    match scanner.peek() {
        Some('H') => {
            scanner.pop();

            match scanner.peek() {
                Some('0'..='9') => match scanner.pop() {
                    Some('0') => Ok(Some(VirtualHydrogen::H0)),
                    Some('1') => Ok(Some(VirtualHydrogen::H1)),
                    Some('2') => Ok(Some(VirtualHydrogen::H2)),
                    Some('3') => Ok(Some(VirtualHydrogen::H3)),
                    Some('4') => Ok(Some(VirtualHydrogen::H4)),
                    Some('5') => Ok(Some(VirtualHydrogen::H5)),
                    Some('6') => Ok(Some(VirtualHydrogen::H6)),
                    Some('7') => Ok(Some(VirtualHydrogen::H7)),
                    Some('8') => Ok(Some(VirtualHydrogen::H8)),
                    Some('9') => Ok(Some(VirtualHydrogen::H9)),
                    _ => Ok(Some(VirtualHydrogen::H1))
                },
                _ => Ok(Some(VirtualHydrogen::H1))
            }
        },
        _ => Ok(None)
    }
}

fn read_isotope(scanner: &mut Scanner) -> Result<Option<Number>, Error> {
    let mut digits = String::new();

    for _ in 0..3 {
        match scanner.peek() {
            Some('0'..='9') => digits.push(*scanner.pop().expect("digit")),
            _ => break
        }
    }

    if digits.is_empty() {
        Ok(None)
    } else {
        Ok(Some(digits.try_into().expect("number")))
    }
}

fn read_map(scanner: &mut Scanner) -> Result<Option<Number>, Error> {
    match scanner.peek() {
        Some(':') => {
            scanner.pop();

            let mut digits = String::new();

            match scanner.pop() {
                Some(next) => if next.is_ascii_digit() {
                    digits.push(*next);
                } else {
                    return Err(Error::Character(scanner.cursor() - 1))
                },
                None => return Err(missing_character(scanner))
            }
 
            for _ in 0..2 {
                match scanner.peek() {
                    Some('0'..='9') =>
                        digits.push(*scanner.pop().expect("digit")),
                    _ => break
                }
            }

            Ok(Some(digits.try_into().expect("number")))
        },
        _ => Ok(None)
    }
}

#[cfg(test)]
mod tests {
    use std::convert::TryInto;
    use pretty_assertions::assert_eq;
    use crate::feature::{ BracketSymbol, Configuration, BracketAromatic, Charge };
    use super::*;

    #[test]
    fn overflow_map() {
        let mut scanner = Scanner::new("[*:1000]");

        assert_eq!(read_bracket(&mut scanner), Err(Error::Character(6)))
    }

    #[test]
    fn overflow_isotope() {
        let mut scanner = Scanner::new("[1000U]");

        assert_eq!(read_bracket(&mut scanner), Err(Error::Character(4)))
    }

    #[test]
    fn bracket_invalid() {
        let mut scanner = Scanner::new("[Q]");

        assert_eq!(read_bracket(&mut scanner), Err(Error::Character(1)))
    }

    #[test]
    fn no_close() {
        let mut scanner = Scanner::new("[C");

        assert_eq!(read_bracket(&mut scanner), Err(Error::EndOfLine))
    }

    #[test]
    fn colon_but_no_map() {
        let mut scanner = Scanner::new("[C:]");

        assert_eq!(read_bracket(&mut scanner), Err(Error::Character(3)))
    }

    #[test]
    fn colon_eol() {
        let mut scanner = Scanner::new("[C:");

        assert_eq!(read_bracket(&mut scanner), Err(Error::EndOfLine))
    }

    #[test]
    fn no_open() {
        let mut scanner = Scanner::new("?");

        assert_eq!(read_bracket(&mut scanner), Ok(None))
    }

    #[test]
    fn star() {
        let mut scanner = Scanner::new("[*]");

        assert_eq!(read_bracket(&mut scanner), Ok(Some(AtomKind::Bracket {
            isotope: None,
            symbol: BracketSymbol::Star,
            configuration: None,
            hcount: None,
            charge: None,
            map: None
        })))
    }

    #[test]
    fn star_isotope() {
        let mut scanner = Scanner::new("[999*]");

        assert_eq!(read_bracket(&mut scanner), Ok(Some(AtomKind::Bracket {
            isotope: Some(999.try_into().unwrap()),
            symbol: BracketSymbol::Star,
            configuration: None,
            hcount: None,
            charge: None,
            map: None
        })))
    }

    #[test]
    fn star_configuration() {
        let mut scanner = Scanner::new("[*@]");

        assert_eq!(read_bracket(&mut scanner), Ok(Some(AtomKind::Bracket {
            isotope: None,
            symbol: BracketSymbol::Star,
            configuration: Some(Configuration::TH1),
            hcount: None,
            charge: None,
            map: None
        })))
    }

    #[test]
    fn star_hcount() {
        let mut scanner = Scanner::new("[*H2]");

        assert_eq!(read_bracket(&mut scanner), Ok(Some(AtomKind::Bracket {
            isotope: None,
            symbol: BracketSymbol::Star,
            configuration: None,
            hcount: Some(VirtualHydrogen::H2),
            charge: None,
            map: None
        })))
    }

    #[test]
    fn star_charge() {
        let mut scanner = Scanner::new("[*+]");

        assert_eq!(read_bracket(&mut scanner), Ok(Some(AtomKind::Bracket {
            isotope: None,
            symbol: BracketSymbol::Star,
            configuration: None,
            hcount: None,
            charge: Some(Charge::One),
            map: None
        })))
    }

    #[test]
    fn star_map() {
        let mut scanner = Scanner::new("[*:999]");

        assert_eq!(read_bracket(&mut scanner), Ok(Some(AtomKind::Bracket {
            isotope: None,
            symbol: BracketSymbol::Star,
            configuration: None,
            hcount: None,
            charge: None,
            map: Some(999u16.try_into().unwrap())
        })))
    }

    #[test]
    fn bracket_aromatic_charge() {
        let mut scanner = Scanner::new("[s+]");

        assert_eq!(read_bracket(&mut scanner), Ok(Some(AtomKind::Bracket {
            isotope: None,
            symbol: BracketSymbol::Aromatic(BracketAromatic::S),
            configuration: None,
            hcount: None,
            charge: Some(Charge::One),
            map: None
        })))
    }
}