Skip to main content

liberty_io/
boolean.rs

1// Copyright (c) 2021-2021 Thomas Kramer.
2// SPDX-FileCopyrightText: 2022 Thomas Kramer <code@tkramer.ch>
3//
4// SPDX-License-Identifier: AGPL-3.0-or-later
5
6//! Parse boolean expressions as used in the liberty format.
7//!
8//! # Example
9//! ```
10//! use liberty_io::boolean::*;
11//!
12//! // Expression to be parsed.
13//! let expression = "A & (B | C)";
14//!
15//! // Construct expected expression.
16//! let a = BooleanExpr::Var(0);
17//! let b = BooleanExpr::Var(1);
18//! let c = BooleanExpr::Var(2);
19//! let expected_expr = a & (b | c);
20//!
21//! let result = parse_boolean_function(expression.chars().peekable());
22//!
23//! let (parsed_expression, name_map) = result.expect("parsing failed");
24//!
25//! let expected_name_map = vec![
26//!     "A".to_string(), "B".to_string(), "C".to_string()
27//! ];
28//!
29//! assert_eq!(parsed_expression, expected_expr);
30//! assert_eq!(name_map, expected_name_map);
31//!
32//! ```
33
34use itertools::{Itertools, PeekingNext};
35use libreda_stream_parser::*;
36use std::ops::*;
37
38/// Boolean expression.
39#[derive(Clone, Debug, Eq, PartialEq, Hash)]
40pub enum BooleanExpr<Var> {
41    /// Boolean value `False`.
42    False,
43    /// Boolean value `True`.
44    True,
45    /// Variable in an expression.
46    Var(Var),
47    /// Unary negation of an expression.
48    Neg(Box<Self>),
49    /// Binary Boolean `AND` of two expressions.
50    And(Box<Self>, Box<Self>),
51    /// Binary Boolean `OR` of two expressions.
52    Or(Box<Self>, Box<Self>),
53    /// Binary Boolean `XOR` of two expressions.
54    Xor(Box<Self>, Box<Self>),
55}
56
57impl<V> From<bool> for BooleanExpr<V> {
58    fn from(value: bool) -> Self {
59        match value {
60            false => BooleanExpr::False,
61            true => BooleanExpr::True,
62        }
63    }
64}
65
66impl<V> BooleanExpr<V> {
67    /// Evaluate the expression.
68    /// Variable are passed with the callback function `value_fn`.
69    /// The type `B` usually represents Boolean logic values. For example `bool` can be used.
70    ///
71    /// Note that `B::default()` is required to return the Boolean value `false`.
72    ///
73    /// # Example
74    /// ```
75    /// use liberty_io::boolean::*;
76    ///
77    /// // Create a simple boolean expression.
78    /// let a = BooleanExpr::Var(0);
79    /// let b = BooleanExpr::Var(1);
80    /// let expr = a ^ b;
81    ///
82    /// let values = [true, false];
83    /// assert_eq!(expr.eval(&|var| values[*var]), true);
84    ///
85    /// let values = [true, true];
86    /// assert_eq!(expr.eval(&|var| values[*var]), false);
87    /// ```
88    pub fn eval<B>(&self, value_fn: &impl Fn(&V) -> B) -> B
89    where
90        B: Not<Output = B> + BitAnd<Output = B> + BitOr<Output = B> + BitXor<Output = B> + Default,
91    {
92        match self {
93            BooleanExpr::False => B::default(),
94            BooleanExpr::True => !B::default(),
95            BooleanExpr::Var(v) => value_fn(v),
96            BooleanExpr::Neg(expr) => !expr.eval(value_fn),
97            BooleanExpr::And(a, b) => a.eval(value_fn) & b.eval(value_fn),
98            BooleanExpr::Or(a, b) => a.eval(value_fn) | b.eval(value_fn),
99            BooleanExpr::Xor(a, b) => a.eval(value_fn) ^ b.eval(value_fn),
100        }
101    }
102
103    /// Substitute variables in the expression. Can also be used to change the data-type of the variables.
104    /// # Example
105    /// ```
106    /// use liberty_io::boolean::*;
107    ///
108    /// // Create a simple boolean expression.
109    /// let a = BooleanExpr::Var(0);
110    /// let b = BooleanExpr::Var(1);
111    /// let expr = a & b;
112    ///
113    /// // Define the variable mapping used for the substitution.
114    /// let substitution = |var: &usize| -> String {
115    ///    match var {
116    ///       0 => "a".into(),
117    ///       1 => "b".into(),
118    ///       _ => panic!("no such variable")
119    ///    }
120    /// };
121    ///
122    /// // Apply the substitution.
123    /// let substituted = expr.substitute_variable_names(&substitution);
124    /// ```
125    pub fn substitute_variable_names<V2>(
126        &self,
127        substitution: &impl Fn(&V) -> V2,
128    ) -> BooleanExpr<V2> {
129        self.substitute_variables(&|v| BooleanExpr::Var(substitution(v)))
130    }
131
132    /// Substitute all variables by an expression.
133    ///
134    /// # Example
135    /// Replace some variables by a constant:
136    /// ```
137    /// use liberty_io::boolean::*;
138    ///
139    /// // Create a simple boolean expression.
140    /// let a = BooleanExpr::Var("a");
141    /// let b = BooleanExpr::Var("b");
142    /// let expr = a & b;
143    ///
144    /// // Define the variable mapping used for the substitution.
145    /// let substitution = |&var: &&'static str| -> BooleanExpr<&'static str> {
146    ///    match var {
147    ///       "a" => BooleanExpr::False, // Replace "a".
148    ///       var => BooleanExpr::Var(var) // Don't do anything else for all other variables.
149    ///    }
150    /// };
151    ///
152    /// // Apply the substitution.
153    /// let substituted = expr.substitute_variables(&substitution);
154    /// ```
155    pub fn substitute_variables<V2>(
156        &self,
157        substitution: &impl Fn(&V) -> BooleanExpr<V2>,
158    ) -> BooleanExpr<V2> {
159        match self {
160            BooleanExpr::False => BooleanExpr::False,
161            BooleanExpr::True => BooleanExpr::True,
162            BooleanExpr::Var(v) => substitution(v),
163            BooleanExpr::Neg(expr) => {
164                BooleanExpr::Neg(Box::new(expr.substitute_variables(substitution)))
165            }
166            BooleanExpr::And(a, b) => BooleanExpr::And(
167                Box::new(a.substitute_variables(substitution)),
168                Box::new(b.substitute_variables(substitution)),
169            ),
170            BooleanExpr::Or(a, b) => BooleanExpr::Or(
171                Box::new(a.substitute_variables(substitution)),
172                Box::new(b.substitute_variables(substitution)),
173            ),
174            BooleanExpr::Xor(a, b) => BooleanExpr::Xor(
175                Box::new(a.substitute_variables(substitution)),
176                Box::new(b.substitute_variables(substitution)),
177            ),
178        }
179    }
180}
181
182impl<V> std::fmt::Display for BooleanExpr<V>
183where
184    V: std::fmt::Display,
185{
186    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
187        match self {
188            BooleanExpr::False => write!(f, "false"),
189            BooleanExpr::True => write!(f, "true"),
190            BooleanExpr::Var(v) => write!(f, "{}", v),
191            BooleanExpr::Neg(expr) => match expr.as_ref() {
192                v @ BooleanExpr::False | v @ BooleanExpr::True => write!(f, "!{}", v),
193                BooleanExpr::Var(v) => write!(f, "!{}", v), // Ommit braces.
194                _ => write!(f, "!({})", expr),
195            },
196            BooleanExpr::And(expr1, expr2) => write!(f, "({} & {})", expr1, expr2),
197            BooleanExpr::Or(expr1, expr2) => write!(f, "({} | {})", expr1, expr2),
198            BooleanExpr::Xor(expr1, expr2) => write!(f, "({} ^ {})", expr1, expr2),
199        }
200    }
201}
202
203#[test]
204fn test_eval_boolean_expr() {
205    let a = BooleanExpr::Var(0);
206    let b = BooleanExpr::Var(1);
207
208    let expr = a & b;
209
210    let values = vec![false, true];
211    assert!(!expr.eval(&|v| values[*v]));
212
213    let values = vec![true, true];
214    assert!(expr.eval(&|v| values[*v]));
215
216    // Apply the expression to a bit vector.
217    let values = vec![0b1010, 0b1100];
218    assert_eq!(expr.eval(&|v| values[*v]), 0b1010 & 0b1100);
219}
220
221#[test]
222fn test_format_then_parse() {
223    let [a, b, c, d] = std::array::from_fn(|i| {
224        BooleanExpr::Var(i + 2 /* avoid using 0 and 1 as variable names*/)
225    });
226
227    let expr = !a & (b | c) ^ d;
228
229    let formatted = format!("{}", &expr);
230
231    let (parsed_expr, var_names) = parse_boolean_function(formatted.chars().peekable()).unwrap();
232    let parsed_expr = parsed_expr.substitute_variable_names(&|v| v + 2);
233
234    assert_eq!(expr, parsed_expr);
235}
236
237impl<V> Not for BooleanExpr<V> {
238    type Output = Self;
239
240    fn not(self) -> Self::Output {
241        BooleanExpr::Neg(Box::new(self))
242    }
243}
244
245impl<V> BitAnd for BooleanExpr<V> {
246    type Output = Self;
247
248    fn bitand(self, rhs: Self) -> Self::Output {
249        Self::And(Box::new(self), Box::new(rhs))
250    }
251}
252
253impl<V> BitOr for BooleanExpr<V> {
254    type Output = Self;
255
256    fn bitor(self, rhs: Self) -> Self::Output {
257        Self::Or(Box::new(self), Box::new(rhs))
258    }
259}
260
261impl<V> BitXor for BooleanExpr<V> {
262    type Output = Self;
263
264    fn bitxor(self, rhs: Self) -> Self::Output {
265        Self::Xor(Box::new(self), Box::new(rhs))
266    }
267}
268
269impl<V> Add for BooleanExpr<V> {
270    type Output = Self;
271
272    fn add(self, rhs: Self) -> Self::Output {
273        self.bitor(rhs)
274    }
275}
276
277impl<V> Mul for BooleanExpr<V> {
278    type Output = Self;
279
280    fn mul(self, rhs: Self) -> Self::Output {
281        self.bitand(rhs)
282    }
283}
284
285fn is_terminal_char(c: char) -> bool {
286    c.is_whitespace()
287        || matches!(
288            c,
289            '(' | ')' | '!' | '~' | '+' | '&' | '|' | '*' | '^' | '\''
290        )
291}
292
293struct LibertyBooleanExpLexer {}
294
295impl Lexer for LibertyBooleanExpLexer {
296    type Char = char;
297
298    fn consume_next_token(
299        &mut self,
300        input: &mut (impl Iterator<Item = Self::Char> + itertools::PeekingNext),
301        mut output: impl FnMut(Self::Char),
302    ) -> Result<(), ParserError<Self::Char>> {
303        loop {
304            // Skip whitespace.
305            let _n = input.peeking_take_while(|c| c.is_whitespace()).count();
306
307            // Look ahead.
308            if let Some(c) = input.next() {
309                match c {
310                    '#' => {
311                        // Skip comments.
312                        input
313                            .peeking_take_while(|&c| c != '\n' && c != '\r')
314                            .count();
315                    }
316                    '/' => {
317                        if input.peeking_next(|&c| c == '*').is_some() {
318                            // Skip comment.
319
320                            loop {
321                                // Consume until next '*'.
322                                input.peeking_take_while(|&c| c != '*').count();
323                                if input.next() != Some('*') {
324                                    break; // End of file.
325                                }
326
327                                if input.peeking_next(|&c| c == '/').is_some() {
328                                    // End of comment.
329                                    break;
330                                }
331                            }
332                        } else if input.peeking_next(|&c| c == '/').is_some() {
333                            // Skip comment.
334
335                            // Consume until next '\n'.
336                            input.peeking_take_while(|&c| c != '\n').count();
337                        } else {
338                            output(c);
339                        }
340                    }
341                    '\\' => {
342                        if input.peeking_next(|&c| c == '\\').is_some() {
343                            // Ignore masked newlines.
344                            output('\\');
345                        } else if input.peeking_next(|&c| c == '\n').is_some() {
346                            // Ignore masked newlines.
347                            input.next();
348                        } else if input.peeking_next(|&c| c == '\r').is_some() {
349                            // Ignore masked newlines.
350                            input.next();
351                            if input.peeking_next(|&c| c == '\n').is_some() {
352                                input.next();
353                            }
354                        } else {
355                            output('\\');
356                        }
357                    }
358                    _ => {
359                        // Normal token.
360                        let mut prev = Some(c);
361                        output(c);
362
363                        if is_terminal_char(c) {
364                            return Ok(());
365                        }
366
367                        while let Some(c) = input.peeking_next(|&c| !is_terminal_char(c)) {
368                            if prev != Some('\\') && (c.is_whitespace() || is_terminal_char(c)) {
369                                // Abort on unmasked whitespace or terminal character.
370                                break;
371                            }
372
373                            output(c);
374                            prev = Some(c);
375                        }
376                        return Ok(());
377                    }
378                }
379            } else {
380                return Ok(());
381            }
382        }
383    }
384}
385
386#[test]
387fn test_tokenize_expression() {
388    let expr = r#"
389        (AA & B)|C1+D1*E1+!f+~g
390    "#;
391
392    let mut tk = tokenize(expr.chars(), LibertyBooleanExpLexer {});
393
394    // Convert to strings.
395    let token_stream = tk.map(|token| {
396        let s: String = token.iter().collect();
397        s
398    });
399
400    let tokens: Vec<_> = token_stream.collect();
401
402    assert_eq!(
403        tokens,
404        vec![
405            "(", "AA", "&", "B", ")", "|", "C1", "+", "D1", "*", "E1", "+", "!", "f", "+", "~", "g"
406        ]
407    );
408}
409
410/// Parse a boolean expression.
411/// Return the parsed expression and a mapping of variable IDs to variable names stored as a `Vec`.
412pub fn parse_boolean_function(
413    chars: impl Iterator<Item = char>,
414) -> Result<(BooleanExpr<usize>, Vec<String>), ParserError<char>> {
415    let mut tk = tokenize(chars.peekable(), LibertyBooleanExpLexer {});
416
417    let mut var_names: Vec<String> = vec![];
418
419    tk.advance(); // Initialize the streaming iterator.
420
421    let expr = parse_or_expr(&mut tk, &mut var_names)?;
422
423    Ok((expr, var_names))
424}
425
426fn parse_or_expr<I: Iterator<Item = char> + PeekingNext>(
427    tk: &mut Tokenized<I, LibertyBooleanExpLexer>,
428    names: &mut Vec<String>,
429) -> Result<BooleanExpr<usize>, ParserError<char>> {
430    let mut or_expr = parse_and_expr(tk, names)?;
431    while tk.current_token_ref().is_some() {
432        if tk.test_str("+")? || tk.test_str("|")? {
433            let rhs = parse_and_expr(tk, names)?;
434            or_expr = BooleanExpr::Or(Box::new(or_expr), Box::new(rhs));
435        } else {
436            break;
437        }
438    }
439
440    Ok(or_expr)
441}
442
443fn parse_and_expr<I: Iterator<Item = char> + PeekingNext>(
444    tk: &mut Tokenized<I, LibertyBooleanExpLexer>,
445    names: &mut Vec<String>,
446) -> Result<BooleanExpr<usize>, ParserError<char>> {
447    let mut and_expr = parse_xor_expr(tk, names)?;
448
449    while let Some(t) = tk.current_token_ref() {
450        let t0 = t[0];
451        // "A & B", "A * B", "A B"
452        if tk.test_str("&")? || tk.test_str("*")? || !"^|+)".contains(t0) {
453            let rhs = parse_xor_expr(tk, names)?;
454            and_expr = BooleanExpr::And(Box::new(and_expr), Box::new(rhs));
455        } else {
456            break;
457        }
458    }
459    Ok(and_expr)
460}
461
462fn parse_xor_expr<I: Iterator<Item = char> + PeekingNext>(
463    tk: &mut Tokenized<I, LibertyBooleanExpLexer>,
464    names: &mut Vec<String>,
465) -> Result<BooleanExpr<usize>, ParserError<char>> {
466    let mut xor_expr = parse_atom(tk, names)?;
467    while tk.current_token_ref().is_some() {
468        if tk.test_str("^")? {
469            let rhs = parse_atom(tk, names)?;
470            xor_expr = BooleanExpr::Xor(Box::new(xor_expr), Box::new(rhs));
471        } else {
472            break;
473        }
474    }
475    Ok(xor_expr)
476}
477
478fn parse_atom<I: Iterator<Item = char> + PeekingNext>(
479    tk: &mut Tokenized<I, LibertyBooleanExpLexer>,
480    names: &mut Vec<String>,
481) -> Result<BooleanExpr<usize>, ParserError<char>> {
482    let mut atom = if tk.test_str("0")? {
483        BooleanExpr::False
484    } else if tk.test_str("1")? {
485        BooleanExpr::True
486    } else if tk.test_str("!")? || tk.test_str("~")? {
487        let atom = parse_atom(tk, names)?;
488        BooleanExpr::Neg(Box::new(atom))
489    } else if tk.test_str("(")? {
490        let or_expr = parse_or_expr(tk, names)?;
491        tk.expect_str(")")?;
492        or_expr
493    } else {
494        // Variable name.
495        let var_name = tk.take_str()?;
496
497        let var_id = {
498            // Convert the variable name to an integer ID.
499            // Find name in the list of existing names.
500            let var_id = names
501                .iter()
502                .enumerate()
503                .find(|(idx, n)| n == &&var_name)
504                .map(|(idx, _)| idx);
505
506            // Insert the variable name if it does not exist yet.
507            var_id.unwrap_or_else(|| {
508                names.push(var_name);
509                names.len() - 1
510            })
511        };
512
513        BooleanExpr::Var(var_id)
514    };
515
516    while tk.current_token_ref().is_some() && tk.test_str("'")? {
517        atom = BooleanExpr::Neg(Box::new(atom))
518    }
519    Ok(atom)
520}
521
522#[test]
523fn test_parse_boolean_expression() {
524    fn v(var_id: usize) -> BooleanExpr<usize> {
525        BooleanExpr::Var(var_id)
526    }
527
528    let testcases = [
529        //
530        ("0", BooleanExpr::False),
531        ("1", BooleanExpr::True),
532        ("A", v(0)),
533        ("(A)", v(0)),
534        ("\t (\nA  ) ", v(0)),
535        ("!A", !v(0)),
536        ("!!A", !!v(0)),
537        ("~A", !v(0)),
538        ("A'", !v(0)),
539        ("A''", !!v(0)),
540        ("A B", v(0) & v(1)),
541        ("x x", v(0) & v(0)),
542        ("(A)(B)", v(0) & v(1)),
543        ("A(B)", v(0) & v(1)),
544        ("(A)B", v(0) & v(1)),
545        ("A&B", v(0) & v(1)),
546        ("A*B", v(0) & v(1)),
547        ("A+B", v(0) | v(1)),
548        ("A|B", v(0) | v(1)),
549        ("(A)|(B)", v(0) | v(1)),
550        ("A B C", v(0) & v(1) & v(2)),
551        ("A (B C)", v(0) & (v(1) & v(2))),
552        ("A | B C", v(0) | v(1) & v(2)),
553        ("A B | C", v(0) & v(1) | v(2)),
554        ("!A B' | ~C", !v(0) & !v(1) | !v(2)),
555        (
556            "(A' & ~B | !C & D & (E|A)')",
557            !v(0) & !v(1) | !v(2) & v(3) & !(v(4) | v(0)),
558        ),
559    ];
560
561    for (expr, expected) in testcases {
562        let result = parse_boolean_function(expr.chars().peekable());
563
564        let (parsed, name_map) = result.unwrap();
565
566        assert_eq!(parsed, expected)
567    }
568}
569
570#[test]
571fn test_substitute_variables() {
572    // Create a simple boolean expression.
573    let a = BooleanExpr::Var(0);
574    let b = BooleanExpr::Var(1);
575    let c = BooleanExpr::Var(2);
576    let expr = a & (b | c);
577
578    // Define the variable mapping used for the substitution.
579    let substitution = |var: &usize| -> String {
580        match var {
581            0 => "a".into(),
582            1 => "b".into(),
583            2 => "c".into(),
584            _ => panic!("no such variable"),
585        }
586    };
587
588    // Apply the substitution.
589    let substituted = expr.substitute_variable_names(&substitution);
590
591    // Construct a reference expression with strings as variables.
592    let a = BooleanExpr::Var("a".into());
593    let b = BooleanExpr::Var("b".into());
594    let c = BooleanExpr::Var("c".into());
595    let reference_expr = a & (b | c);
596
597    // Check for correctness.
598    assert_eq!(reference_expr, substituted);
599}