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