use std::collections::BTreeMap;
use super::ast::ConditionExpr;
use super::token::{strip_status_prefix, tokenize, SpannedToken, Token};
use crate::error::ParseError;
pub struct ConditionParser;
impl ConditionParser {
pub fn parse(input: &str) -> Result<Option<ConditionExpr>, ParseError> {
Self::parse_with_ub(input, &BTreeMap::new())
}
pub fn parse_with_ub(
input: &str,
ub_definitions: &BTreeMap<String, ConditionExpr>,
) -> Result<Option<ConditionExpr>, ParseError> {
let input = input.trim();
if input.is_empty() {
return Ok(None);
}
let lines: Vec<&str> = input
.lines()
.map(str::trim)
.filter(|l| !l.is_empty())
.collect();
let mut alternatives: Vec<ConditionExpr> = Vec::new();
for line in &lines {
let stripped = strip_status_prefix(line);
if stripped.is_empty() {
continue;
}
let tokens = tokenize(stripped)?;
if tokens.is_empty() {
continue;
}
let mut pos = 0;
if let Some(expr) = parse_expression(&tokens, &mut pos, ub_definitions)? {
alternatives.push(expr);
}
}
match alternatives.len() {
0 => Ok(None),
1 => Ok(Some(alternatives.into_iter().next().unwrap())),
_ => Ok(Some(ConditionExpr::Or(alternatives))),
}
}
pub fn parse_raw(input: &str) -> Result<Option<ConditionExpr>, ParseError> {
let input = input.trim();
if input.is_empty() {
return Ok(None);
}
let tokens = tokenize(input)?;
if tokens.is_empty() {
return Ok(None);
}
let mut pos = 0;
let expr = parse_expression(&tokens, &mut pos, &BTreeMap::new())?;
Ok(expr)
}
}
fn parse_expression(
tokens: &[SpannedToken],
pos: &mut usize,
ub_definitions: &BTreeMap<String, ConditionExpr>,
) -> Result<Option<ConditionExpr>, ParseError> {
parse_xor(tokens, pos, ub_definitions)
}
fn parse_xor(
tokens: &[SpannedToken],
pos: &mut usize,
ub_definitions: &BTreeMap<String, ConditionExpr>,
) -> Result<Option<ConditionExpr>, ParseError> {
let mut left = match parse_or(tokens, pos, ub_definitions)? {
Some(expr) => expr,
None => return Ok(None),
};
while *pos < tokens.len() && tokens[*pos].token == Token::Xor {
*pos += 1; let right = match parse_or(tokens, pos, ub_definitions)? {
Some(expr) => expr,
None => return Ok(Some(left)),
};
left = ConditionExpr::Xor(Box::new(left), Box::new(right));
}
Ok(Some(left))
}
fn parse_or(
tokens: &[SpannedToken],
pos: &mut usize,
ub_definitions: &BTreeMap<String, ConditionExpr>,
) -> Result<Option<ConditionExpr>, ParseError> {
let mut left = match parse_and(tokens, pos, ub_definitions)? {
Some(expr) => expr,
None => return Ok(None),
};
while *pos < tokens.len() && tokens[*pos].token == Token::Or {
*pos += 1; let right = match parse_and(tokens, pos, ub_definitions)? {
Some(expr) => expr,
None => return Ok(Some(left)),
};
left = match left {
ConditionExpr::Or(mut exprs) => {
exprs.push(right);
ConditionExpr::Or(exprs)
}
_ => ConditionExpr::Or(vec![left, right]),
};
}
Ok(Some(left))
}
fn parse_and(
tokens: &[SpannedToken],
pos: &mut usize,
ub_definitions: &BTreeMap<String, ConditionExpr>,
) -> Result<Option<ConditionExpr>, ParseError> {
let mut left = match parse_not(tokens, pos, ub_definitions)? {
Some(expr) => expr,
None => return Ok(None),
};
while *pos < tokens.len() {
if tokens[*pos].token == Token::And {
*pos += 1; let right = match parse_not(tokens, pos, ub_definitions)? {
Some(expr) => expr,
None => return Ok(Some(left)),
};
left = flatten_and(left, right);
} else if matches!(
tokens[*pos].token,
Token::ConditionId(_) | Token::LeftParen | Token::Not
) {
let right = match parse_not(tokens, pos, ub_definitions)? {
Some(expr) => expr,
None => return Ok(Some(left)),
};
left = flatten_and(left, right);
} else {
break;
}
}
Ok(Some(left))
}
fn flatten_and(left: ConditionExpr, right: ConditionExpr) -> ConditionExpr {
match left {
ConditionExpr::And(mut exprs) => {
exprs.push(right);
ConditionExpr::And(exprs)
}
_ => ConditionExpr::And(vec![left, right]),
}
}
fn parse_not(
tokens: &[SpannedToken],
pos: &mut usize,
ub_definitions: &BTreeMap<String, ConditionExpr>,
) -> Result<Option<ConditionExpr>, ParseError> {
if *pos < tokens.len() && tokens[*pos].token == Token::Not {
*pos += 1; let inner = match parse_not(tokens, pos, ub_definitions)? {
Some(expr) => expr,
None => {
return Err(ParseError::UnexpectedToken {
position: if *pos < tokens.len() {
tokens[*pos].position
} else {
0
},
expected: "expression after NOT".to_string(),
found: "end of input".to_string(),
});
}
};
return Ok(Some(ConditionExpr::Not(Box::new(inner))));
}
parse_primary(tokens, pos, ub_definitions)
}
fn parse_primary(
tokens: &[SpannedToken],
pos: &mut usize,
ub_definitions: &BTreeMap<String, ConditionExpr>,
) -> Result<Option<ConditionExpr>, ParseError> {
if *pos >= tokens.len() {
return Ok(None);
}
match &tokens[*pos].token {
Token::ConditionId(id) => {
*pos += 1;
if let Some(ub_expr) = ub_definitions.get(id.as_str()) {
return Ok(Some(ub_expr.clone()));
}
Ok(Some(parse_condition_id(id)))
}
Token::LeftParen => {
*pos += 1; let expr = parse_expression(tokens, pos, ub_definitions)?;
if *pos < tokens.len() && tokens[*pos].token == Token::RightParen {
*pos += 1;
}
Ok(expr)
}
_ => Ok(None),
}
}
fn parse_condition_id(id: &str) -> ConditionExpr {
if let Ok(num) = id.parse::<u32>() {
return ConditionExpr::Ref(num);
}
if let Some(p_pos) = id.find('P') {
let num_part = &id[..p_pos];
let range_part = &id[p_pos + 1..];
if let Ok(pkg_id) = num_part.parse::<u32>() {
let (min, max) = parse_package_range(range_part);
return ConditionExpr::Package {
id: pkg_id,
min,
max,
};
}
}
let numeric_part: String = id.chars().take_while(|c| c.is_ascii_digit()).collect();
if let Ok(num) = numeric_part.parse::<u32>() {
ConditionExpr::Ref(num)
} else {
ConditionExpr::Ref(0)
}
}
fn parse_package_range(range: &str) -> (u32, u32) {
if range.is_empty() {
return (0, u32::MAX);
}
if let Some((min_str, max_str)) = range.split_once("..") {
let min = min_str.parse::<u32>().unwrap_or(0);
let max = max_str.parse::<u32>().unwrap_or(u32::MAX);
(min, max)
} else {
let n = range.parse::<u32>().unwrap_or(0);
(n, n)
}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_parse_single_condition() {
let result = ConditionParser::parse("[931]").unwrap().unwrap();
assert_eq!(result, ConditionExpr::Ref(931));
}
#[test]
fn test_parse_with_muss_prefix() {
let result = ConditionParser::parse("Muss [494]").unwrap().unwrap();
assert_eq!(result, ConditionExpr::Ref(494));
}
#[test]
fn test_parse_with_soll_prefix() {
let result = ConditionParser::parse("Soll [494]").unwrap().unwrap();
assert_eq!(result, ConditionExpr::Ref(494));
}
#[test]
fn test_parse_with_kann_prefix() {
let result = ConditionParser::parse("Kann [182]").unwrap().unwrap();
assert_eq!(result, ConditionExpr::Ref(182));
}
#[test]
fn test_parse_with_x_prefix() {
let result = ConditionParser::parse("X [567]").unwrap().unwrap();
assert_eq!(result, ConditionExpr::Ref(567));
}
#[test]
fn test_parse_simple_and() {
let result = ConditionParser::parse("[182] ∧ [152]").unwrap().unwrap();
assert_eq!(
result,
ConditionExpr::And(vec![ConditionExpr::Ref(182), ConditionExpr::Ref(152)])
);
}
#[test]
fn test_parse_simple_or() {
let result = ConditionParser::parse("[1] ∨ [2]").unwrap().unwrap();
assert_eq!(
result,
ConditionExpr::Or(vec![ConditionExpr::Ref(1), ConditionExpr::Ref(2)])
);
}
#[test]
fn test_parse_simple_xor() {
let result = ConditionParser::parse("[1] ⊻ [2]").unwrap().unwrap();
assert_eq!(
result,
ConditionExpr::Xor(
Box::new(ConditionExpr::Ref(1)),
Box::new(ConditionExpr::Ref(2)),
)
);
}
#[test]
fn test_parse_three_way_and() {
let result = ConditionParser::parse("[1] ∧ [2] ∧ [3]").unwrap().unwrap();
assert_eq!(
result,
ConditionExpr::And(vec![
ConditionExpr::Ref(1),
ConditionExpr::Ref(2),
ConditionExpr::Ref(3),
])
);
}
#[test]
fn test_parse_three_way_and_with_prefix() {
let result = ConditionParser::parse("Kann [182] ∧ [6] ∧ [570]")
.unwrap()
.unwrap();
assert_eq!(
result,
ConditionExpr::And(vec![
ConditionExpr::Ref(182),
ConditionExpr::Ref(6),
ConditionExpr::Ref(570),
])
);
assert_eq!(result.condition_ids(), [6, 182, 570].into());
}
#[test]
fn test_parse_multiple_xor() {
let result = ConditionParser::parse("[1] ⊻ [2] ⊻ [3] ⊻ [4]")
.unwrap()
.unwrap();
assert_eq!(result.condition_ids(), [1, 2, 3, 4].into());
}
#[test]
fn test_parse_parenthesized_expression() {
let result = ConditionParser::parse("([1] ∨ [2]) ∧ [3]")
.unwrap()
.unwrap();
assert_eq!(
result,
ConditionExpr::And(vec![
ConditionExpr::Or(vec![ConditionExpr::Ref(1), ConditionExpr::Ref(2)]),
ConditionExpr::Ref(3),
])
);
}
#[test]
fn test_parse_nested_parentheses() {
let result = ConditionParser::parse("(([1] ∧ [2]) ∨ ([3] ∧ [4])) ∧ [5]")
.unwrap()
.unwrap();
assert_eq!(result.condition_ids(), [1, 2, 3, 4, 5].into());
match &result {
ConditionExpr::And(exprs) => {
assert_eq!(exprs.len(), 2);
assert!(matches!(&exprs[0], ConditionExpr::Or(_)));
assert_eq!(exprs[1], ConditionExpr::Ref(5));
}
other => panic!("Expected And, got {other:?}"),
}
}
#[test]
fn test_and_has_higher_precedence_than_or() {
let result = ConditionParser::parse("[1] ∨ [2] ∧ [3]").unwrap().unwrap();
assert_eq!(
result,
ConditionExpr::Or(vec![
ConditionExpr::Ref(1),
ConditionExpr::And(vec![ConditionExpr::Ref(2), ConditionExpr::Ref(3)]),
])
);
}
#[test]
fn test_or_has_higher_precedence_than_xor() {
let result = ConditionParser::parse("[1] ⊻ [2] ∨ [3]").unwrap().unwrap();
assert_eq!(
result,
ConditionExpr::Xor(
Box::new(ConditionExpr::Ref(1)),
Box::new(ConditionExpr::Or(vec![
ConditionExpr::Ref(2),
ConditionExpr::Ref(3),
])),
)
);
}
#[test]
fn test_adjacent_conditions_implicit_and() {
let result = ConditionParser::parse("[1] [2]").unwrap().unwrap();
assert_eq!(
result,
ConditionExpr::And(vec![ConditionExpr::Ref(1), ConditionExpr::Ref(2)])
);
}
#[test]
fn test_adjacent_conditions_no_space_implicit_and() {
let result = ConditionParser::parse("[939][14]").unwrap().unwrap();
assert_eq!(
result,
ConditionExpr::And(vec![ConditionExpr::Ref(939), ConditionExpr::Ref(14)])
);
}
#[test]
fn test_parse_not() {
let result = ConditionParser::parse("NOT [1]").unwrap().unwrap();
assert_eq!(result, ConditionExpr::Not(Box::new(ConditionExpr::Ref(1))));
}
#[test]
fn test_parse_not_with_and() {
let result = ConditionParser::parse("NOT [1] ∧ [2]").unwrap().unwrap();
assert_eq!(
result,
ConditionExpr::And(vec![
ConditionExpr::Not(Box::new(ConditionExpr::Ref(1))),
ConditionExpr::Ref(2),
])
);
}
#[test]
fn test_real_world_orders_expression() {
let result = ConditionParser::parse("X (([939] [147]) ∨ ([940] [148])) ∧ [567]")
.unwrap()
.unwrap();
assert_eq!(result.condition_ids(), [147, 148, 567, 939, 940].into());
}
#[test]
fn test_real_world_xor_expression() {
let result = ConditionParser::parse("Muss ([102] ∧ [2006]) ⊻ ([103] ∧ [2005])")
.unwrap()
.unwrap();
assert!(matches!(result, ConditionExpr::Xor(_, _)));
assert_eq!(result.condition_ids(), [102, 103, 2005, 2006].into());
}
#[test]
fn test_real_world_complex_nested_with_implicit_and() {
let result = ConditionParser::parse("([939][14]) ∨ ([940][15])")
.unwrap()
.unwrap();
assert!(matches!(result, ConditionExpr::Or(_)));
assert_eq!(result.condition_ids(), [14, 15, 939, 940].into());
}
#[test]
fn test_parse_empty_string() {
assert!(ConditionParser::parse("").unwrap().is_none());
}
#[test]
fn test_parse_whitespace_only() {
assert!(ConditionParser::parse(" \t ").unwrap().is_none());
}
#[test]
fn test_parse_bare_muss() {
assert!(ConditionParser::parse("Muss").unwrap().is_none());
}
#[test]
fn test_parse_bare_x() {
assert!(ConditionParser::parse("X").unwrap().is_none());
}
#[test]
fn test_parse_unmatched_open_paren_graceful() {
let result = ConditionParser::parse("([1] ∧ [2]").unwrap().unwrap();
assert_eq!(
result,
ConditionExpr::And(vec![ConditionExpr::Ref(1), ConditionExpr::Ref(2)])
);
}
#[test]
fn test_parse_text_and_operator() {
let result = ConditionParser::parse("[1] AND [2]").unwrap().unwrap();
assert_eq!(
result,
ConditionExpr::And(vec![ConditionExpr::Ref(1), ConditionExpr::Ref(2)])
);
}
#[test]
fn test_parse_text_or_operator() {
let result = ConditionParser::parse("[1] OR [2]").unwrap().unwrap();
assert_eq!(
result,
ConditionExpr::Or(vec![ConditionExpr::Ref(1), ConditionExpr::Ref(2)])
);
}
#[test]
fn test_parse_text_xor_operator() {
let result = ConditionParser::parse("[1] XOR [2]").unwrap().unwrap();
assert_eq!(
result,
ConditionExpr::Xor(
Box::new(ConditionExpr::Ref(1)),
Box::new(ConditionExpr::Ref(2)),
)
);
}
#[test]
fn test_parse_mixed_unicode_and_text_operators() {
let result = ConditionParser::parse("[1] ∧ [2] OR [3]").unwrap().unwrap();
assert_eq!(
result,
ConditionExpr::Or(vec![
ConditionExpr::And(vec![ConditionExpr::Ref(1), ConditionExpr::Ref(2)]),
ConditionExpr::Ref(3),
])
);
}
#[test]
fn test_parse_deeply_nested() {
let result = ConditionParser::parse("((([1])))").unwrap().unwrap();
assert_eq!(result, ConditionExpr::Ref(1));
}
#[test]
fn test_parse_package_condition_0_1() {
let result = ConditionParser::parse("[4P0..1]").unwrap().unwrap();
assert_eq!(
result,
ConditionExpr::Package {
id: 4,
min: 0,
max: 1
}
);
}
#[test]
fn test_parse_package_condition_1_5() {
let result = ConditionParser::parse("[10P1..5]").unwrap().unwrap();
assert_eq!(
result,
ConditionExpr::Package {
id: 10,
min: 1,
max: 5
}
);
}
#[test]
fn test_parse_package_in_expression() {
let result = ConditionParser::parse("X [4P0..1] ⊻ [5P0..1]")
.unwrap()
.unwrap();
assert_eq!(
result,
ConditionExpr::Xor(
Box::new(ConditionExpr::Package {
id: 4,
min: 0,
max: 1
}),
Box::new(ConditionExpr::Package {
id: 5,
min: 0,
max: 1
}),
)
);
}
#[test]
fn test_parse_package_bare_p() {
let result = ConditionParser::parse("[1P]").unwrap().unwrap();
assert_eq!(
result,
ConditionExpr::Package {
id: 1,
min: 0,
max: u32::MAX
}
);
}
#[test]
fn test_condition_ids_extraction_full() {
let result = ConditionParser::parse("Muss ([102] ∧ [2006]) ⊻ ([103] ∧ [2005])")
.unwrap()
.unwrap();
let ids = result.condition_ids();
assert!(ids.contains(&102));
assert!(ids.contains(&103));
assert!(ids.contains(&2005));
assert!(ids.contains(&2006));
assert_eq!(ids.len(), 4);
}
#[test]
fn test_parse_ub_inline_expansion() {
let ub1_expr = ConditionParser::parse("[931] ∧ [932]").unwrap().unwrap();
let mut ub_map = BTreeMap::new();
ub_map.insert("UB1".to_string(), ub1_expr.clone());
let result = ConditionParser::parse_with_ub("X [UB1]", &ub_map)
.unwrap()
.unwrap();
assert_eq!(result, ub1_expr);
}
#[test]
fn test_parse_ub_unknown_falls_back() {
let ub_map = BTreeMap::new();
let result = ConditionParser::parse_with_ub("[UB99]", &ub_map)
.unwrap()
.unwrap();
assert_eq!(result, ConditionExpr::Ref(0));
}
#[test]
fn test_parse_with_ub_empty_map_same_as_parse() {
let ub_map = BTreeMap::new();
let result = ConditionParser::parse_with_ub("X [931] ∧ [932]", &ub_map)
.unwrap()
.unwrap();
let expected = ConditionParser::parse("X [931] ∧ [932]").unwrap().unwrap();
assert_eq!(result, expected);
}
#[test]
fn test_parse_ub_in_complex_expression() {
let ub1_expr = ConditionParser::parse("[931] ∧ [932]").unwrap().unwrap();
let mut ub_map = BTreeMap::new();
ub_map.insert("UB1".to_string(), ub1_expr);
let result = ConditionParser::parse_with_ub("X [UB1] ∨ [100]", &ub_map)
.unwrap()
.unwrap();
assert_eq!(
result,
ConditionExpr::Or(vec![
ConditionExpr::And(vec![ConditionExpr::Ref(931), ConditionExpr::Ref(932)]),
ConditionExpr::Ref(100),
])
);
}
#[test]
fn test_parse_ub_multiple_references() {
let ub1_expr = ConditionParser::parse("[931]").unwrap().unwrap();
let ub2_expr = ConditionParser::parse("[932]").unwrap().unwrap();
let mut ub_map = BTreeMap::new();
ub_map.insert("UB1".to_string(), ub1_expr);
ub_map.insert("UB2".to_string(), ub2_expr);
let result = ConditionParser::parse_with_ub("X [UB1] ∧ [UB2]", &ub_map)
.unwrap()
.unwrap();
assert_eq!(
result,
ConditionExpr::And(vec![ConditionExpr::Ref(931), ConditionExpr::Ref(932)])
);
}
#[test]
fn test_parse_multi_line_alternatives_are_or() {
let status = "Muss [315] ∧ [707]\nSoll [8] ∧ [301] ∧ [707]";
let result = ConditionParser::parse(status).unwrap().unwrap();
assert_eq!(
result,
ConditionExpr::Or(vec![
ConditionExpr::And(vec![ConditionExpr::Ref(315), ConditionExpr::Ref(707)]),
ConditionExpr::And(vec![
ConditionExpr::Ref(8),
ConditionExpr::Ref(301),
ConditionExpr::Ref(707),
]),
])
);
}
#[test]
fn test_parse_multi_line_with_crlf() {
let result = ConditionParser::parse("Muss [10]\r\nSoll [20]")
.unwrap()
.unwrap();
assert_eq!(
result,
ConditionExpr::Or(vec![ConditionExpr::Ref(10), ConditionExpr::Ref(20)])
);
}
#[test]
fn test_parse_single_line_unchanged() {
let result = ConditionParser::parse("Muss [315] ∧ [707]")
.unwrap()
.unwrap();
assert_eq!(
result,
ConditionExpr::And(vec![ConditionExpr::Ref(315), ConditionExpr::Ref(707)])
);
}
}