1use crate::parser::bind_rules::{statement_block, Statement, StatementBlock};
6use crate::parser::common::{
7 compound_identifier, many_until_eof, map_err, using_list, ws, BindParserError,
8 CompoundIdentifier, Include, NodeType, NomSpan,
9};
10use nom::branch::alt;
11use nom::bytes::complete::{escaped, is_not, tag};
12use nom::character::complete::{char, one_of};
13use nom::combinator::{map, opt};
14use nom::sequence::delimited;
15use nom::{IResult, Parser};
16use std::collections::HashSet;
17
18#[derive(Debug, PartialEq)]
19pub struct Node<'a> {
20 pub name: String,
21 pub statements: StatementBlock<'a>,
22}
23
24#[derive(Debug, PartialEq)]
25pub struct Ast<'a> {
26 pub name: CompoundIdentifier,
27 pub using: Vec<Include>,
28 pub primary_node: Node<'a>,
29 pub additional_nodes: Vec<Node<'a>>,
30 pub optional_nodes: Vec<Node<'a>>,
31}
32
33impl<'a> TryFrom<&'a str> for Ast<'a> {
34 type Error = BindParserError;
35
36 fn try_from(input: &'a str) -> Result<Self, Self::Error> {
37 match composite(NomSpan::new(input)) {
38 Ok((_, ast)) => Ok(ast),
39 Err(nom::Err::Error(e)) => Err(e),
40 Err(nom::Err::Failure(e)) => Err(e),
41 Err(nom::Err::Incomplete(_)) => {
42 unreachable!("Parser should never generate Incomplete errors")
43 }
44 }
45 }
46}
47
48fn keyword_composite(input: NomSpan) -> IResult<NomSpan, NomSpan, BindParserError> {
49 ws(map_err(tag("composite"), BindParserError::CompositeKeyword)).parse(input)
50}
51
52fn keyword_node(input: NomSpan) -> IResult<NomSpan, NomSpan, BindParserError> {
53 ws(map_err(tag("node"), BindParserError::NodeKeyword)).parse(input)
54}
55
56fn keyword_primary(input: NomSpan) -> IResult<NomSpan, NomSpan, BindParserError> {
57 ws(map_err(tag("primary"), BindParserError::PrimaryOrOptionalKeyword)).parse(input)
58}
59
60fn keyword_optional(input: NomSpan) -> IResult<NomSpan, NomSpan, BindParserError> {
61 ws(map_err(tag("optional"), BindParserError::PrimaryOrOptionalKeyword)).parse(input)
62}
63
64fn node_type(input: NomSpan) -> IResult<NomSpan, NodeType, BindParserError> {
65 let (input, keyword) = opt(alt((keyword_optional, keyword_primary))).parse(input)?;
66 match keyword {
67 Some(kw) => match kw.fragment() {
68 &"optional" => Ok((input, NodeType::Optional)),
69 &"primary" => Ok((input, NodeType::Primary)),
70 &&_ => Err(nom::Err::Error(BindParserError::PrimaryOrOptionalKeyword(
71 kw.fragment().to_string(),
72 ))),
73 },
74 None => Ok((input, NodeType::Additional)),
75 }
76}
77
78fn composite_name(input: NomSpan) -> IResult<NomSpan, CompoundIdentifier, BindParserError> {
79 let terminator = ws(map_err(tag(";"), BindParserError::Semicolon));
80 delimited(keyword_composite, ws(compound_identifier), terminator).parse(input)
81}
82
83fn node_name(input: NomSpan) -> IResult<NomSpan, String, BindParserError> {
84 let escapable = escaped(is_not(r#"\""#), '\\', one_of(r#"\""#));
85 let literal = delimited(char('"'), escapable, char('"'));
86 map_err(map(literal, |s: NomSpan| s.fragment().to_string()), BindParserError::InvalidNodeName)
87 .parse(input)
88}
89
90fn node(input: NomSpan) -> IResult<NomSpan, (NodeType, String, Vec<Statement>), BindParserError> {
91 let (input, node_type) = node_type(input)?;
92 let (input, _node) = keyword_node(input)?;
93 let (input, node_name) = ws(node_name).parse(input)?;
94
95 let (input, statements) = statement_block(input)?;
96 return Ok((input, (node_type, node_name, statements)));
97}
98
99fn composite<'a>(input: NomSpan<'a>) -> IResult<NomSpan, Ast, BindParserError> {
100 let nodes =
101 |input: NomSpan<'a>| -> IResult<NomSpan, (Node<'a>, Vec<Node<'a>>, Vec<Node<'a>>), BindParserError> {
102 let (input, nodes) = many_until_eof(ws(node)).parse(input)?;
103 if nodes.is_empty() {
104 return Err(nom::Err::Error(BindParserError::NoNodes(input.to_string())));
105 }
106 let mut primary_node = None;
107 let mut additional_nodes = vec![];
108 let mut optional_nodes = vec![];
109 let mut node_names = HashSet::new();
110 for (node_type, name, statements) in nodes {
111 if node_names.contains(&name) {
112 return Err(nom::Err::Error(BindParserError::DuplicateNodeName(
113 input.to_string(),
114 )));
115 }
116 node_names.insert(name.clone());
117
118 match node_type {
119 NodeType::Primary => {
120 if primary_node.is_some() {
121 return Err(nom::Err::Error(BindParserError::OnePrimaryNode(
122 input.to_string(),
123 )));
124 }
125 primary_node = Some(Node { name: name, statements: statements });
126 }
127 NodeType::Additional => {
128 additional_nodes.push(Node { name: name, statements: statements });
129 },
130 NodeType::Optional => {
131 optional_nodes.push(Node { name: name, statements: statements });
132 },
133 }
134 }
135 if let Some(primary_node) = primary_node {
136 return Ok((input, (primary_node, additional_nodes, optional_nodes)));
137 }
138 return Err(nom::Err::Error(BindParserError::OnePrimaryNode(input.to_string())));
139 };
140 map(
141 (ws(composite_name), ws(using_list), nodes),
142 |(name, using, (primary_node, additional_nodes, optional_nodes))| Ast {
143 name,
144 using,
145 primary_node,
146 additional_nodes,
147 optional_nodes,
148 },
149 )
150 .parse(input)
151}
152
153#[cfg(test)]
154mod test {
155 use super::*;
156 use crate::make_identifier;
157 use crate::parser::bind_rules::{Condition, ConditionOp};
158 use crate::parser::common::test::check_result;
159 use crate::parser::common::{Span, Value};
160
161 mod composite_name {
162 use super::*;
163
164 #[test]
165 fn single_name() {
166 check_result(composite_name(NomSpan::new("composite a;")), "", make_identifier!["a"]);
167 }
168
169 #[test]
170 fn compound_name() {
171 check_result(
172 composite_name(NomSpan::new("composite a.b;")),
173 "",
174 make_identifier!["a", "b"],
175 );
176 }
177
178 #[test]
179 fn whitespace() {
180 check_result(
181 composite_name(NomSpan::new("composite \n\t a\n\t ;")),
182 "",
183 make_identifier!["a"],
184 );
185 }
186
187 #[test]
188 fn invalid() {
189 assert_eq!(
191 composite_name(NomSpan::new("composite ;")),
192 Err(nom::Err::Error(BindParserError::Identifier(";".to_string())))
193 );
194
195 assert_eq!(
197 composite_name(NomSpan::new("composite a")),
198 Err(nom::Err::Error(BindParserError::Semicolon("".to_string())))
199 );
200 }
201
202 #[test]
203 fn empty() {
204 assert_eq!(
206 composite_name(NomSpan::new("")),
207 Err(nom::Err::Error(BindParserError::CompositeKeyword("".to_string())))
208 );
209 }
210 }
211
212 mod composites {
213 use super::*;
214
215 #[test]
216 fn empty() {
217 assert_eq!(
219 composite(NomSpan::new("")),
220 Err(nom::Err::Error(BindParserError::CompositeKeyword("".to_string())))
221 );
222 }
223
224 #[test]
225 fn one_primary_node() {
226 check_result(
227 composite(NomSpan::new("composite a; primary node \"bananaquit\" { true; }")),
228 "",
229 Ast {
230 name: make_identifier!["a"],
231 using: vec![],
232 primary_node: Node {
233 name: "bananaquit".to_string(),
234 statements: vec![Statement::True {
235 span: Span { offset: 41, line: 1, fragment: "true;" },
236 }],
237 },
238 additional_nodes: vec![],
239 optional_nodes: vec![],
240 },
241 );
242 }
243
244 #[test]
245 fn one_primary_node_one_additional() {
246 check_result(
247 composite(NomSpan::new("composite a; primary node \"dipper\" { true; } node \"streamcreeper\" { false; }")),
248 "",
249 Ast {
250 name: make_identifier!["a"],
251 using: vec![],
252 primary_node: Node {
253 name: "dipper".to_string(),
254 statements: vec![Statement::True {
255 span: Span { offset: 37, line: 1, fragment: "true;" },
256 }]},
257 additional_nodes: vec![Node {
258 name: "streamcreeper".to_string(),
259 statements: vec![Statement::False {
260 span: Span { offset: 68, line: 1, fragment: "false;" },
261 }]}],
262 optional_nodes: vec![],
263 },
264 );
265 }
266
267 #[test]
268 fn one_primary_node_one_optional() {
269 check_result(
270 composite(NomSpan::new("composite a; primary node \"dipper\" { true; } optional node \"oilbird\" { x == 1; }")),
271 "",
272 Ast {
273 name: make_identifier!["a"],
274 using: vec![],
275 primary_node: Node {
276 name: "dipper".to_string(),
277 statements: vec![Statement::True {
278 span: Span { offset: 37, line: 1, fragment: "true;" },
279 }]},
280 additional_nodes: vec![],
281 optional_nodes: vec![Node {
282 name: "oilbird".to_string(),
283 statements:
284 vec![Statement::ConditionStatement {
285 span: Span { offset: 71, line: 1, fragment: "x == 1;" },
286 condition: Condition {
287 span: Span { offset: 71, line: 1, fragment: "x == 1" },
288 lhs: make_identifier!["x"],
289 op: ConditionOp::Equals,
290 rhs: Value::NumericLiteral(1),
291 },
292 }],
293 }],
294 },
295 );
296 }
297
298 #[test]
299 fn one_primary_node_one_additional_one_optional() {
300 check_result(
301 composite(NomSpan::new("composite a; primary node \"dipper\" { true; } node \"streamcreeper\" { false; } optional node \"oilbird\" { x == 1; }")),
302 "",
303 Ast {
304 name: make_identifier!["a"],
305 using: vec![],
306 primary_node: Node {
307 name: "dipper".to_string(),
308 statements: vec![Statement::True {
309 span: Span { offset: 37, line: 1, fragment: "true;" },
310 }]},
311 additional_nodes: vec![Node {
312 name: "streamcreeper".to_string(),
313 statements: vec![Statement::False {
314 span: Span { offset: 68, line: 1, fragment: "false;" },
315 }]}],
316 optional_nodes: vec![Node {
317 name: "oilbird".to_string(),
318 statements:
319 vec![Statement::ConditionStatement {
320 span: Span { offset: 103, line: 1, fragment: "x == 1;" },
321 condition: Condition {
322 span: Span { offset: 103, line: 1, fragment: "x == 1" },
323 lhs: make_identifier!["x"],
324 op: ConditionOp::Equals,
325 rhs: Value::NumericLiteral(1),
326 },
327 }],
328 }],
329 },
330 );
331 }
332
333 #[test]
334 fn one_primary_node_two_additional() {
335 check_result(
336 composite(NomSpan::new(
337 "composite a; primary node \"fireback\" { true; } node \"ovenbird\" { false; } node \"oilbird\" { x == 1; }",
338 )),
339 "",
340 Ast {
341 name: make_identifier!["a"],
342 using: vec![],
343 primary_node: Node {
344 name: "fireback".to_string(),
345 statements: vec![Statement::True {
346 span: Span { offset: 39, line: 1, fragment: "true;" },
347 }]
348 },
349 additional_nodes: vec![
350 Node {
351 name: "ovenbird".to_string(),
352 statements: vec![Statement::False {
353 span: Span { offset: 65, line: 1, fragment: "false;" },
354 }]
355 },
356 Node {
357 name: "oilbird".to_string(),
358 statements:
359 vec![Statement::ConditionStatement {
360 span: Span { offset: 91, line: 1, fragment: "x == 1;" },
361 condition: Condition {
362 span: Span { offset: 91, line: 1, fragment: "x == 1" },
363 lhs: make_identifier!["x"],
364 op: ConditionOp::Equals,
365 rhs: Value::NumericLiteral(1),
366 },
367 }],
368 }
369 ],
370 optional_nodes: vec![],
371 },
372 );
373 }
374
375 #[test]
376 fn using_list() {
377 check_result(
378 composite(NomSpan::new(
379 "composite a; using x.y as z; primary node \"oilbird\" { true; }",
380 )),
381 "",
382 Ast {
383 name: make_identifier!["a"],
384 using: vec![Include {
385 name: make_identifier!["x", "y"],
386 alias: Some("z".to_string()),
387 }],
388 primary_node: Node {
389 name: "oilbird".to_string(),
390 statements: vec![Statement::True {
391 span: Span { offset: 54, line: 1, fragment: "true;" },
392 }],
393 },
394 additional_nodes: vec![],
395 optional_nodes: vec![],
396 },
397 );
398 }
399
400 #[test]
401 fn no_nodes() {
402 assert_eq!(
403 composite(NomSpan::new("composite a; using x.y as z;")),
404 Err(nom::Err::Error(BindParserError::NoNodes("".to_string())))
405 );
406 assert_eq!(
407 composite(NomSpan::new("composite a;")),
408 Err(nom::Err::Error(BindParserError::NoNodes("".to_string())))
409 );
410 }
411
412 #[test]
413 fn not_one_primary_node() {
414 assert_eq!(
415 composite(NomSpan::new("composite a; node \"chiffchaff\"{ true; }")),
416 Err(nom::Err::Error(BindParserError::OnePrimaryNode("".to_string())))
417 );
418 assert_eq!(
419 composite(NomSpan::new(
420 "composite a; primary node \"chiffchaff\" { true; } primary node \"warbler\" { false; }"
421 )),
422 Err(nom::Err::Error(BindParserError::OnePrimaryNode("".to_string())))
423 );
424 }
425
426 #[test]
427 fn no_primary_node_name() {
428 assert_eq!(
429 composite(NomSpan::new("composite a; primary node { true; }")),
430 Err(nom::Err::Error(BindParserError::InvalidNodeName("{ true; }".to_string())))
431 );
432 assert_eq!(
433 composite(NomSpan::new("composite a; primary node chiffchaff { true; }")),
434 Err(nom::Err::Error(BindParserError::InvalidNodeName(
435 "chiffchaff { true; }".to_string()
436 )))
437 );
438
439 assert_eq!(
440 composite(NomSpan::new("composite a; primary node chiffchaff\" { true; }")),
441 Err(nom::Err::Error(BindParserError::InvalidNodeName(
442 "chiffchaff\" { true; }".to_string()
443 )))
444 );
445 }
446
447 #[test]
448 fn no_node_name() {
449 assert_eq!(
450 composite(NomSpan::new(
451 "composite a; primary node \"oilbird\" { true; } node { x == 1; }"
452 )),
453 Err(nom::Err::Error(BindParserError::InvalidNodeName("{ x == 1; }".to_string())))
454 );
455 assert_eq!(
456 composite(NomSpan::new(
457 "composite a; primary node \"oilbird\" { true; } node \"warbler { x == 1; }"
458 )),
459 Err(nom::Err::Error(BindParserError::InvalidNodeName("".to_string())))
460 );
461
462 assert_eq!(
463 composite(NomSpan::new(
464 "composite a; primary node \"oilbird\" { true; } node warbler { x == 1; }"
465 )),
466 Err(nom::Err::Error(BindParserError::InvalidNodeName(
467 "warbler { x == 1; }".to_string()
468 )))
469 );
470 }
471
472 #[test]
473 fn duplicate_node_names() {
474 assert_eq!(
475 composite(NomSpan::new(
476 "composite a; primary node \"bobolink\" { true; } node \"bobolink\" { x == 1; }"
477 )),
478 Err(nom::Err::Error(BindParserError::DuplicateNodeName(
479 "".to_string()
480 )))
481 );
482
483 assert_eq!(
484 composite(NomSpan::new(
485 "composite a; primary node \"bobolink\" { true; } node \"cowbird\" { x == 1; } node \"cowbird\" { false; }"
486 )),
487 Err(nom::Err::Error(BindParserError::DuplicateNodeName(
488 "".to_string()
489 )))
490 );
491 }
492 }
493}