smiles_parser/
lib.rs

1#[cfg(feature = "graph")]
2pub mod graph;
3
4use nom::branch::alt;
5use nom::bytes::complete::tag;
6use nom::bytes::complete::take_while_m_n;
7use nom::character::complete::char;
8use nom::character::is_digit;
9use nom::combinator::map;
10use nom::combinator::map_res;
11use nom::combinator::opt;
12use nom::multi::many0;
13use nom::sequence::delimited;
14use nom::sequence::preceded;
15use nom::sequence::tuple;
16use nom::IResult;
17use ptable::Element;
18
19#[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Copy, Clone, Hash)]
20pub enum Symbol {
21    ElementSymbol(Element),
22    AromaticSymbol(Element),
23    Unknown,
24}
25
26fn raw_symbol(input: &[u8]) -> IResult<&[u8], &[u8]> {
27    alt((
28        // Unknown
29        tag(b"*"),
30        // ElementSymbol
31        alt((
32            // Two letter symbols have to appear before one letter symbols or they won't be recognized
33            alt((
34                tag(b"Ac"),
35                tag(b"Ag"),
36                tag(b"Al"),
37                tag(b"Am"),
38                tag(b"Ar"),
39                tag(b"As"),
40                tag(b"At"),
41                tag(b"Au"),
42                tag(b"Ba"),
43                tag(b"Be"),
44                tag(b"Bh"),
45                tag(b"Bi"),
46                tag(b"Bk"),
47                tag(b"Br"),
48                tag(b"Ca"),
49                tag(b"Cd"),
50                tag(b"Ce"),
51                tag(b"Cf"),
52                tag(b"Cl"),
53                tag(b"Cm"),
54            )),
55            alt((
56                tag(b"Cn"),
57                tag(b"Co"),
58                tag(b"Cr"),
59                tag(b"Cs"),
60                tag(b"Cu"),
61                tag(b"Db"),
62                tag(b"Ds"),
63                tag(b"Dy"),
64                tag(b"Er"),
65                tag(b"Es"),
66                tag(b"Eu"),
67                tag(b"Fe"),
68                tag(b"Fl"),
69                tag(b"Fm"),
70                tag(b"Fr"),
71                tag(b"Ga"),
72                tag(b"Gd"),
73                tag(b"Ge"),
74                tag(b"He"),
75                tag(b"Hf"),
76            )),
77            alt((
78                tag(b"Hg"),
79                tag(b"Ho"),
80                tag(b"Hs"),
81                tag(b"In"),
82                tag(b"Ir"),
83                tag(b"Kr"),
84                tag(b"La"),
85                tag(b"Li"),
86                tag(b"Lr"),
87                tag(b"Lu"),
88                tag(b"Lv"),
89                tag(b"Mc"),
90                tag(b"Md"),
91                tag(b"Mg"),
92                tag(b"Mn"),
93                tag(b"Mo"),
94                tag(b"Mt"),
95                tag(b"Na"),
96                tag(b"Nb"),
97                tag(b"Nd"),
98            )),
99            alt((
100                tag(b"Ne"),
101                tag(b"Nh"),
102                tag(b"Ni"),
103                tag(b"No"),
104                tag(b"Np"),
105                tag(b"Og"),
106                tag(b"Os"),
107                tag(b"Pa"),
108                tag(b"Pb"),
109                tag(b"Pd"),
110                tag(b"Pm"),
111                tag(b"Po"),
112                tag(b"Pr"),
113                tag(b"Pt"),
114                tag(b"Pu"),
115                tag(b"Ra"),
116                tag(b"Rb"),
117                tag(b"Re"),
118                tag(b"Rf"),
119                tag(b"Rg"),
120            )),
121            alt((
122                tag(b"Rh"),
123                tag(b"Rn"),
124                tag(b"Ru"),
125                tag(b"Sb"),
126                tag(b"Sc"),
127                tag(b"Se"),
128                tag(b"Sg"),
129                tag(b"Si"),
130                tag(b"Sm"),
131                tag(b"Sn"),
132                tag(b"Sr"),
133                tag(b"Ta"),
134                tag(b"Tb"),
135                tag(b"Tc"),
136                tag(b"Te"),
137                tag(b"Th"),
138                tag(b"Ti"),
139                tag(b"Tl"),
140                tag(b"Tm"),
141                tag(b"Ts"),
142            )),
143            alt((tag(b"Xe"), tag(b"Yb"), tag(b"Zn"), tag(b"Zr"))),
144            // Single letter
145            alt((
146                tag(b"B"),
147                tag(b"C"),
148                tag(b"F"),
149                tag(b"H"),
150                tag(b"I"),
151                tag(b"K"),
152                tag(b"N"),
153                tag(b"O"),
154                tag(b"P"),
155                tag(b"S"),
156                tag(b"U"),
157                tag(b"V"),
158                tag(b"W"),
159                tag(b"Y"),
160            )),
161        )),
162        // AromaticSymbol
163        alt((
164            tag(b"se"),
165            tag(b"as"),
166            //
167            tag(b"b"),
168            tag(b"c"),
169            tag(b"n"),
170            tag(b"o"),
171            tag(b"p"),
172            tag(b"s"),
173        )),
174    ))(input)
175}
176
177fn symbol(input: &[u8]) -> IResult<&[u8], Symbol> {
178    map_res(raw_symbol, |sym: &[u8]| match sym {
179        b"*" => Ok(Symbol::Unknown),
180        b"se" | b"as" | b"b" | b"c" | b"n" | b"o" | b"p" | b"s" => Ok(match sym {
181            b"se" => Symbol::AromaticSymbol(Element::Selenium),
182            b"as" => Symbol::AromaticSymbol(Element::Arsenic),
183            b"b" => Symbol::AromaticSymbol(Element::Boron),
184            b"c" => Symbol::AromaticSymbol(Element::Carbon),
185            b"n" => Symbol::AromaticSymbol(Element::Nitrogen),
186            b"o" => Symbol::AromaticSymbol(Element::Oxygen),
187            b"p" => Symbol::AromaticSymbol(Element::Phosphorus),
188            b"s" => Symbol::AromaticSymbol(Element::Sulfur),
189            _ => unreachable!(),
190        }),
191        other => {
192            let other_str = std::str::from_utf8(other).map_err(|_| "Unparsable UTF-8")?;
193            let try_element = Element::from_symbol(other_str);
194            try_element
195                .ok_or("Unknown element symbol")
196                .map(|element| Symbol::ElementSymbol(element))
197        }
198    })(input)
199}
200
201#[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Copy, Clone, Hash)]
202pub struct BracketAtom {
203    pub isotope: Option<u16>,
204    pub symbol: Symbol,
205    pub chiral: Option<Chirality>,
206    pub hcount: u8,
207    pub charge: i8,
208    // TODO: class?
209}
210
211fn charge(input: &[u8]) -> IResult<&[u8], i8> {
212    map(
213        many0(map(
214            tuple((
215                alt((tag("+"), tag("-"))),
216                opt(map_res(
217                    map_res(take_while_m_n(1, 2, is_digit), |s: &[u8]| {
218                        std::str::from_utf8(s)
219                    }),
220                    |s: &str| s.parse::<u8>(),
221                )),
222            )),
223            |(tag, count): (&[u8], Option<u8>)| {
224                let count = count.unwrap_or(1) as i8;
225                if tag[0] == b'+' {
226                    count
227                } else {
228                    -count
229                }
230            },
231        )),
232        |v| v.into_iter().fold(0, |acc, x| acc + x),
233    )(input)
234}
235
236fn hcount(input: &[u8]) -> IResult<&[u8], u8> {
237    map(
238        opt(map(
239            tuple((
240                tag("H"),
241                opt(map_res(
242                    map_res(take_while_m_n(1, 1, is_digit), |s: &[u8]| {
243                        std::str::from_utf8(s)
244                    }),
245                    |s: &str| s.parse::<u8>(),
246                )),
247            )),
248            |(_, count): (&[u8], Option<u8>)| count.unwrap_or(1),
249        )),
250        |res| res.unwrap_or(0),
251    )(input)
252}
253
254fn isotope_opt(input: &[u8]) -> IResult<&[u8], Option<u16>> {
255    opt(map_res(
256        map_res(take_while_m_n(1, 3, is_digit), |s: &[u8]| {
257            std::str::from_utf8(s)
258        }),
259        |s: &str| s.parse::<u16>(),
260    ))(input)
261}
262
263fn bracket_atom(input: &[u8]) -> IResult<&[u8], BracketAtom> {
264    delimited(
265        char('['),
266        map(
267            tuple((isotope_opt, symbol, opt(chirality), hcount, charge)),
268            |(isotope, sym, chiral, hcount, charge): (
269                Option<u16>,
270                Symbol,
271                Option<Chirality>,
272                u8,
273                i8,
274            )| BracketAtom {
275                isotope,
276                symbol: sym,
277                chiral,
278                hcount,
279                charge,
280            },
281        ),
282        char(']'),
283    )(input)
284}
285
286#[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Copy, Clone, Hash)]
287pub struct AliphaticOrganicAtom {
288    pub element: Element,
289}
290
291fn raw_aliphatic_organic(input: &[u8]) -> IResult<&[u8], &[u8]> {
292    alt((
293        // Two letter symbols have to appear before one letter symbols or they won't be recognized
294        tag(b"Cl"),
295        tag(b"Br"),
296        tag(b"B"),
297        tag(b"C"),
298        tag(b"N"),
299        tag(b"O"),
300        tag(b"S"),
301        tag(b"P"),
302        tag(b"F"),
303        tag(b"I"),
304    ))(input)
305}
306
307fn aliphatic_organic_atom(input: &[u8]) -> IResult<&[u8], AliphaticOrganicAtom> {
308    map_res(raw_aliphatic_organic, |sym: &[u8]| {
309        let other_str = std::str::from_utf8(sym).map_err(|_| "Unparsable UTF-8")?;
310        let try_element = Element::from_symbol(other_str);
311        try_element
312            .ok_or("Unknown element symbol")
313            .map(|element| AliphaticOrganicAtom { element })
314    })(input)
315}
316
317#[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Copy, Clone, Hash)]
318pub enum Atom {
319    Bracket(BracketAtom),
320    AliphaticOrganic(AliphaticOrganicAtom),
321    // AromaticOrganic not supported
322    Unknown,
323}
324
325fn atom(input: &[u8]) -> IResult<&[u8], Atom> {
326    alt((
327        map(tag(b"*"), |_| Atom::Unknown),
328        map(bracket_atom, |inner| Atom::Bracket(inner)),
329        map(aliphatic_organic_atom, |inner| {
330            Atom::AliphaticOrganic(inner)
331        }),
332    ))(input)
333}
334
335#[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Clone, Hash)]
336pub struct BranchedAtom {
337    pub atom: Atom,
338    pub ring_bonds: Vec<RingBond>,
339    pub branches: Vec<Branch>,
340}
341
342fn branched_atom(input: &[u8]) -> IResult<&[u8], BranchedAtom> {
343    map(
344        tuple((atom, many0(ring_bond), many0(branch))),
345        |(atom, ring_bonds, branches)| BranchedAtom {
346            atom,
347            ring_bonds,
348            branches,
349        },
350    )(input)
351}
352
353#[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Copy, Clone, Hash)]
354pub enum Bond {
355    Single,
356    Double,
357    Triple,
358    Quadruple,
359    Aromatic,
360    Up,
361    Down,
362}
363
364fn raw_bond(input: &[u8]) -> IResult<&[u8], &[u8]> {
365    alt((
366        tag(b"-"),
367        tag(b"="),
368        tag(b"#"),
369        tag(b"$"),
370        tag(b":"),
371        tag(b"/"),
372        tag(b"\\"),
373    ))(input)
374}
375
376fn bond(input: &[u8]) -> IResult<&[u8], Bond> {
377    map(raw_bond, |bnd: &[u8]| match bnd {
378        b"-" => Bond::Single,
379        b"=" => Bond::Double,
380        b"#" => Bond::Triple,
381        b"$" => Bond::Quadruple,
382        b":" => Bond::Aromatic,
383        b"/" => Bond::Up,
384        b"\\" => Bond::Down,
385        _ => unreachable!(),
386    })(input)
387}
388
389#[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Copy, Clone, Hash)]
390pub struct RingBond {
391    pub bond: Option<Bond>,
392    pub ring_number: u8,
393}
394
395fn bond_digits(input: &[u8]) -> IResult<&[u8], u8> {
396    map_res(
397        map_res(
398            alt((
399                take_while_m_n(1, 1, is_digit),
400                preceded(tag(b"%"), take_while_m_n(2, 2, is_digit)),
401            )),
402            |s: &[u8]| std::str::from_utf8(s),
403        ),
404        |s: &str| s.parse::<u8>(),
405    )(input)
406}
407
408fn ring_bond(input: &[u8]) -> IResult<&[u8], RingBond> {
409    map(tuple((opt(bond), bond_digits)), |(bond, ring_number)| {
410        RingBond { bond, ring_number }
411    })(input)
412}
413
414#[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Clone, Hash)]
415pub struct Chain {
416    pub chain: Option<Box<Chain>>,
417    pub bond_or_dot: Option<BondOrDot>,
418    pub branched_atom: BranchedAtom,
419}
420
421pub fn chain(input: &[u8]) -> IResult<&[u8], Chain> {
422    map(
423        tuple((branched_atom, opt(bond_or_dot), opt(chain))),
424        |(branched_atom, bond_or_dot, chain)| Chain {
425            chain: chain.map(|n| Box::new(n)),
426            bond_or_dot,
427            branched_atom,
428        },
429    )(input)
430}
431
432// Symbol for non-connected parts of compound
433#[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Copy, Clone, Hash)]
434pub struct Dot;
435
436fn dot(input: &[u8]) -> IResult<&[u8], Dot> {
437    map(tag(b"."), |_| Dot)(input)
438}
439
440#[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Copy, Clone, Hash)]
441pub enum BondOrDot {
442    Bond(Bond),
443    Dot(Dot),
444}
445
446fn bond_or_dot(input: &[u8]) -> IResult<&[u8], BondOrDot> {
447    alt((
448        map(bond, |inner| BondOrDot::Bond(inner)),
449        map(dot, |inner| BondOrDot::Dot(inner)),
450    ))(input)
451}
452
453#[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Clone, Hash)]
454pub struct Branch {
455    pub bond_or_dot: Option<BondOrDot>,
456    pub chain: Chain,
457}
458
459fn branch(input: &[u8]) -> IResult<&[u8], Branch> {
460    delimited(
461        char('('),
462        map(tuple((opt(bond_or_dot), chain)), |(bond_or_dot, chain)| {
463            Branch { bond_or_dot, chain }
464        }),
465        char(')'),
466    )(input)
467}
468
469#[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Copy, Clone, Hash)]
470pub enum Chirality {
471    /// `@`
472    Anticlockwise,
473    /// `@@`
474    Clockwise,
475    /// `@TH1`, `@TH2`
476    Tetrahedral(u8),
477    /// `@AL1`, `@AL2`
478    Allenal(u8),
479    /// `@SP1`, `@SP2`, `@SP3`
480    SquarePlanar(u8),
481    /// `@TB1` ... `@TB20`
482    TrigonalBipyramidal(u8),
483    /// `@OH1` ... `@OH30`
484    Octahedral(u8),
485}
486
487fn raw_chirality(input: &[u8]) -> IResult<&[u8], &[u8]> {
488    alt((
489        alt((tag(b"@TH1"), tag(b"@TH2"))),
490        alt((tag(b"@AL1"), tag(b"@AL2"))),
491        alt((tag(b"@SP1"), tag(b"@SP2"), tag(b"@SP3"))),
492        alt((
493            tag(b"@TB10"),
494            tag(b"@TB11"),
495            tag(b"@TB12"),
496            tag(b"@TB13"),
497            tag(b"@TB14"),
498            tag(b"@TB15"),
499            tag(b"@TB16"),
500            tag(b"@TB17"),
501            tag(b"@TB18"),
502            tag(b"@TB19"),
503            tag(b"@TB20"),
504            tag(b"@TB1"),
505            tag(b"@TB2"),
506            tag(b"@TB3"),
507            tag(b"@TB4"),
508            tag(b"@TB5"),
509            tag(b"@TB6"),
510            tag(b"@TB7"),
511            tag(b"@TB8"),
512            tag(b"@TB9"),
513        )),
514        alt((
515            tag(b"@OH10"),
516            tag(b"@OH11"),
517            tag(b"@OH12"),
518            tag(b"@OH13"),
519            tag(b"@OH14"),
520            tag(b"@OH15"),
521            tag(b"@OH16"),
522            tag(b"@OH17"),
523            tag(b"@OH18"),
524            tag(b"@OH19"),
525            tag(b"@OH20"),
526            tag(b"@OH1"),
527            tag(b"@OH2"),
528            tag(b"@OH3"),
529            tag(b"@OH4"),
530            tag(b"@OH5"),
531            tag(b"@OH6"),
532            tag(b"@OH7"),
533            tag(b"@OH8"),
534            tag(b"@OH9"),
535        )),
536        alt((
537            tag(b"@OH21"),
538            tag(b"@OH22"),
539            tag(b"@OH23"),
540            tag(b"@OH24"),
541            tag(b"@OH25"),
542            tag(b"@OH26"),
543            tag(b"@OH27"),
544            tag(b"@OH28"),
545            tag(b"@OH29"),
546            tag(b"@OH30"),
547        )),
548        tag(b"@@"),
549        tag(b"@"),
550    ))(input)
551}
552
553fn chirality(input: &[u8]) -> IResult<&[u8], Chirality> {
554    map_res(raw_chirality, |sym: &[u8]| {
555        let other_str = std::str::from_utf8(sym).map_err(|_| "Unparsable UTF-8")?;
556
557        let chirality: Result<Chirality, &'static str> = match other_str {
558            "@" => Ok(Chirality::Anticlockwise),
559            "@@" => Ok(Chirality::Clockwise),
560            "@TH1" | "@TH2" => Ok(Chirality::Tetrahedral(other_str[3..].parse().unwrap())),
561            "@AL1" | "@AL2" => Ok(Chirality::Allenal(other_str[3..].parse().unwrap())),
562            "@SP1" | "@SP2" | "@SP3" => {
563                Ok(Chirality::SquarePlanar(other_str[3..].parse().unwrap()))
564            }
565            "@TB1" | "@TB2" | "@TB3" | "@TB4" | "@TB5" | "@TB6" | "@TB7" | "@TB8" | "@TB9"
566            | "@TB10" | "@TB11" | "@TB12" | "@TB13" | "@TB14" | "@TB15" | "@TB16" | "@TB17"
567            | "@TB18" | "@TB19" | "@TB20" => Ok(Chirality::TrigonalBipyramidal(
568                other_str[3..].parse().unwrap(),
569            )),
570            "@OH1" | "@OH2" | "@OH3" | "@OH4" | "@OH5" | "@OH6" | "@OH7" | "@OH8" | "@OH9"
571            | "@OH10" | "@OH11" | "@OH12" | "@OH13" | "@OH14" | "@OH15" | "@OH16" | "@OH17"
572            | "@OH18" | "@OH19" | "@OH20" | "@OH21" | "@OH22" | "@OH23" | "@OH24" | "@OH25"
573            | "@OH26" | "@OH27" | "@OH28" | "@OH29" | "@OH30" => {
574                Ok(Chirality::Octahedral(other_str[3..].parse().unwrap()))
575            }
576            _ => unreachable!(),
577        };
578
579        chirality
580    })(input)
581}
582
583#[cfg(test)]
584mod tests {
585    use super::*;
586
587    #[test]
588    fn symbol_cases() {
589        assert_eq!(Ok(("".as_bytes(), Symbol::Unknown)), symbol(b"*"));
590        assert_eq!(
591            Ok(("".as_bytes(), Symbol::ElementSymbol(Element::Helium))),
592            symbol(b"He")
593        );
594    }
595
596    #[test]
597    fn isotope_opt_cases() {
598        assert_eq!(Ok(("".as_bytes(), Some(0u16))), isotope_opt(b"0"));
599        assert_eq!(Ok(("".as_bytes(), Some(125u16))), isotope_opt(b"125"));
600        assert_eq!(Ok(("X".as_bytes(), Some(125u16))), isotope_opt(b"125X"));
601        assert_eq!(Ok(("7".as_bytes(), Some(125u16))), isotope_opt(b"1257"));
602    }
603
604    #[test]
605    fn bracket_atom_cases() {
606        assert_eq!(
607            Ok((
608                "".as_bytes(),
609                BracketAtom {
610                    isotope: Some(16),
611                    symbol: Symbol::ElementSymbol(Element::Carbon),
612                    chiral: None,
613                    hcount: 0,
614                    charge: -2,
615                }
616            )),
617            bracket_atom(b"[16C--]")
618        );
619        assert_eq!(
620            Ok((
621                "CC".as_bytes(),
622                BracketAtom {
623                    isotope: Some(16),
624                    symbol: Symbol::ElementSymbol(Element::Carbon),
625                    chiral: None,
626                    hcount: 1,
627                    charge: 3,
628                }
629            )),
630            bracket_atom(b"[16CH+3]CC")
631        );
632    }
633
634    #[test]
635    fn ring_bond_digit_cases() {
636        assert_eq!(Ok(("".as_bytes(), 0u8)), bond_digits(b"0"));
637        assert_eq!(Ok(("".as_bytes(), 12u8)), bond_digits(b"%12"));
638        assert_eq!(Ok(("5".as_bytes(), 12u8)), bond_digits(b"%125"));
639    }
640
641    #[test]
642    fn chirality_cases() {
643        assert_eq!(
644            Ok(("".as_bytes(), Chirality::Anticlockwise)),
645            chirality(b"@")
646        );
647        assert_eq!(
648            Ok(("".as_bytes(), Chirality::Tetrahedral(1))),
649            chirality(b"@TH1")
650        );
651        assert_eq!(
652            Ok(("".as_bytes(), Chirality::Allenal(2))),
653            chirality(b"@AL2")
654        );
655        assert_eq!(
656            Ok(("".as_bytes(), Chirality::SquarePlanar(3))),
657            chirality(b"@SP3")
658        );
659        assert_eq!(
660            Ok(("".as_bytes(), Chirality::TrigonalBipyramidal(1))),
661            chirality(b"@TB1")
662        );
663        assert_eq!(
664            Ok(("".as_bytes(), Chirality::TrigonalBipyramidal(11))),
665            chirality(b"@TB11")
666        );
667        assert_eq!(
668            Ok(("".as_bytes(), Chirality::Octahedral(1))),
669            chirality(b"@OH1")
670        );
671        assert_eq!(
672            Ok(("".as_bytes(), Chirality::Octahedral(11))),
673            chirality(b"@OH11")
674        );
675    }
676
677    #[test]
678    fn atom_cases() {
679        assert_eq!(
680            Ok((
681                "".as_bytes(),
682                Atom::Bracket(BracketAtom {
683                    isotope: Some(16),
684                    symbol: Symbol::ElementSymbol(Element::Carbon),
685                    chiral: None,
686                    hcount: 0,
687                    charge: 0,
688                })
689            )),
690            atom(b"[16C]")
691        );
692        assert_eq!(Ok(("".as_bytes(), Atom::Unknown)), atom(b"*"));
693    }
694
695    #[test]
696    fn chain_ethane() {
697        assert_eq!(
698            Ok((
699                "".as_bytes(),
700                Chain {
701                    chain: Some(Box::new(Chain {
702                        chain: None,
703                        bond_or_dot: None,
704                        branched_atom: BranchedAtom {
705                            atom: Atom::AliphaticOrganic(AliphaticOrganicAtom {
706                                element: Element::Carbon
707                            }),
708                            ring_bonds: vec![],
709                            branches: vec![]
710                        }
711                    })),
712                    bond_or_dot: None,
713                    branched_atom: BranchedAtom {
714                        atom: Atom::AliphaticOrganic(AliphaticOrganicAtom {
715                            element: Element::Carbon
716                        }),
717                        ring_bonds: vec![],
718                        branches: vec![]
719                    }
720                }
721            )),
722            chain(b"CC")
723        );
724    }
725
726    #[test]
727    fn chain_fluoromethane() {
728        assert_eq!(
729            Ok((
730                "".as_bytes(),
731                Chain {
732                    chain: Some(Box::new(Chain {
733                        chain: None,
734                        bond_or_dot: None,
735                        branched_atom: BranchedAtom {
736                            atom: Atom::AliphaticOrganic(AliphaticOrganicAtom {
737                                element: Element::Fluorine
738                            }),
739                            ring_bonds: vec![],
740                            branches: vec![]
741                        }
742                    })),
743                    bond_or_dot: None,
744                    branched_atom: BranchedAtom {
745                        atom: Atom::AliphaticOrganic(AliphaticOrganicAtom {
746                            element: Element::Carbon
747                        }),
748                        ring_bonds: vec![],
749                        branches: vec![]
750                    }
751                }
752            )),
753            chain(b"CF")
754        );
755    }
756
757    #[test]
758    fn chain_ethene() {
759        assert_eq!(
760            Ok((
761                "".as_bytes(),
762                Chain {
763                    chain: Some(Box::new(Chain {
764                        chain: None,
765                        bond_or_dot: None,
766                        branched_atom: BranchedAtom {
767                            atom: Atom::AliphaticOrganic(AliphaticOrganicAtom {
768                                element: Element::Carbon
769                            }),
770                            ring_bonds: vec![],
771                            branches: vec![]
772                        }
773                    })),
774                    bond_or_dot: Some(BondOrDot::Bond(Bond::Double)),
775                    branched_atom: BranchedAtom {
776                        atom: Atom::AliphaticOrganic(AliphaticOrganicAtom {
777                            element: Element::Carbon
778                        }),
779                        ring_bonds: vec![],
780                        branches: vec![]
781                    }
782                }
783            )),
784            chain(b"C=C")
785        );
786    }
787
788    // 1-Oxaspiro[2.5]octane
789    #[test]
790    fn ring_and_branch_chain() {
791        let chain = chain(b"C1CCC2(CC1)CO2");
792        assert!(chain.is_ok());
793        assert!(chain.unwrap().0.is_empty());
794    }
795
796    // Isobutane
797    #[test]
798    fn branch_isobutane() {
799        let chain = chain(b"CC(C)C");
800        assert!(chain.is_ok());
801        assert!(chain.unwrap().0.is_empty());
802    }
803
804    // Neopentane
805    #[test]
806    fn branch_neopentane() {
807        let chain = chain(b"CC(C)(C)C");
808        assert!(chain.is_ok());
809        assert!(chain.unwrap().0.is_empty());
810    }
811
812    // Cyclopropyloxirane
813    #[test]
814    fn rings_chain() {
815        let chain = chain(b"C1CC1C2CO2");
816        println!("{:?}", chain);
817        assert!(chain.is_ok());
818        assert!(chain.unwrap().0.is_empty());
819    }
820
821    #[test]
822    fn chain_trigonal_bipyramidal() {
823        assert_eq!(
824            Ok((
825                "".as_bytes(),
826                Chain {
827                    chain: Some(Box::new(Chain {
828                        chain: Some(Box::new(Chain {
829                            chain: None,
830                            bond_or_dot: None,
831                            branched_atom: BranchedAtom {
832                                atom: Atom::AliphaticOrganic(AliphaticOrganicAtom {
833                                    element: Element::Nitrogen
834                                }),
835                                ring_bonds: vec![],
836                                branches: vec![]
837                            }
838                        })),
839                        bond_or_dot: None,
840                        branched_atom: BranchedAtom {
841                            atom: Atom::Bracket(BracketAtom {
842                                isotope: None,
843                                symbol: Symbol::ElementSymbol(Element::Arsenic),
844                                chiral: Some(Chirality::TrigonalBipyramidal(15)),
845                                hcount: 0,
846                                charge: 0,
847                            }),
848                            ring_bonds: vec![],
849                            branches: vec![
850                                Branch {
851                                    bond_or_dot: None,
852                                    chain: Chain {
853                                        chain: None,
854                                        bond_or_dot: None,
855                                        branched_atom: BranchedAtom {
856                                            atom: Atom::AliphaticOrganic(AliphaticOrganicAtom {
857                                                element: Element::Chlorine
858                                            }),
859                                            ring_bonds: vec![],
860                                            branches: vec![]
861                                        }
862                                    },
863                                },
864                                Branch {
865                                    bond_or_dot: None,
866                                    chain: Chain {
867                                        chain: None,
868                                        bond_or_dot: None,
869                                        branched_atom: BranchedAtom {
870                                            atom: Atom::AliphaticOrganic(AliphaticOrganicAtom {
871                                                element: Element::Sulfur
872                                            }),
873                                            ring_bonds: vec![],
874                                            branches: vec![]
875                                        }
876                                    },
877                                },
878                                Branch {
879                                    bond_or_dot: None,
880                                    chain: Chain {
881                                        chain: None,
882                                        bond_or_dot: None,
883                                        branched_atom: BranchedAtom {
884                                            atom: Atom::AliphaticOrganic(AliphaticOrganicAtom {
885                                                element: Element::Bromine
886                                            }),
887                                            ring_bonds: vec![],
888                                            branches: vec![]
889                                        }
890                                    },
891                                },
892                            ]
893                        }
894                    })),
895                    bond_or_dot: None,
896                    branched_atom: BranchedAtom {
897                        atom: Atom::AliphaticOrganic(AliphaticOrganicAtom {
898                            element: Element::Fluorine
899                        }),
900                        ring_bonds: vec![],
901                        branches: vec![]
902                    }
903                }
904            )),
905            chain(b"F[As@TB15](Cl)(S)(Br)N")
906        );
907    }
908
909    #[test]
910    fn chain_sodium_chloride() {
911        assert_eq!(
912            Ok((
913                "".as_bytes(),
914                Chain {
915                    chain: Some(Box::new(Chain {
916                        chain: None,
917                        bond_or_dot: None,
918                        branched_atom: BranchedAtom {
919                            atom: Atom::Bracket(BracketAtom {
920                                isotope: None,
921                                symbol: Symbol::ElementSymbol(Element::Chlorine),
922                                chiral: None,
923                                hcount: 0,
924                                charge: -1,
925                            }),
926                            ring_bonds: vec![],
927                            branches: vec![]
928                        }
929                    })),
930                    bond_or_dot: Some(BondOrDot::Dot(Dot)),
931                    branched_atom: BranchedAtom {
932                        atom: Atom::Bracket(BracketAtom {
933                            isotope: None,
934                            symbol: Symbol::ElementSymbol(Element::Sodium),
935                            chiral: None,
936                            hcount: 0,
937                            charge: 1,
938                        }),
939                        ring_bonds: vec![],
940                        branches: vec![]
941                    }
942                }
943            )),
944            chain(b"[Na+].[Cl-]")
945        );
946    }
947}