chem_parse/
parser.rs

1use crate::ast_types::Node;
2use crate::token_types::Tokens;
3
4/// Using an iterator (usually `LazyTokenStream`), parse tokens and return a result with the root node
5pub fn parse<'a, T: Iterator<Item = Result<Tokens, String>>>(
6    stream: T,
7) -> Result<Box<Node>, String> {
8    let mut stream = stream.peekable();
9    let mut paren_level = 0;
10    let mut current_stack = vec![Box::new(Node::ForumulaUnit(1, vec![]))];
11    loop {
12        match stream.next() {
13            Some(Err(val)) => return Err(val),
14            Some(Ok(Tokens::Number { data, meta })) => {
15                let fu = current_stack.pop().unwrap();
16                if let box Node::ForumulaUnit(_, vec) = fu {
17                    current_stack.push(Box::new(Node::ForumulaUnit(data, vec)));
18                } else {
19                    return Err("Invalid parent".to_owned());
20                }
21            }
22            Some(Ok(Tokens::Element { data, meta })) => {
23                let mut fu = *current_stack.pop().unwrap();
24
25                if let Node::ForumulaUnit(_, ref mut vec) = fu {
26                    if let Some(Ok(Tokens::Number { data: count, meta })) = stream.peek() {
27                        vec.push(Node::Element(*count, data));
28                        stream.next();
29                    } else {
30                        vec.push(Node::Element(1, data));
31                    }
32                    current_stack.push(Box::new(fu));
33                } else {
34                    return Err("Invalid Parent".to_owned());
35                }
36            }
37            Some(Ok(Tokens::Plus { meta })) => {
38                let fu = *current_stack.pop().unwrap();
39                let mut maybe_reactants = current_stack.pop().map(|val| *val);
40                match maybe_reactants {
41                    Some(Node::Reactants(ref mut vec)) => {
42                        vec.push(fu);
43                        current_stack.push(Box::new(maybe_reactants.unwrap()));
44                        current_stack.push(Box::new(Node::ForumulaUnit(1, vec![])));
45                    }
46                    Some(Node::Products(ref mut vec)) => {
47                        vec.push(fu);
48                        current_stack.push(Box::new(maybe_reactants.unwrap()));
49                        current_stack.push(Box::new(Node::ForumulaUnit(1, vec![])));
50                    }
51                    Some(_) => return Err("Invalid plus".to_owned()),
52                    None => {
53                        if let Node::ForumulaUnit(_, _) = fu {
54                            current_stack.push(Box::new(Node::Reactants(vec![fu])));
55                            current_stack.push(Box::new(Node::ForumulaUnit(1, vec![])));
56                        }
57                    }
58                }
59            }
60            Some(Ok(Tokens::Yields { meta })) => {
61                let fu = *current_stack.pop().unwrap();
62                let mut maybe_reactants = current_stack.pop();
63
64                match maybe_reactants {
65                    Some(box Node::Reactants(ref mut vec)) => {
66                        vec.push(fu);
67                        current_stack.push(maybe_reactants.unwrap());
68                        current_stack.push(Box::new(Node::Products(vec![])));
69                        current_stack.push(Box::new(Node::ForumulaUnit(1, vec![])));
70                    }
71                    Some(_) => return Err("Invalid yields location".to_owned()),
72                    None => {
73                        current_stack.push(Box::new(Node::Reactants(vec![fu])));
74                        current_stack.push(Box::new(Node::ForumulaUnit(1, vec![])));
75                    }
76                }
77            }
78            Some(Ok(Tokens::Paren {
79                data: super::token_types::ParenType::OPEN,
80                meta,
81            })) => {
82                paren_level += 1;
83                current_stack.push(Box::new(Node::Group(1, vec![])));
84            }
85            Some(Ok(Tokens::Paren {
86                data: super::token_types::ParenType::CLOSE,
87                meta,
88            })) => {
89                if paren_level == 0 {
90                    return Err("Invalid closing paren".to_owned());
91                };
92                paren_level -= 1;
93                let group = *current_stack.pop().unwrap();
94                let mut maybe_fu_or_group = current_stack.pop();
95
96                match maybe_fu_or_group {
97                    Some(box Node::Group(_, ref mut vec)) => {
98                        if let Some(Ok(Tokens::Number { data, meta: _ })) = stream.peek() {
99                            if let Node::Group(_, inner_vec) = group {
100                                vec.push(Node::Group(*data, inner_vec));
101                            }
102                        }
103                        current_stack.push(maybe_fu_or_group.unwrap());
104                    }
105                    Some(box Node::ForumulaUnit(_, ref mut vec)) => {
106                        vec.push(group);
107                        current_stack.push(maybe_fu_or_group.unwrap());
108                    }
109
110                    Some(_) => return Err("Invalid Parent".to_owned()),
111                    None => return Err("Stack Underflow in group".to_owned()),
112                }
113            }
114            None => {
115                let fu = *current_stack.pop().unwrap();
116                let mut products_or_none = current_stack.pop();
117                match products_or_none {
118                    Some(box Node::Products(ref mut vec)) => {
119                        let reactants = current_stack.pop().unwrap();
120                        vec.push(fu);
121                        current_stack.push(Box::new(Node::Equation(
122                            reactants,
123                            products_or_none.unwrap(),
124                        )));
125                    }
126                    Some(_) => return Err("Stack had unexpected value".to_owned()),
127                    None => {
128                        current_stack.push(Box::new(fu));
129                    }
130                }
131                break;
132            }
133        }
134    }
135    Ok(current_stack.pop().unwrap())
136}
137
138#[cfg(test)]
139mod tests {
140    use crate::token_types::TokenMetadata;
141
142    use super::*;
143    #[test]
144    fn can_parse_equation() {
145        let stream = vec![
146            Tokens::Number {
147                data: 2,
148                meta: TokenMetadata::new("2", 0),
149            },
150            Tokens::Element {
151                data: "Fe".to_owned(),
152                meta: TokenMetadata::new("Fe", 1),
153            },
154            Tokens::Plus {
155                meta: TokenMetadata::new("+", 3),
156            },
157            Tokens::Element {
158                data: "Na".to_owned(),
159                meta: TokenMetadata::new("Na", 4),
160            },
161            Tokens::Number {
162                data: 2,
163                meta: TokenMetadata::new("2", 6),
164            },
165            Tokens::Element {
166                data: "F".to_owned(),
167                meta: TokenMetadata::new("F", 7),
168            },
169            Tokens::Number {
170                data: 3,
171                meta: TokenMetadata::new("3", 8),
172            },
173            Tokens::Yields {
174                meta: TokenMetadata::new("->", 9),
175            },
176            Tokens::Number {
177                data: 2,
178                meta: TokenMetadata::new("2", 11),
179            },
180            Tokens::Element {
181                data: "Fe".to_owned(),
182                meta: TokenMetadata::new("Fe", 12),
183            },
184            Tokens::Element {
185                data: "Na".to_owned(),
186                meta: TokenMetadata::new("Na", 14),
187            },
188            Tokens::Plus {
189                meta: TokenMetadata::new("+", 16),
190            },
191            Tokens::Element {
192                data: "F".to_owned(),
193                meta: TokenMetadata::new("F", 17),
194            },
195            Tokens::Number {
196                data: 3,
197                meta: TokenMetadata::new("3", 18),
198            },
199        ];
200
201        let exp = Node::Equation(
202            Box::new(Node::Reactants(vec![
203                Node::ForumulaUnit(2, vec![Node::Element(1, "Fe".to_owned())]),
204                Node::ForumulaUnit(
205                    1,
206                    vec![
207                        Node::Element(2, "Na".to_owned()),
208                        Node::Element(3, "F".to_owned()),
209                    ],
210                ),
211            ])),
212            Box::new(Node::Products(vec![
213                Node::ForumulaUnit(
214                    2,
215                    vec![
216                        Node::Element(1, "Fe".to_owned()),
217                        Node::Element(1, "Na".to_owned()),
218                    ],
219                ),
220                Node::ForumulaUnit(1, vec![Node::Element(3, "F".to_owned())]),
221            ])),
222        );
223
224        let res = parse(stream.into_iter().map(|box_tokens| Ok(box_tokens)));
225
226        assert!(res.is_ok());
227
228        assert_eq!(exp, *res.unwrap());
229    }
230    #[test]
231    fn can_parse_formula_unit() {
232        let stream = vec![
233            Tokens::Number {
234                data: 2,
235                meta: TokenMetadata::new("2", 0),
236            },
237            Tokens::Element {
238                data: "Fe".to_owned(),
239                meta: TokenMetadata::new("Fe", 1),
240            },
241            Tokens::Element {
242                data: "C".to_owned(),
243                meta: TokenMetadata::new("C", 3),
244            },
245            Tokens::Element {
246                data: "O".to_owned(),
247                meta: TokenMetadata::new("O", 4),
248            },
249            Tokens::Number {
250                data: 3,
251                meta: TokenMetadata::new("3", 5),
252            },
253        ];
254
255        let exp = Node::ForumulaUnit(
256            2,
257            vec![
258                Node::Element(1, "Fe".to_owned()),
259                Node::Element(1, "C".to_owned()),
260                Node::Element(3, "O".to_owned()),
261            ],
262        );
263
264        let res = parse(stream.into_iter().map(|box_tokens| Ok(box_tokens)));
265
266        assert!(res.is_ok());
267
268        assert_eq!(exp, *res.unwrap());
269    }
270}