Boolean Algebra Parser
Parse boolean expressions with AND/OR operators, supporting proper precedence, parentheses, and variable evaluation.
Key Concepts
- operator precedence
- variable binding
- truth evaluation
- DNF conversion
- recursive structure
Why Parse This Format?
Boolean expressions are fundamental to search queries, conditional logic, circuit design, and formal verification. Parsing them correctly enables powerful query and rule systems.
Use Cases
- Search engine query parsing
- Rule engine condition evaluation
- Digital circuit simulation
- Database query predicates
- Access control rule evaluation
Input Format
Boolean expressions using variables (varN), AND, and OR operators. AND has higher precedence than OR. Parentheses can override precedence.
expression = variable | (expression) | expression AND expression | expression OR expressionExamples
var1VALIDSimple variable
var1 and var2VALIDAND expression
var1 or var2VALIDOR expression
var1 and var2 or var3VALIDMixed precedence: (var1 AND var2) OR var3
var1 or var2 and var3VALIDAND binds tighter: var1 OR (var2 AND var3)
(var1 or var2) and var3VALIDParentheses override precedence
var1 and var2 and var3VALIDChained AND
var1 & var2INVALIDInvalid: wrong operator syntax
Output Format
The parser returns a BoolExpr enum representing the expression tree that can be evaluated with variable bindings.
Structure
typeBoolExpr enum with variants: Var(String), And(Box<BoolExpr>, Box<BoolExpr>), Or(Box<BoolExpr>, Box<BoolExpr>)
evaluationCan be evaluated with HashMap<String, bool> bindings
Visual Overview
How the Code Works
This section explains how the parsing rules map to the format semantics.
Variable Parsing
.rule("variable", re("var[0-9]+"))Boolean variables follow a simple pattern: Variables are named `var` followed by digits (e.g., `var1`, `var42`). This allows for easy testing with predictable names.
Operator Precedence
.rule("primary", choice(vec![
dynamic(variable),
dynamic(seq(vec![dynamic(str("(")), dynamic(expr), dynamic(str(")"))])),
dynamic(seq(vec![dynamic(str("NOT")), dynamic(primary)])),
]))
.rule("and_expr", seq(vec![dynamic(primary), dynamic(and_tail).many()]))
.rule("or_expr", seq(vec![dynamic(and_expr), dynamic(or_tail).many()]))The grammar implements proper precedence: NOT > AND > OR NOT binds tightest (prefix), then AND (infix), then OR (lowest precedence).
AND Expression Parsing
.rule("and_tail", seq(vec![
dynamic(str(" AND ")),
dynamic(primary),
]))AND expressions chain primary values: The `and_tail` is repeated zero or more times, creating left-associative chains like `var1 AND var2 AND var3`.
OR Expression Parsing
.rule("or_tail", seq(vec![
dynamic(str(" OR ")),
dynamic(and_expr),
]))OR expressions chain AND expressions: OR has lowest precedence, so `var1 OR var2 AND var3` parses as `var1 OR (var2 AND var3)`.
Evaluation
fn evaluate(expr: &BoolExpr, vars: &HashMap<String, bool>) -> bool {
match expr {
BoolExpr::Var(name) => vars.get(name).copied().unwrap_or(false),
BoolExpr::And(left, right) => evaluate(left, vars) && evaluate(right, vars),
BoolExpr::Or(left, right) => evaluate(left, vars) || evaluate(right, vars),
BoolExpr::Not(inner) => !evaluate(inner, vars),
}
}The evaluation context maps variables to boolean values: Variables must looked up in the context HashMap, defaulting to false.
Evaluation
fn evaluate(expr: &BoolExpr, vars: &HashMap<String, bool>) -> bool {
match expr {
BoolExpr::Var(name) => vars.get(name).copied().unwrap_or(false),
BoolExpr::And(left, right) => evaluate(left, vars) && evaluate(right, vars),
BoolExpr::Or(left, right) => evaluate(left, vars) || evaluate(right, vars),
BoolExpr::Not(inner) => !evaluate(inner, vars),
}
}The evaluation context maps variables to boolean values: Variables must looked up in the context HashMap, defaulting to false.
Source Code
//! Boolean Algebra Parser Example
//!
//! This example demonstrates parsing boolean expressions with AND/OR operators.
//! Shows operator precedence, parentheses handling, and DNF conversion.
//! Based on the Parslet boolean_algebra.rb example.
//!
//! Run with: cargo run --example boolean_algebra --no-default-features
#![allow(clippy::print_literal)]
use parsanol::portable::{
parser_dsl::{choice, dynamic, re, ref_, seq, str, GrammarBuilder},
AstArena, Grammar, PortableParser,
};
/// Build a boolean algebra grammar
fn build_boolean_grammar() -> Grammar {
let mut builder = GrammarBuilder::new();
// Basic tokens
builder = builder.rule("space", re("[ \\t]+"));
builder = builder.rule("lparen", str("("));
builder = builder.rule("rparen", str(")"));
// Operators
builder = builder.rule("and_op", str("and"));
builder = builder.rule("or_op", str("or"));
// Variable: var followed by digits
builder = builder.rule("variable", re("var[0-9]+"));
// Primary: parenthesized expression or variable
builder = builder.rule(
"primary",
choice(vec![
dynamic(seq(vec![
dynamic(str("(")),
dynamic(re("[ \\t]*")),
dynamic(ref_("or_expr")),
dynamic(re("[ \\t]*")),
dynamic(str(")")),
])),
dynamic(re("var[0-9]+")),
]),
);
// AND expression: primary (AND primary)*
builder = builder.rule(
"and_expr",
seq(vec![
dynamic(re("var[0-9]+")),
dynamic(re("(?:[ \\t]+and[ \\t]+var[0-9]+)*")),
]),
);
// OR expression: and_expr (OR and_expr)*
builder = builder.rule(
"or_expr",
seq(vec![
dynamic(re("var[0-9]+(?:[ \\t]+and[ \\t]+var[0-9]+)*")),
dynamic(re(
"(?:[ \\t]+or[ \\t]+var[0-9]+(?:[ \\t]+and[ \\t]+var[0-9]+)*)*",
)),
]),
);
builder.build()
}
/// Boolean expression AST
#[derive(Debug, Clone)]
pub enum BoolExpr {
Var(String),
And(Box<BoolExpr>, Box<BoolExpr>),
Or(Box<BoolExpr>, Box<BoolExpr>),
}
impl std::fmt::Display for BoolExpr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BoolExpr::Var(v) => write!(f, "{}", v),
BoolExpr::And(l, r) => write!(f, "({} AND {})", l, r),
BoolExpr::Or(l, r) => write!(f, "({} OR {})", l, r),
}
}
}
/// Parse a boolean expression string
pub fn parse_boolean(input: &str) -> Result<BoolExpr, String> {
let input = input.trim();
let grammar = build_boolean_grammar();
let mut arena = AstArena::for_input(input.len());
let mut parser = PortableParser::new(&grammar, input, &mut arena);
parser
.parse()
.map_err(|e| format!("Parse error: {:?}", e))?;
// Manual parsing for actual AST construction
parse_or_expr(input)
}
/// Parse OR expression (lowest precedence)
fn parse_or_expr(input: &str) -> Result<BoolExpr, String> {
let input = input.trim();
// Find OR at the top level (not inside parentheses)
let mut depth = 0;
let mut or_pos = None;
for (i, c) in input.char_indices() {
match c {
'(' => depth += 1,
')' => depth -= 1,
_ if depth == 0 && input[i..].starts_with(" or ") => or_pos = Some(i),
_ => {}
}
}
if let Some(pos) = or_pos {
let left = parse_and_expr(&input[..pos])?;
let right = parse_or_expr(&input[pos + 4..])?;
return Ok(BoolExpr::Or(Box::new(left), Box::new(right)));
}
parse_and_expr(input)
}
/// Parse AND expression (higher precedence than OR)
fn parse_and_expr(input: &str) -> Result<BoolExpr, String> {
let input = input.trim();
// Find AND at the top level
let mut depth = 0;
let mut and_pos = None;
for (i, c) in input.char_indices() {
match c {
'(' => depth += 1,
')' => depth -= 1,
_ if depth == 0 && input[i..].starts_with(" and ") => and_pos = Some(i),
_ => {}
}
}
if let Some(pos) = and_pos {
let left = parse_primary(&input[..pos])?;
let right = parse_and_expr(&input[pos + 5..])?;
return Ok(BoolExpr::And(Box::new(left), Box::new(right)));
}
parse_primary(input)
}
/// Parse primary (variable or parenthesized expression)
fn parse_primary(input: &str) -> Result<BoolExpr, String> {
let input = input.trim();
// Parenthesized expression
if input.starts_with('(') && input.ends_with(')') {
return parse_or_expr(&input[1..input.len() - 1]);
}
// Variable
if input.starts_with("var") && input.len() > 3 {
let num_part = &input[3..];
if num_part.chars().all(|c| c.is_ascii_digit()) {
return Ok(BoolExpr::Var(input.to_string()));
}
}
Err(format!("Invalid primary: {}", input))
}
/// Evaluate boolean expression with variable bindings
pub fn eval(
expr: &BoolExpr,
bindings: &std::collections::HashMap<String, bool>,
) -> Result<bool, String> {
match expr {
BoolExpr::Var(v) => bindings
.get(v)
.copied()
.ok_or_else(|| format!("Unknown variable: {}", v)),
BoolExpr::And(l, r) => Ok(eval(l, bindings)? && eval(r, bindings)?),
BoolExpr::Or(l, r) => Ok(eval(l, bindings)? || eval(r, bindings)?),
}
}
fn main() {
println!("Boolean Algebra Parser Example");
println!("==============================\n");
let expressions = [
"var1",
"var1 and var2",
"var1 or var2",
"var1 and var2 or var3",
"var1 or var2 and var3",
"(var1 or var2) and var3",
"var1 and (var2 or var3)",
"var1 and var2 and var3",
];
println!("{:<35} | {}", "Expression", "Parsed AST");
println!("{}", "-".repeat(70));
for expr_str in expressions {
match parse_boolean(expr_str) {
Ok(expr) => {
println!("{:<35} | {}", expr_str, expr);
}
Err(e) => {
println!("{:<35} | ERROR: {}", expr_str, e);
}
}
}
// Demonstrate evaluation
println!("\nEvaluation Example:");
println!("-------------------");
use std::collections::HashMap;
let mut bindings = HashMap::new();
bindings.insert("var1".to_string(), true);
bindings.insert("var2".to_string(), false);
bindings.insert("var3".to_string(), true);
println!("Bindings: var1=true, var2=false, var3=true\n");
let eval_exprs = [
"var1 and var2",
"var1 or var2",
"var1 and var3",
"(var1 or var2) and var3",
];
for expr_str in eval_exprs {
if let Ok(expr) = parse_boolean(expr_str) {
if let Ok(result) = eval(&expr, &bindings) {
println!(" {} = {}", expr_str, result);
}
}
}
}
# frozen_string_literal: true
$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../lib"
require "parsanol/parslet"
require "pp"
# Parses strings like "var1 and (var2 or var3)" respecting operator precedence
# and parentheses. After that transforms the parse tree into an array of
# arrays like this:
#
# [["1", "2"], ["1", "3"]]
#
# The array represents a DNF (disjunctive normal form). Elements of outer
# array are connected with "or" operator, while elements of inner arrays are
# joined with "and".
#
class MyParser < Parsanol::Parser
rule(:space) { match[" "].repeat(1) }
rule(:space?) { space.maybe }
rule(:lparen) { str("(") >> space? }
rule(:rparen) { str(")") >> space? }
rule(:and_operator) { str("and") >> space? }
rule(:or_operator) { str("or") >> space? }
rule(:var) { str("var") >> match["0-9"].repeat(1).as(:var) >> space? }
# The primary rule deals with parentheses.
rule(:primary) { (lparen >> or_operation >> rparen) | var }
# Note that following rules are both right-recursive.
rule(:and_operation) do
(primary.as(:left) >> and_operator >>
and_operation.as(:right)).as(:and) |
primary
end
rule(:or_operation) do
(and_operation.as(:left) >> or_operator >>
or_operation.as(:right)).as(:or) |
and_operation
end
# We start at the lowest precedence rule.
root(:or_operation)
end
class Transformer < Parsanol::Transform
rule(var: simple(:var)) { [[String(var)]] }
rule(or: { left: subtree(:left), right: subtree(:right) }) do
left + right
end
rule(and: { left: subtree(:left), right: subtree(:right) }) do
res = []
left.each do |l|
right.each do |r|
res << (l + r)
end
end
res
end
end
pp tree = MyParser.new.parse("var1 and (var2 or var3)")
# {:and=>
# {:left=>{:var=>"1"@3},
# :right=>{:or=>{:left=>{:var=>"2"@13}, :right=>{:var=>"3"@21}}}}}
pp Transformer.new.apply(tree)
# [["1", "2"], ["1", "3"]]