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 tag(b"*"),
30 alt((
32 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 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 alt((
164 tag(b"se"),
165 tag(b"as"),
166 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 }
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 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 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#[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 Anticlockwise,
473 Clockwise,
475 Tetrahedral(u8),
477 Allenal(u8),
479 SquarePlanar(u8),
481 TrigonalBipyramidal(u8),
483 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 #[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 #[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 #[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 #[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}