1use crate::parser::bind_rules::{Statement, StatementBlock, statement_block};
6use crate::parser::common::{
7 BindParserError, CompoundIdentifier, Include, NomSpan, ParentType, compound_identifier,
8 many_until_eof, map_err, using_list, ws,
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 Parent<'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_parent: Parent<'a>,
29 pub additional_parents: Vec<Parent<'a>>,
30 pub optional_parents: Vec<Parent<'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_parent(input: NomSpan) -> IResult<NomSpan, NomSpan, BindParserError> {
53 let (input, kw) =
54 ws(map_err(alt((tag("node"), tag("parent"))), BindParserError::ParentKeyword))
55 .parse(input)?;
56 if kw.fragment() == &"node" {
57 return Err(nom::Err::Error(BindParserError::DeprecatedNodeKeyword(
58 kw.fragment().to_string(),
59 )));
60 }
61 Ok((input, kw))
62}
63
64fn keyword_primary(input: NomSpan) -> IResult<NomSpan, NomSpan, BindParserError> {
65 ws(map_err(tag("primary"), BindParserError::PrimaryOrOptionalKeyword)).parse(input)
66}
67
68fn keyword_optional(input: NomSpan) -> IResult<NomSpan, NomSpan, BindParserError> {
69 ws(map_err(tag("optional"), BindParserError::PrimaryOrOptionalKeyword)).parse(input)
70}
71
72fn parent_type(input: NomSpan) -> IResult<NomSpan, ParentType, BindParserError> {
73 let (input, keyword) = opt(alt((keyword_optional, keyword_primary))).parse(input)?;
74 match keyword {
75 Some(kw) => match kw.fragment() {
76 &"optional" => Ok((input, ParentType::Optional)),
77 &"primary" => Ok((input, ParentType::Primary)),
78 &&_ => Err(nom::Err::Error(BindParserError::PrimaryOrOptionalKeyword(
79 kw.fragment().to_string(),
80 ))),
81 },
82 None => Ok((input, ParentType::Additional)),
83 }
84}
85
86fn composite_name(input: NomSpan) -> IResult<NomSpan, CompoundIdentifier, BindParserError> {
87 let terminator = ws(map_err(tag(";"), BindParserError::Semicolon));
88 delimited(keyword_composite, ws(compound_identifier), terminator).parse(input)
89}
90
91fn parent_name(input: NomSpan) -> IResult<NomSpan, String, BindParserError> {
92 let escapable = escaped(is_not(r#"\""#), '\\', one_of(r#"\""#));
93 let literal = delimited(char('"'), escapable, char('"'));
94 map_err(map(literal, |s: NomSpan| s.fragment().to_string()), BindParserError::InvalidParentName)
95 .parse(input)
96}
97
98fn parent(
99 input: NomSpan,
100) -> IResult<NomSpan, (ParentType, String, Vec<Statement>), BindParserError> {
101 let (input, parent_type) = parent_type(input)?;
102 let (input, _parent) = keyword_parent(input)?;
103 let (input, parent_name) = ws(parent_name).parse(input)?;
104
105 let (input, statements) = statement_block(input)?;
106 return Ok((input, (parent_type, parent_name, statements)));
107}
108
109fn composite<'a>(input: NomSpan<'a>) -> IResult<NomSpan, Ast, BindParserError> {
110 let parents = |input: NomSpan<'a>| -> IResult<
111 NomSpan,
112 (Parent<'a>, Vec<Parent<'a>>, Vec<Parent<'a>>),
113 BindParserError,
114 > {
115 let (input, parents) = many_until_eof(ws(parent)).parse(input)?;
116 if parents.is_empty() {
117 return Err(nom::Err::Error(BindParserError::NoParents(input.to_string())));
118 }
119 let mut primary_parent = None;
120 let mut additional_parents = vec![];
121 let mut optional_parents = vec![];
122 let mut parent_names = HashSet::new();
123 for (parent_type, name, statements) in parents {
124 if parent_names.contains(&name) {
125 return Err(nom::Err::Error(BindParserError::DuplicateParentName(
126 input.to_string(),
127 )));
128 }
129 parent_names.insert(name.clone());
130
131 match parent_type {
132 ParentType::Primary => {
133 if primary_parent.is_some() {
134 return Err(nom::Err::Error(BindParserError::OnePrimaryParent(
135 input.to_string(),
136 )));
137 }
138 primary_parent = Some(Parent { name: name, statements: statements });
139 }
140 ParentType::Additional => {
141 additional_parents.push(Parent { name: name, statements: statements });
142 }
143 ParentType::Optional => {
144 optional_parents.push(Parent { name: name, statements: statements });
145 }
146 }
147 }
148 if let Some(primary_parent) = primary_parent {
149 return Ok((input, (primary_parent, additional_parents, optional_parents)));
150 }
151 return Err(nom::Err::Error(BindParserError::OnePrimaryParent(input.to_string())));
152 };
153 map(
154 (ws(composite_name), ws(using_list), parents),
155 |(name, using, (primary_parent, additional_parents, optional_parents))| Ast {
156 name,
157 using,
158 primary_parent,
159 additional_parents,
160 optional_parents,
161 },
162 )
163 .parse(input)
164}
165
166#[cfg(test)]
167mod test {
168 use super::*;
169 use crate::make_identifier;
170 use crate::parser::bind_rules::{Condition, ConditionOp};
171 use crate::parser::common::test::check_result;
172 use crate::parser::common::{Span, Value};
173
174 mod composite_name {
175 use super::*;
176
177 #[test]
178 fn single_name() {
179 check_result(composite_name(NomSpan::new("composite a;")), "", make_identifier!["a"]);
180 }
181
182 #[test]
183 fn compound_name() {
184 check_result(
185 composite_name(NomSpan::new("composite a.b;")),
186 "",
187 make_identifier!["a", "b"],
188 );
189 }
190
191 #[test]
192 fn whitespace() {
193 check_result(
194 composite_name(NomSpan::new("composite \n\t a\n\t ;")),
195 "",
196 make_identifier!["a"],
197 );
198 }
199
200 #[test]
201 fn invalid() {
202 assert_eq!(
204 composite_name(NomSpan::new("composite ;")),
205 Err(nom::Err::Error(BindParserError::Identifier(";".to_string())))
206 );
207
208 assert_eq!(
210 composite_name(NomSpan::new("composite a")),
211 Err(nom::Err::Error(BindParserError::Semicolon("".to_string())))
212 );
213 }
214
215 #[test]
216 fn empty() {
217 assert_eq!(
219 composite_name(NomSpan::new("")),
220 Err(nom::Err::Error(BindParserError::CompositeKeyword("".to_string())))
221 );
222 }
223 }
224
225 mod composites {
226 use super::*;
227
228 #[test]
229 fn empty() {
230 assert_eq!(
232 composite(NomSpan::new("")),
233 Err(nom::Err::Error(BindParserError::CompositeKeyword("".to_string())))
234 );
235 }
236
237 #[test]
238 fn node_keyword_error() {
239 assert_eq!(
240 composite(NomSpan::new("composite a; primary node \"bananaquit\" { true; }")),
241 Err(nom::Err::Error(BindParserError::DeprecatedNodeKeyword("node".to_string())))
242 );
243 }
244
245 #[test]
246 fn one_primary_parent() {
247 check_result(
248 composite(NomSpan::new("composite a; primary parent \"bananaquit\" { true; }")),
249 "",
250 Ast {
251 name: make_identifier!["a"],
252 using: vec![],
253 primary_parent: Parent {
254 name: "bananaquit".to_string(),
255 statements: vec![Statement::True {
256 span: Span { offset: 43, line: 1, fragment: "true;" },
257 }],
258 },
259 additional_parents: vec![],
260 optional_parents: vec![],
261 },
262 );
263 }
264
265 #[test]
266 fn one_primary_parent_keyword() {
267 check_result(
268 composite(NomSpan::new("composite a; primary parent \"pdev\" { true; }")),
269 "",
270 Ast {
271 name: make_identifier!["a"],
272 using: vec![],
273 primary_parent: Parent {
274 name: "pdev".to_string(),
275 statements: vec![Statement::True {
276 span: Span { offset: 37, line: 1, fragment: "true;" },
277 }],
278 },
279 additional_parents: vec![],
280 optional_parents: vec![],
281 },
282 );
283 }
284
285 #[test]
286 fn one_primary_parent_one_additional() {
287 check_result(
288 composite(NomSpan::new(
289 "composite a; primary parent \"dipper\" { true; } parent \"streamcreeper\" { false; }",
290 )),
291 "",
292 Ast {
293 name: make_identifier!["a"],
294 using: vec![],
295 primary_parent: Parent {
296 name: "dipper".to_string(),
297 statements: vec![Statement::True {
298 span: Span { offset: 39, line: 1, fragment: "true;" },
299 }],
300 },
301 additional_parents: vec![Parent {
302 name: "streamcreeper".to_string(),
303 statements: vec![Statement::False {
304 span: Span { offset: 72, line: 1, fragment: "false;" },
305 }],
306 }],
307 optional_parents: vec![],
308 },
309 );
310 }
311
312 #[test]
313 fn one_primary_parent_one_optional() {
314 check_result(
315 composite(NomSpan::new(
316 "composite a; primary parent \"dipper\" { true; } optional parent \"oilbird\" { x == 1; }",
317 )),
318 "",
319 Ast {
320 name: make_identifier!["a"],
321 using: vec![],
322 primary_parent: Parent {
323 name: "dipper".to_string(),
324 statements: vec![Statement::True {
325 span: Span { offset: 39, line: 1, fragment: "true;" },
326 }],
327 },
328 additional_parents: vec![],
329 optional_parents: vec![Parent {
330 name: "oilbird".to_string(),
331 statements: vec![Statement::ConditionStatement {
332 span: Span { offset: 75, line: 1, fragment: "x == 1;" },
333 condition: Condition {
334 span: Span { offset: 75, line: 1, fragment: "x == 1" },
335 lhs: make_identifier!["x"],
336 op: ConditionOp::Equals,
337 rhs: Value::NumericLiteral(1),
338 },
339 }],
340 }],
341 },
342 );
343 }
344
345 #[test]
346 fn one_primary_parent_one_additional_one_optional() {
347 check_result(
348 composite(NomSpan::new(
349 "composite a; primary parent \"dipper\" { true; } parent \"streamcreeper\" { false; } optional parent \"oilbird\" { x == 1; }",
350 )),
351 "",
352 Ast {
353 name: make_identifier!["a"],
354 using: vec![],
355 primary_parent: Parent {
356 name: "dipper".to_string(),
357 statements: vec![Statement::True {
358 span: Span { offset: 39, line: 1, fragment: "true;" },
359 }],
360 },
361 additional_parents: vec![Parent {
362 name: "streamcreeper".to_string(),
363 statements: vec![Statement::False {
364 span: Span { offset: 72, line: 1, fragment: "false;" },
365 }],
366 }],
367 optional_parents: vec![Parent {
368 name: "oilbird".to_string(),
369 statements: vec![Statement::ConditionStatement {
370 span: Span { offset: 109, line: 1, fragment: "x == 1;" },
371 condition: Condition {
372 span: Span { offset: 109, line: 1, fragment: "x == 1" },
373 lhs: make_identifier!["x"],
374 op: ConditionOp::Equals,
375 rhs: Value::NumericLiteral(1),
376 },
377 }],
378 }],
379 },
380 );
381 }
382
383 #[test]
384 fn one_primary_parent_two_additional() {
385 check_result(
386 composite(NomSpan::new(
387 "composite a; primary parent \"fireback\" { true; } parent \"ovenbird\" { false; } parent \"oilbird\" { x == 1; }",
388 )),
389 "",
390 Ast {
391 name: make_identifier!["a"],
392 using: vec![],
393 primary_parent: Parent {
394 name: "fireback".to_string(),
395 statements: vec![Statement::True {
396 span: Span { offset: 41, line: 1, fragment: "true;" },
397 }],
398 },
399 additional_parents: vec![
400 Parent {
401 name: "ovenbird".to_string(),
402 statements: vec![Statement::False {
403 span: Span { offset: 69, line: 1, fragment: "false;" },
404 }],
405 },
406 Parent {
407 name: "oilbird".to_string(),
408 statements: vec![Statement::ConditionStatement {
409 span: Span { offset: 97, line: 1, fragment: "x == 1;" },
410 condition: Condition {
411 span: Span { offset: 97, line: 1, fragment: "x == 1" },
412 lhs: make_identifier!["x"],
413 op: ConditionOp::Equals,
414 rhs: Value::NumericLiteral(1),
415 },
416 }],
417 },
418 ],
419 optional_parents: vec![],
420 },
421 );
422 }
423
424 #[test]
425 fn using_list() {
426 check_result(
427 composite(NomSpan::new(
428 "composite a; using x.y as z; primary parent \"oilbird\" { true; }",
429 )),
430 "",
431 Ast {
432 name: make_identifier!["a"],
433 using: vec![Include {
434 name: make_identifier!["x", "y"],
435 alias: Some("z".to_string()),
436 }],
437 primary_parent: Parent {
438 name: "oilbird".to_string(),
439 statements: vec![Statement::True {
440 span: Span { offset: 56, line: 1, fragment: "true;" },
441 }],
442 },
443 additional_parents: vec![],
444 optional_parents: vec![],
445 },
446 );
447 }
448
449 #[test]
450 fn no_nodes() {
451 assert_eq!(
452 composite(NomSpan::new("composite a; using x.y as z;")),
453 Err(nom::Err::Error(BindParserError::NoParents("".to_string())))
454 );
455 assert_eq!(
456 composite(NomSpan::new("composite a;")),
457 Err(nom::Err::Error(BindParserError::NoParents("".to_string())))
458 );
459 }
460
461 #[test]
462 fn not_one_primary_parent() {
463 assert_eq!(
464 composite(NomSpan::new("composite a; parent \"chiffchaff\"{ true; }")),
465 Err(nom::Err::Error(BindParserError::OnePrimaryParent("".to_string())))
466 );
467 assert_eq!(
468 composite(NomSpan::new(
469 "composite a; primary parent \"chiffchaff\" { true; } primary parent \"warbler\" { false; }"
470 )),
471 Err(nom::Err::Error(BindParserError::OnePrimaryParent("".to_string())))
472 );
473 }
474
475 #[test]
476 fn no_primary_parent_name() {
477 assert_eq!(
478 composite(NomSpan::new("composite a; primary parent { true; }")),
479 Err(nom::Err::Error(BindParserError::InvalidParentName("{ true; }".to_string())))
480 );
481 assert_eq!(
482 composite(NomSpan::new("composite a; primary parent chiffchaff { true; }")),
483 Err(nom::Err::Error(BindParserError::InvalidParentName(
484 "chiffchaff { true; }".to_string()
485 )))
486 );
487
488 assert_eq!(
489 composite(NomSpan::new("composite a; primary parent chiffchaff\" { true; }")),
490 Err(nom::Err::Error(BindParserError::InvalidParentName(
491 "chiffchaff\" { true; }".to_string()
492 )))
493 );
494 }
495
496 #[test]
497 fn no_parent_name() {
498 assert_eq!(
499 composite(NomSpan::new(
500 "composite a; primary parent \"oilbird\" { true; } parent { x == 1; }"
501 )),
502 Err(nom::Err::Error(BindParserError::InvalidParentName("{ x == 1; }".to_string())))
503 );
504 assert_eq!(
505 composite(NomSpan::new(
506 "composite a; primary parent \"oilbird\" { true; } parent \"warbler { x == 1; }"
507 )),
508 Err(nom::Err::Error(BindParserError::InvalidParentName("".to_string())))
509 );
510
511 assert_eq!(
512 composite(NomSpan::new(
513 "composite a; primary parent \"oilbird\" { true; } parent warbler { x == 1; }"
514 )),
515 Err(nom::Err::Error(BindParserError::InvalidParentName(
516 "warbler { x == 1; }".to_string()
517 )))
518 );
519 }
520
521 #[test]
522 fn duplicate_parent_names() {
523 assert_eq!(
524 composite(NomSpan::new(
525 "composite a; primary parent \"bobolink\" { true; } parent \"bobolink\" { x == 1; }"
526 )),
527 Err(nom::Err::Error(BindParserError::DuplicateParentName("".to_string())))
528 );
529
530 assert_eq!(
531 composite(NomSpan::new(
532 "composite a; primary parent \"bobolink\" { true; } parent \"cowbird\" { x == 1; } parent \"cowbird\" { false; }"
533 )),
534 Err(nom::Err::Error(BindParserError::DuplicateParentName("".to_string())))
535 );
536 }
537 }
538}