1use std::collections::BTreeMap;
18
19use super::ast::ConditionExpr;
20use super::status::{status_lines, StatusBranch, StatusKind};
21use super::token::{strip_status_prefix, tokenize, SpannedToken, Token};
22use crate::error::ParseError;
23
24pub struct ConditionParser;
26
27impl ConditionParser {
28 pub fn parse(input: &str) -> Result<Option<ConditionExpr>, ParseError> {
43 Self::parse_with_ub(input, &BTreeMap::new())
44 }
45
46 pub fn parse_with_ub(
52 input: &str,
53 ub_definitions: &BTreeMap<String, ConditionExpr>,
54 ) -> Result<Option<ConditionExpr>, ParseError> {
55 let input = input.trim();
56 if input.is_empty() {
57 return Ok(None);
58 }
59
60 let alternatives: Vec<ConditionExpr> = Self::parse_status_branches(input, ub_definitions)?
68 .into_iter()
69 .filter_map(|b| b.condition)
70 .collect();
71
72 match alternatives.len() {
73 0 => Ok(None),
74 1 => Ok(alternatives.into_iter().next()),
75 _ => Ok(Some(ConditionExpr::Or(alternatives))),
76 }
77 }
78
79 pub fn parse_status_branches(
86 input: &str,
87 ub_definitions: &BTreeMap<String, ConditionExpr>,
88 ) -> Result<Vec<StatusBranch>, ParseError> {
89 thread_local! {
90 static PARSED: std::cell::RefCell<
91 std::collections::HashMap<String, Result<Vec<StatusBranch>, ParseError>>,
92 > = Default::default();
93 }
94 const KEPT: usize = 16_384;
97 if input.contains("UB") {
98 return Self::parse_status_branches_uncached(input, ub_definitions);
99 }
100 if let Some(hit) = PARSED.with(|p| p.borrow().get(input).cloned()) {
101 return hit;
102 }
103 let parsed = Self::parse_status_branches_uncached(input, ub_definitions);
104 PARSED.with(|p| {
105 let mut p = p.borrow_mut();
106 if p.len() < KEPT {
107 p.insert(input.to_string(), parsed.clone());
108 }
109 });
110 parsed
111 }
112
113 fn parse_status_branches_uncached(
114 input: &str,
115 ub_definitions: &BTreeMap<String, ConditionExpr>,
116 ) -> Result<Vec<StatusBranch>, ParseError> {
117 let mut branches = Vec::new();
118 for line in status_lines(input) {
119 let kind = StatusKind::of(line);
120 let stripped = strip_status_prefix(line);
121 let condition = if kind.is_some() && stripped == line {
123 None
124 } else {
125 let tokens = tokenize(stripped)?;
126 let mut pos = 0;
127 parse_expression(&tokens, &mut pos, ub_definitions)?
128 };
129 branches.push(StatusBranch { kind, condition });
130 }
131 Ok(branches)
132 }
133
134 pub fn parse_raw(input: &str) -> Result<Option<ConditionExpr>, ParseError> {
138 let input = input.trim();
139 if input.is_empty() {
140 return Ok(None);
141 }
142
143 let tokens = tokenize(input)?;
144 if tokens.is_empty() {
145 return Ok(None);
146 }
147
148 let mut pos = 0;
149 let expr = parse_expression(&tokens, &mut pos, &BTreeMap::new())?;
150
151 Ok(expr)
152 }
153}
154
155fn parse_expression(
157 tokens: &[SpannedToken],
158 pos: &mut usize,
159 ub_definitions: &BTreeMap<String, ConditionExpr>,
160) -> Result<Option<ConditionExpr>, ParseError> {
161 parse_xor(tokens, pos, ub_definitions)
162}
163
164fn parse_xor(
166 tokens: &[SpannedToken],
167 pos: &mut usize,
168 ub_definitions: &BTreeMap<String, ConditionExpr>,
169) -> Result<Option<ConditionExpr>, ParseError> {
170 let mut left = match parse_or(tokens, pos, ub_definitions)? {
171 Some(expr) => expr,
172 None => return Ok(None),
173 };
174
175 while *pos < tokens.len() && tokens[*pos].token == Token::Xor {
176 *pos += 1; let right = match parse_or(tokens, pos, ub_definitions)? {
178 Some(expr) => expr,
179 None => return Ok(Some(left)),
180 };
181 left = ConditionExpr::Xor(Box::new(left), Box::new(right));
182 }
183
184 Ok(Some(left))
185}
186
187fn parse_or(
189 tokens: &[SpannedToken],
190 pos: &mut usize,
191 ub_definitions: &BTreeMap<String, ConditionExpr>,
192) -> Result<Option<ConditionExpr>, ParseError> {
193 let mut left = match parse_and(tokens, pos, ub_definitions)? {
194 Some(expr) => expr,
195 None => return Ok(None),
196 };
197
198 while *pos < tokens.len() && tokens[*pos].token == Token::Or {
199 *pos += 1; let right = match parse_and(tokens, pos, ub_definitions)? {
201 Some(expr) => expr,
202 None => return Ok(Some(left)),
203 };
204 left = match left {
206 ConditionExpr::Or(mut exprs) => {
207 exprs.push(right);
208 ConditionExpr::Or(exprs)
209 }
210 _ => ConditionExpr::Or(vec![left, right]),
211 };
212 }
213
214 Ok(Some(left))
215}
216
217fn parse_and(
220 tokens: &[SpannedToken],
221 pos: &mut usize,
222 ub_definitions: &BTreeMap<String, ConditionExpr>,
223) -> Result<Option<ConditionExpr>, ParseError> {
224 let mut left = match parse_not(tokens, pos, ub_definitions)? {
225 Some(expr) => expr,
226 None => return Ok(None),
227 };
228
229 while *pos < tokens.len() {
230 if tokens[*pos].token == Token::And {
231 *pos += 1; let right = match parse_not(tokens, pos, ub_definitions)? {
233 Some(expr) => expr,
234 None => return Ok(Some(left)),
235 };
236 left = flatten_and(left, right);
237 } else if matches!(
238 tokens[*pos].token,
239 Token::ConditionId(_) | Token::LeftParen | Token::Not
240 ) {
241 let right = match parse_not(tokens, pos, ub_definitions)? {
243 Some(expr) => expr,
244 None => return Ok(Some(left)),
245 };
246 left = flatten_and(left, right);
247 } else {
248 break;
249 }
250 }
251
252 Ok(Some(left))
253}
254
255fn flatten_and(left: ConditionExpr, right: ConditionExpr) -> ConditionExpr {
257 match left {
258 ConditionExpr::And(mut exprs) => {
259 exprs.push(right);
260 ConditionExpr::And(exprs)
261 }
262 _ => ConditionExpr::And(vec![left, right]),
263 }
264}
265
266fn parse_not(
268 tokens: &[SpannedToken],
269 pos: &mut usize,
270 ub_definitions: &BTreeMap<String, ConditionExpr>,
271) -> Result<Option<ConditionExpr>, ParseError> {
272 if *pos < tokens.len() && tokens[*pos].token == Token::Not {
273 *pos += 1; let inner = match parse_not(tokens, pos, ub_definitions)? {
275 Some(expr) => expr,
276 None => {
277 return Err(ParseError::UnexpectedToken {
278 position: if *pos < tokens.len() {
279 tokens[*pos].position
280 } else {
281 0
282 },
283 expected: "expression after NOT".to_string(),
284 found: "end of input".to_string(),
285 });
286 }
287 };
288 return Ok(Some(ConditionExpr::Not(Box::new(inner))));
289 }
290 parse_primary(tokens, pos, ub_definitions)
291}
292
293fn parse_primary(
298 tokens: &[SpannedToken],
299 pos: &mut usize,
300 ub_definitions: &BTreeMap<String, ConditionExpr>,
301) -> Result<Option<ConditionExpr>, ParseError> {
302 if *pos >= tokens.len() {
303 return Ok(None);
304 }
305
306 match &tokens[*pos].token {
307 Token::ConditionId(id) => {
308 *pos += 1;
309 if let Some(ub_expr) = ub_definitions.get(id.as_str()) {
311 return Ok(Some(ub_expr.clone()));
312 }
313 Ok(Some(parse_condition_id(id)))
314 }
315 Token::LeftParen => {
316 *pos += 1; let expr = parse_expression(tokens, pos, ub_definitions)?;
318 if *pos < tokens.len() && tokens[*pos].token == Token::RightParen {
320 *pos += 1;
321 }
322 Ok(expr)
323 }
324 _ => Ok(None),
325 }
326}
327
328fn parse_condition_id(id: &str) -> ConditionExpr {
335 if let Ok(num) = id.parse::<u32>() {
337 return ConditionExpr::Ref(num);
338 }
339
340 if let Some(p_pos) = id.find('P') {
342 let num_part = &id[..p_pos];
343 let range_part = &id[p_pos + 1..];
344 if let Ok(pkg_id) = num_part.parse::<u32>() {
345 let (min, max) = parse_package_range(range_part);
346 return ConditionExpr::Package {
347 id: pkg_id,
348 min,
349 max,
350 };
351 }
352 }
353
354 let numeric_part: String = id.chars().take_while(|c| c.is_ascii_digit()).collect();
356 if let Ok(num) = numeric_part.parse::<u32>() {
357 ConditionExpr::Ref(num)
358 } else {
359 ConditionExpr::Ref(0)
360 }
361}
362
363fn parse_package_range(range: &str) -> (u32, u32) {
365 if range.is_empty() {
366 return (0, u32::MAX);
367 }
368 if let Some((min_str, max_str)) = range.split_once("..") {
369 let min = min_str.parse::<u32>().unwrap_or(0);
370 let max = max_str.parse::<u32>().unwrap_or(u32::MAX);
371 (min, max)
372 } else {
373 let n = range.parse::<u32>().unwrap_or(0);
374 (n, n)
375 }
376}
377
378#[cfg(test)]
379mod tests {
380 use super::*;
381 use pretty_assertions::assert_eq;
382
383 #[test]
386 fn test_parse_single_condition() {
387 let result = ConditionParser::parse("[931]").unwrap().unwrap();
388 assert_eq!(result, ConditionExpr::Ref(931));
389 }
390
391 #[test]
392 fn test_parse_with_muss_prefix() {
393 let result = ConditionParser::parse("Muss [494]").unwrap().unwrap();
394 assert_eq!(result, ConditionExpr::Ref(494));
395 }
396
397 #[test]
398 fn test_parse_with_soll_prefix() {
399 let result = ConditionParser::parse("Soll [494]").unwrap().unwrap();
400 assert_eq!(result, ConditionExpr::Ref(494));
401 }
402
403 #[test]
404 fn test_parse_with_kann_prefix() {
405 let result = ConditionParser::parse("Kann [182]").unwrap().unwrap();
406 assert_eq!(result, ConditionExpr::Ref(182));
407 }
408
409 #[test]
410 fn test_parse_with_x_prefix() {
411 let result = ConditionParser::parse("X [567]").unwrap().unwrap();
412 assert_eq!(result, ConditionExpr::Ref(567));
413 }
414
415 #[test]
418 fn test_parse_simple_and() {
419 let result = ConditionParser::parse("[182] ∧ [152]").unwrap().unwrap();
420 assert_eq!(
421 result,
422 ConditionExpr::And(vec![ConditionExpr::Ref(182), ConditionExpr::Ref(152)])
423 );
424 }
425
426 #[test]
427 fn test_parse_simple_or() {
428 let result = ConditionParser::parse("[1] ∨ [2]").unwrap().unwrap();
429 assert_eq!(
430 result,
431 ConditionExpr::Or(vec![ConditionExpr::Ref(1), ConditionExpr::Ref(2)])
432 );
433 }
434
435 #[test]
436 fn test_parse_simple_xor() {
437 let result = ConditionParser::parse("[1] ⊻ [2]").unwrap().unwrap();
438 assert_eq!(
439 result,
440 ConditionExpr::Xor(
441 Box::new(ConditionExpr::Ref(1)),
442 Box::new(ConditionExpr::Ref(2)),
443 )
444 );
445 }
446
447 #[test]
450 fn test_parse_three_way_and() {
451 let result = ConditionParser::parse("[1] ∧ [2] ∧ [3]").unwrap().unwrap();
452 assert_eq!(
453 result,
454 ConditionExpr::And(vec![
455 ConditionExpr::Ref(1),
456 ConditionExpr::Ref(2),
457 ConditionExpr::Ref(3),
458 ])
459 );
460 }
461
462 #[test]
463 fn test_parse_three_way_and_with_prefix() {
464 let result = ConditionParser::parse("Kann [182] ∧ [6] ∧ [570]")
465 .unwrap()
466 .unwrap();
467 assert_eq!(
468 result,
469 ConditionExpr::And(vec![
470 ConditionExpr::Ref(182),
471 ConditionExpr::Ref(6),
472 ConditionExpr::Ref(570),
473 ])
474 );
475 assert_eq!(result.condition_ids(), [6, 182, 570].into());
476 }
477
478 #[test]
479 fn test_parse_multiple_xor() {
480 let result = ConditionParser::parse("[1] ⊻ [2] ⊻ [3] ⊻ [4]")
481 .unwrap()
482 .unwrap();
483 assert_eq!(result.condition_ids(), [1, 2, 3, 4].into());
484 }
485
486 #[test]
489 fn test_parse_parenthesized_expression() {
490 let result = ConditionParser::parse("([1] ∨ [2]) ∧ [3]")
491 .unwrap()
492 .unwrap();
493 assert_eq!(
494 result,
495 ConditionExpr::And(vec![
496 ConditionExpr::Or(vec![ConditionExpr::Ref(1), ConditionExpr::Ref(2)]),
497 ConditionExpr::Ref(3),
498 ])
499 );
500 }
501
502 #[test]
503 fn test_parse_nested_parentheses() {
504 let result = ConditionParser::parse("(([1] ∧ [2]) ∨ ([3] ∧ [4])) ∧ [5]")
506 .unwrap()
507 .unwrap();
508 assert_eq!(result.condition_ids(), [1, 2, 3, 4, 5].into());
509 match &result {
511 ConditionExpr::And(exprs) => {
512 assert_eq!(exprs.len(), 2);
513 assert!(matches!(&exprs[0], ConditionExpr::Or(_)));
514 assert_eq!(exprs[1], ConditionExpr::Ref(5));
515 }
516 other => panic!("Expected And, got {other:?}"),
517 }
518 }
519
520 #[test]
523 fn test_and_has_higher_precedence_than_or() {
524 let result = ConditionParser::parse("[1] ∨ [2] ∧ [3]").unwrap().unwrap();
526 assert_eq!(
527 result,
528 ConditionExpr::Or(vec![
529 ConditionExpr::Ref(1),
530 ConditionExpr::And(vec![ConditionExpr::Ref(2), ConditionExpr::Ref(3)]),
531 ])
532 );
533 }
534
535 #[test]
536 fn test_or_has_higher_precedence_than_xor() {
537 let result = ConditionParser::parse("[1] ⊻ [2] ∨ [3]").unwrap().unwrap();
539 assert_eq!(
540 result,
541 ConditionExpr::Xor(
542 Box::new(ConditionExpr::Ref(1)),
543 Box::new(ConditionExpr::Or(vec![
544 ConditionExpr::Ref(2),
545 ConditionExpr::Ref(3),
546 ])),
547 )
548 );
549 }
550
551 #[test]
554 fn test_adjacent_conditions_implicit_and() {
555 let result = ConditionParser::parse("[1] [2]").unwrap().unwrap();
557 assert_eq!(
558 result,
559 ConditionExpr::And(vec![ConditionExpr::Ref(1), ConditionExpr::Ref(2)])
560 );
561 }
562
563 #[test]
564 fn test_adjacent_conditions_no_space_implicit_and() {
565 let result = ConditionParser::parse("[939][14]").unwrap().unwrap();
567 assert_eq!(
568 result,
569 ConditionExpr::And(vec![ConditionExpr::Ref(939), ConditionExpr::Ref(14)])
570 );
571 }
572
573 #[test]
576 fn test_parse_not() {
577 let result = ConditionParser::parse("NOT [1]").unwrap().unwrap();
578 assert_eq!(result, ConditionExpr::Not(Box::new(ConditionExpr::Ref(1))));
579 }
580
581 #[test]
582 fn test_parse_not_with_and() {
583 let result = ConditionParser::parse("NOT [1] ∧ [2]").unwrap().unwrap();
585 assert_eq!(
586 result,
587 ConditionExpr::And(vec![
588 ConditionExpr::Not(Box::new(ConditionExpr::Ref(1))),
589 ConditionExpr::Ref(2),
590 ])
591 );
592 }
593
594 #[test]
597 fn test_real_world_orders_expression() {
598 let result = ConditionParser::parse("X (([939] [147]) ∨ ([940] [148])) ∧ [567]")
600 .unwrap()
601 .unwrap();
602 assert_eq!(result.condition_ids(), [147, 148, 567, 939, 940].into());
603 }
604
605 #[test]
606 fn test_real_world_xor_expression() {
607 let result = ConditionParser::parse("Muss ([102] ∧ [2006]) ⊻ ([103] ∧ [2005])")
609 .unwrap()
610 .unwrap();
611 assert!(matches!(result, ConditionExpr::Xor(_, _)));
612 assert_eq!(result.condition_ids(), [102, 103, 2005, 2006].into());
613 }
614
615 #[test]
616 fn test_real_world_complex_nested_with_implicit_and() {
617 let result = ConditionParser::parse("([939][14]) ∨ ([940][15])")
619 .unwrap()
620 .unwrap();
621 assert!(matches!(result, ConditionExpr::Or(_)));
622 assert_eq!(result.condition_ids(), [14, 15, 939, 940].into());
623 }
624
625 #[test]
628 fn test_parse_empty_string() {
629 assert!(ConditionParser::parse("").unwrap().is_none());
630 }
631
632 #[test]
633 fn test_parse_whitespace_only() {
634 assert!(ConditionParser::parse(" \t ").unwrap().is_none());
635 }
636
637 #[test]
638 fn test_parse_bare_muss() {
639 assert!(ConditionParser::parse("Muss").unwrap().is_none());
640 }
641
642 #[test]
643 fn test_parse_bare_x() {
644 assert!(ConditionParser::parse("X").unwrap().is_none());
646 }
647
648 #[test]
649 fn test_parse_unmatched_open_paren_graceful() {
650 let result = ConditionParser::parse("([1] ∧ [2]").unwrap().unwrap();
652 assert_eq!(
653 result,
654 ConditionExpr::And(vec![ConditionExpr::Ref(1), ConditionExpr::Ref(2)])
655 );
656 }
657
658 #[test]
659 fn test_parse_text_and_operator() {
660 let result = ConditionParser::parse("[1] AND [2]").unwrap().unwrap();
661 assert_eq!(
662 result,
663 ConditionExpr::And(vec![ConditionExpr::Ref(1), ConditionExpr::Ref(2)])
664 );
665 }
666
667 #[test]
668 fn test_parse_text_or_operator() {
669 let result = ConditionParser::parse("[1] OR [2]").unwrap().unwrap();
670 assert_eq!(
671 result,
672 ConditionExpr::Or(vec![ConditionExpr::Ref(1), ConditionExpr::Ref(2)])
673 );
674 }
675
676 #[test]
677 fn test_parse_text_xor_operator() {
678 let result = ConditionParser::parse("[1] XOR [2]").unwrap().unwrap();
679 assert_eq!(
680 result,
681 ConditionExpr::Xor(
682 Box::new(ConditionExpr::Ref(1)),
683 Box::new(ConditionExpr::Ref(2)),
684 )
685 );
686 }
687
688 #[test]
689 fn test_parse_mixed_unicode_and_text_operators() {
690 let result = ConditionParser::parse("[1] ∧ [2] OR [3]").unwrap().unwrap();
691 assert_eq!(
692 result,
693 ConditionExpr::Or(vec![
694 ConditionExpr::And(vec![ConditionExpr::Ref(1), ConditionExpr::Ref(2)]),
695 ConditionExpr::Ref(3),
696 ])
697 );
698 }
699
700 #[test]
701 fn test_parse_deeply_nested() {
702 let result = ConditionParser::parse("((([1])))").unwrap().unwrap();
704 assert_eq!(result, ConditionExpr::Ref(1));
705 }
706
707 #[test]
710 fn test_parse_package_condition_0_1() {
711 let result = ConditionParser::parse("[4P0..1]").unwrap().unwrap();
712 assert_eq!(
713 result,
714 ConditionExpr::Package {
715 id: 4,
716 min: 0,
717 max: 1
718 }
719 );
720 }
721
722 #[test]
723 fn test_parse_package_condition_1_5() {
724 let result = ConditionParser::parse("[10P1..5]").unwrap().unwrap();
725 assert_eq!(
726 result,
727 ConditionExpr::Package {
728 id: 10,
729 min: 1,
730 max: 5
731 }
732 );
733 }
734
735 #[test]
736 fn test_parse_package_in_expression() {
737 let result = ConditionParser::parse("X [4P0..1] ⊻ [5P0..1]")
738 .unwrap()
739 .unwrap();
740 assert_eq!(
741 result,
742 ConditionExpr::Xor(
743 Box::new(ConditionExpr::Package {
744 id: 4,
745 min: 0,
746 max: 1
747 }),
748 Box::new(ConditionExpr::Package {
749 id: 5,
750 min: 0,
751 max: 1
752 }),
753 )
754 );
755 }
756
757 #[test]
758 fn test_parse_package_bare_p() {
759 let result = ConditionParser::parse("[1P]").unwrap().unwrap();
760 assert_eq!(
761 result,
762 ConditionExpr::Package {
763 id: 1,
764 min: 0,
765 max: u32::MAX
766 }
767 );
768 }
769
770 #[test]
771 fn test_condition_ids_extraction_full() {
772 let result = ConditionParser::parse("Muss ([102] ∧ [2006]) ⊻ ([103] ∧ [2005])")
773 .unwrap()
774 .unwrap();
775 let ids = result.condition_ids();
776 assert!(ids.contains(&102));
777 assert!(ids.contains(&103));
778 assert!(ids.contains(&2005));
779 assert!(ids.contains(&2006));
780 assert_eq!(ids.len(), 4);
781 }
782
783 #[test]
786 fn test_parse_ub_inline_expansion() {
787 let ub1_expr = ConditionParser::parse("[931] ∧ [932]").unwrap().unwrap();
788 let mut ub_map = BTreeMap::new();
789 ub_map.insert("UB1".to_string(), ub1_expr.clone());
790
791 let result = ConditionParser::parse_with_ub("X [UB1]", &ub_map)
792 .unwrap()
793 .unwrap();
794 assert_eq!(result, ub1_expr);
795 }
796
797 #[test]
800 fn a_kept_status_follows_its_ub_definitions() {
801 let with = |expr: &str| {
802 let mut ub_map = BTreeMap::new();
803 ub_map.insert(
804 "UB1".to_string(),
805 ConditionParser::parse(expr).unwrap().unwrap(),
806 );
807 ConditionParser::parse_with_ub("X [931] ∧ [UB1]", &ub_map)
808 .unwrap()
809 .unwrap()
810 };
811 let first = with("[1]");
812 assert_eq!(first, with("[1]"));
813 assert_ne!(first, with("[2]"));
814 let plain = ConditionParser::parse("Muss [92] ∧ [126]").unwrap();
816 assert_eq!(plain, ConditionParser::parse("Muss [92] ∧ [126]").unwrap());
817 }
818
819 #[test]
820 fn test_parse_ub_unknown_falls_back() {
821 let ub_map = BTreeMap::new();
822 let result = ConditionParser::parse_with_ub("[UB99]", &ub_map)
823 .unwrap()
824 .unwrap();
825 assert_eq!(result, ConditionExpr::Ref(0));
826 }
827
828 #[test]
829 fn test_parse_with_ub_empty_map_same_as_parse() {
830 let ub_map = BTreeMap::new();
831 let result = ConditionParser::parse_with_ub("X [931] ∧ [932]", &ub_map)
833 .unwrap()
834 .unwrap();
835 let expected = ConditionParser::parse("X [931] ∧ [932]").unwrap().unwrap();
836 assert_eq!(result, expected);
837 }
838
839 #[test]
840 fn test_parse_ub_in_complex_expression() {
841 let ub1_expr = ConditionParser::parse("[931] ∧ [932]").unwrap().unwrap();
843 let mut ub_map = BTreeMap::new();
844 ub_map.insert("UB1".to_string(), ub1_expr);
845
846 let result = ConditionParser::parse_with_ub("X [UB1] ∨ [100]", &ub_map)
847 .unwrap()
848 .unwrap();
849 assert_eq!(
851 result,
852 ConditionExpr::Or(vec![
853 ConditionExpr::And(vec![ConditionExpr::Ref(931), ConditionExpr::Ref(932)]),
854 ConditionExpr::Ref(100),
855 ])
856 );
857 }
858
859 #[test]
860 fn test_parse_ub_multiple_references() {
861 let ub1_expr = ConditionParser::parse("[931]").unwrap().unwrap();
863 let ub2_expr = ConditionParser::parse("[932]").unwrap().unwrap();
864 let mut ub_map = BTreeMap::new();
865 ub_map.insert("UB1".to_string(), ub1_expr);
866 ub_map.insert("UB2".to_string(), ub2_expr);
867
868 let result = ConditionParser::parse_with_ub("X [UB1] ∧ [UB2]", &ub_map)
869 .unwrap()
870 .unwrap();
871 assert_eq!(
872 result,
873 ConditionExpr::And(vec![ConditionExpr::Ref(931), ConditionExpr::Ref(932)])
874 );
875 }
876
877 #[test]
878 fn test_parse_multi_line_alternatives_are_or() {
879 let status = "Muss [315] ∧ [707]\nSoll [8] ∧ [301] ∧ [707]";
882 let result = ConditionParser::parse(status).unwrap().unwrap();
883 assert_eq!(
884 result,
885 ConditionExpr::Or(vec![
886 ConditionExpr::And(vec![ConditionExpr::Ref(315), ConditionExpr::Ref(707)]),
887 ConditionExpr::And(vec![
888 ConditionExpr::Ref(8),
889 ConditionExpr::Ref(301),
890 ConditionExpr::Ref(707),
891 ]),
892 ])
893 );
894 }
895
896 #[test]
897 fn test_parse_multi_line_with_crlf() {
898 let result = ConditionParser::parse("Muss [10]\r\nSoll [20]")
900 .unwrap()
901 .unwrap();
902 assert_eq!(
903 result,
904 ConditionExpr::Or(vec![ConditionExpr::Ref(10), ConditionExpr::Ref(20)])
905 );
906 }
907
908 #[test]
909 fn test_parse_single_line_unchanged() {
910 let result = ConditionParser::parse("Muss [315] ∧ [707]")
912 .unwrap()
913 .unwrap();
914 assert_eq!(
915 result,
916 ConditionExpr::And(vec![ConditionExpr::Ref(315), ConditionExpr::Ref(707)])
917 );
918 }
919
920 fn branches(status: &str) -> Vec<StatusBranch> {
923 ConditionParser::parse_status_branches(status, &BTreeMap::new()).unwrap()
924 }
925
926 #[test]
927 fn test_status_branches_keep_each_lines_status() {
928 assert_eq!(
929 branches("Muss [2119] \r\nSoll [130]"),
930 vec![
931 StatusBranch {
932 kind: Some(StatusKind::Muss),
933 condition: Some(ConditionExpr::Ref(2119)),
934 },
935 StatusBranch {
936 kind: Some(StatusKind::Soll),
937 condition: Some(ConditionExpr::Ref(130)),
938 },
939 ]
940 );
941 }
942
943 #[test]
944 fn test_status_branches_bare_line_has_no_condition() {
945 assert_eq!(
946 branches("Muss [48]\r\nKann"),
947 vec![
948 StatusBranch {
949 kind: Some(StatusKind::Muss),
950 condition: Some(ConditionExpr::Ref(48)),
951 },
952 StatusBranch {
953 kind: Some(StatusKind::Kann),
954 condition: None,
955 },
956 ]
957 );
958 }
959
960 #[test]
961 fn test_status_branches_x_is_muss() {
962 assert_eq!(
963 branches("X [931]"),
964 vec![StatusBranch {
965 kind: Some(StatusKind::Muss),
966 condition: Some(ConditionExpr::Ref(931)),
967 }]
968 );
969 }
970
971 #[test]
972 fn test_status_branches_split_a_second_status_on_the_same_line() {
973 assert_eq!(
975 branches("Muss [56] ∧ [59] Soll [70]"),
976 vec![
977 StatusBranch {
978 kind: Some(StatusKind::Muss),
979 condition: Some(ConditionExpr::And(vec![
980 ConditionExpr::Ref(56),
981 ConditionExpr::Ref(59),
982 ])),
983 },
984 StatusBranch {
985 kind: Some(StatusKind::Soll),
986 condition: Some(ConditionExpr::Ref(70)),
987 },
988 ]
989 );
990 }
991
992 #[test]
993 fn test_status_branches_do_not_split_inside_parentheses() {
994 let status = "Muss ([525]) ∧ ([56] ∧ [59] Soll [70]) ∧ ([54])";
998 let found = branches(status);
999 assert_eq!(found.len(), 1, "{found:?}");
1000 assert_eq!(found[0].kind, Some(StatusKind::Muss));
1001 assert_eq!(
1002 found[0].condition,
1003 ConditionParser::parse_raw("([525]) ∧ ([56] ∧ [59] [70]) ∧ ([54])").unwrap()
1004 );
1005 }
1006
1007 #[test]
1008 fn test_parse_same_line_second_status_is_or() {
1009 let result = ConditionParser::parse("Muss [56] ∧ [59] Soll [70]")
1010 .unwrap()
1011 .unwrap();
1012 assert_eq!(
1013 result,
1014 ConditionExpr::Or(vec![
1015 ConditionExpr::And(vec![ConditionExpr::Ref(56), ConditionExpr::Ref(59)]),
1016 ConditionExpr::Ref(70),
1017 ])
1018 );
1019 }
1020}