Skip to content

Commit

Permalink
Merge pull request #53 from zao111222333/master
Browse files Browse the repository at this point in the history
Support `Conditional` ternary operator
  • Loading branch information
daemontus authored Apr 3, 2024
2 parents da57ff9 + cd9e9c1 commit 40b2f72
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 16 deletions.
9 changes: 9 additions & 0 deletions src/boolean_expression/_impl_boolean_expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ impl Display for BooleanExpression {
Xor(l, r) => write!(f, "({} ^ {})", l, r),
Imp(l, r) => write!(f, "({} => {})", l, r),
Iff(l, r) => write!(f, "({} <=> {})", l, r),
Cond(cond, then_expr, else_expr) => {
write!(f, "({} ? {} : {})", cond, then_expr, else_expr)
}
}
}
}
Expand Down Expand Up @@ -66,6 +69,12 @@ impl BddVariableSet {
let right = self.safe_eval_expression(r)?;
Some(left.iff(&right))
}
Cond(cond, then_expr, else_expr) => {
let cond = self.safe_eval_expression(cond)?;
let then_expr = self.safe_eval_expression(then_expr)?;
let else_expr = self.safe_eval_expression(else_expr)?;
Some(Bdd::if_then_else(&cond, &then_expr, &else_expr))
}
}
}

Expand Down
82 changes: 67 additions & 15 deletions src/boolean_expression/_impl_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ enum ExprToken {
Xor, // '^'
Imp, // '=>'
Iff, // '<=>'
Colon, // ':'
QuestionMark, // '?'
Id(String), // 'variable'
Tokens(Vec<ExprToken>), // A block of tokens inside parentheses
}
Expand All @@ -47,6 +49,8 @@ fn tokenize_group(data: &mut Peekable<Chars>, top_level: bool) -> Result<Vec<Exp
'&' => output.push(ExprToken::And),
'|' => output.push(ExprToken::Or),
'^' => output.push(ExprToken::Xor),
':' => output.push(ExprToken::Colon),
'?' => output.push(ExprToken::QuestionMark),
'=' => {
if Some('>') == data.next() {
output.push(ExprToken::Imp);
Expand Down Expand Up @@ -128,13 +132,40 @@ fn iff(data: &[ExprToken]) -> Result<Box<BooleanExpression>, String> {
fn imp(data: &[ExprToken]) -> Result<Box<BooleanExpression>, String> {
let imp_token = index_of_first(data, ExprToken::Imp);
Ok(if let Some(imp_token) = imp_token {
Box::new(Imp(or(&data[..imp_token])?, imp(&data[(imp_token + 1)..])?))
Box::new(Imp(
cond(&data[..imp_token])?,
imp(&data[(imp_token + 1)..])?,
))
} else {
or(data)?
cond(data)?
})
}

/// **(internal)** Recursive parsing step 3: extract `|` operators.
/// **(internal)** Recursive parsing step 3: extract `cond ? then_expr : else_expr` operators.
///
/// + Vaild: `(cond1 ? then_expr1 : else_expr1) + (cond2 ? then_expr2 : else_expr2)`
///
/// + Vaild: `(cond1 ? then_expr1 : else_expr1) + cond2 ? then_expr2 : else_expr2`
///
/// + Vaild: `cond1 ? then_expr1 : else_expr1 + (cond2 ? then_expr2 : else_expr2)`
///
/// + Invalid: `cond1 ? then_expr1 : cond2 ? then_expr2 : else_expr2`
fn cond(data: &[ExprToken]) -> Result<Box<BooleanExpression>, String> {
let question_token = index_of_first(data, ExprToken::QuestionMark);
let colon_token = index_of_first(data, ExprToken::Colon);
match (question_token, colon_token) {
(None, None) => or(data),
(Some(question_token), Some(colon_token)) => Ok(Box::new(Cond(
or(&data[..question_token])?,
or(&data[(question_token + 1)..colon_token])?,
or(&data[(colon_token + 1)..])?,
))),
(None, Some(_)) => Err(format!("Expected `?` but only found `:`.")),
(Some(_), None) => Err(format!("Expected `:` but only found `?`.")),
}
}

/// **(internal)** Recursive parsing step 4: extract `|` operators.
fn or(data: &[ExprToken]) -> Result<Box<BooleanExpression>, String> {
let or_token = index_of_first(data, ExprToken::Or);
Ok(if let Some(or_token) = or_token {
Expand All @@ -144,7 +175,7 @@ fn or(data: &[ExprToken]) -> Result<Box<BooleanExpression>, String> {
})
}

/// **(internal)** Recursive parsing step 4: extract `&` operators.
/// **(internal)** Recursive parsing step 5: extract `&` operators.
fn and(data: &[ExprToken]) -> Result<Box<BooleanExpression>, String> {
let and_token = index_of_first(data, ExprToken::And);
Ok(if let Some(and_token) = and_token {
Expand All @@ -157,7 +188,7 @@ fn and(data: &[ExprToken]) -> Result<Box<BooleanExpression>, String> {
})
}

/// **(internal)** Recursive parsing step 5: extract `^` operators.
/// **(internal)** Recursive parsing step 6: extract `^` operators.
fn xor(data: &[ExprToken]) -> Result<Box<BooleanExpression>, String> {
let xor_token = index_of_first(data, ExprToken::Xor);
Ok(if let Some(xor_token) = xor_token {
Expand All @@ -170,7 +201,7 @@ fn xor(data: &[ExprToken]) -> Result<Box<BooleanExpression>, String> {
})
}

/// **(internal)** Recursive parsing step 6: extract terminals and negations.
/// **(internal)** Recursive parsing step 7: extract terminals and negations.
fn terminal(data: &[ExprToken]) -> Result<Box<BooleanExpression>, String> {
if data.is_empty() {
Err("Expected formula, found nothing :(".to_string())
Expand Down Expand Up @@ -207,15 +238,16 @@ mod tests {
#[test]
fn parse_boolean_formula_basic() {
let inputs = vec![
"v_1+{14}", // just a variable name with fancy symbols
"!v_1", // negation
"true", // true
"false", // false
"(v_1 & v_2)", // and
"(v_1 | v_2)", // or
"(v_1 ^ v_2)", // xor
"(v_1 => v_2)", // imp
"(v_1 <=> v_2)", // iff
"v_1+{14}", // just a variable name with fancy symbols
"!v_1", // negation
"true", // true
"false", // false
"(v_1 & v_2)", // and
"(cond ? then_expr : else_expr)", // cond
"(v_1 | v_2)", // or
"(v_1 ^ v_2)", // xor
"(v_1 => v_2)", // imp
"(v_1 <=> v_2)", // iff
];
for input in inputs {
assert_eq!(
Expand Down Expand Up @@ -258,6 +290,20 @@ mod tests {
"(a <=> (b <=> c))",
format!("{}", parse_boolean_expression("a <=> b <=> c").unwrap())
);
assert_eq!(
"((a ? b : c) ? d : e)",
format!(
"{}",
parse_boolean_expression("(a ? b : c) ? d : e").unwrap()
)
);
assert_eq!(
"(a ? b : (c ? d : e))",
format!(
"{}",
parse_boolean_expression("a ? b : (c ? d : e)").unwrap()
)
);
}

#[test]
Expand Down Expand Up @@ -319,6 +365,12 @@ mod tests {
parse_boolean_expression("a & (b))").unwrap();
}

#[test]
#[should_panic]
fn parse_boolean_formula_invalid_parentheses_5() {
parse_boolean_expression("a ? b : c ? d : e").unwrap();
}

#[test]
#[should_panic]
fn parse_boolean_formula_invalid_formula_1() {
Expand Down
6 changes: 6 additions & 0 deletions src/boolean_expression/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,10 @@ pub enum BooleanExpression {
Xor(Box<BooleanExpression>, Box<BooleanExpression>),
Imp(Box<BooleanExpression>, Box<BooleanExpression>),
Iff(Box<BooleanExpression>, Box<BooleanExpression>),
/// cond ? then_expr : else_expr
Cond(
Box<BooleanExpression>,
Box<BooleanExpression>,
Box<BooleanExpression>,
),
}
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ mod _test_util;

/// **(internal)** Characters that cannot appear in the variable name
/// (based on possible tokens in a boolean expression).
const NOT_IN_VAR_NAME: [char; 9] = ['!', '&', '|', '^', '=', '<', '>', '(', ')'];
const NOT_IN_VAR_NAME: [char; 11] = ['!', '&', '|', '^', '=', '<', '>', '(', ')', '?', ':'];

/// An array-based encoding of the binary decision diagram implementing basic logical operations.
///
Expand Down

0 comments on commit 40b2f72

Please sign in to comment.