Precedence Calculator
Parse and evaluate mathematical expressions using precedence climbing for operator precedence handling.
Key Concepts
- precedence climbing
- left vs right associativity
- variable assignment
- expression evaluation
Why Parse This Format?
Precedence climbing is a classic algorithm for parsing infix expressions with proper operator precedence.
Use Cases
- Calculator applications
- Expression evaluation in spreadsheets
- Query language parsers
- Configuration file expressions
Input Format
Variable assignments with mathematical expressions, one per line.
identifier = expressionExamples
a = 1 + 2 * 3VALIDAssignment with precedence (multiplication first)
b = 10 - 3 - 2VALIDLeft-associative subtraction
c = 100VALIDSimple integer assignment
Output Format
Variable bindings mapping names to evaluated values.
Structure
bindingsMap of variable names to integer values
Visual Overview
How the Code Works
This section explains how the parsing rules map to the format semantics.
Grammar Construction
let mut builder = GrammarBuilder::new();
builder = builder.rule("digit", re(r"[0-9]"));
builder = builder.rule("integer", re(r"[0-9]+"));
builder = builder.rule("identifier", re(r"[a-z][a-zA-Z0-9]*"));The grammar is built using the GrammarBuilder DSL: Basic rules define atoms: digits, integers, and identifiers.
InfixBuilder for Precedence
let expr_atom = InfixBuilder::new()
.primary(ref_("primary"))
.op("*", 2, Assoc::Left)
.op("/", 2, Assoc::Left)
.op("+", 1, Assoc::Right)
.op("-", 1, Assoc::Right)
.build(&mut builder);The InfixBuilder handles operator precedence automatically: Higher precedence numbers bind tighter. Multiplication (2) binds before addition (1).
Association Types
Assoc::Left // 10 - 3 - 2 = (10 - 3) - 2 = 5
Assoc::Right // 2 ^ 3 ^ 2 = 2 ^ (3 ^ 2) = 512Left and right associativity are specified: Left-associative operators group left-to-right; right-associative group right-to-left.
Expression AST
pub enum Expr {
Integer(i64),
BinaryOp {
left: Box<Expr>,
op: String,
right: Box<Expr>,
},
}The AST represents binary operations recursively: Box enables recursive structure; operation strings allow multiple operators.
Variable Bindings
pub struct Bindings {
vars: HashMap<String, i64>,
}A simple HashMap stores variable values: Assignments add to the map; lookups read from it.
Evaluation
fn eval(expr: &Expr) -> i64 {
match expr {
Expr::Integer(n) => *n,
Expr::BinaryOp { left, op, right } => {
let l = eval(left);
let r = eval(right);
match op.as_str() {
"+" => l + r,
"-" => l - r,
"*" => l * r,
"/" => l / r,
_ => panic!("Unknown operator"),
}
}
}
}Recursive evaluation computes expression values: Post-order traversal evaluates children before applying operator.
Evaluation
fn eval(expr: &Expr) -> i64 {
match expr {
Expr::Integer(n) => *n,
Expr::BinaryOp { left, op, right } => {
let l = eval(left);
let r = eval(right);
match op.as_str() {
"+" => l + r,
"-" => l - r,
"*" => l * r,
"/" => l / r,
_ => panic!("Unknown operator"),
}
}
}
}Recursive evaluation computes expression values: Post-order traversal evaluates children before applying operator.
Source Code
//! Precedence Calculator Example
//!
//! Demonstrates precedence climbing for infix expression parsing.
//! Shows how to parse variable assignments with proper operator precedence.
//!
//! Run with: cargo run --example prec-calc --no-default-features
#![allow(clippy::print_literal)]
use std::collections::HashMap;
/// Expression AST
#[derive(Debug, Clone)]
pub enum Expr {
Integer(i64),
BinaryOp {
left: Box<Expr>,
op: String,
right: Box<Expr>,
},
}
/// Variable bindings
#[derive(Debug, Clone, Default)]
pub struct Bindings {
vars: HashMap<String, i64>,
}
impl Bindings {
pub fn new() -> Self {
Self::default()
}
pub fn set(&mut self, name: &str, value: i64) {
self.vars.insert(name.to_string(), value);
}
pub fn get(&self, name: &str) -> Option<i64> {
self.vars.get(name).copied()
}
}
/// Evaluate an expression
fn eval(expr: &Expr) -> i64 {
match expr {
Expr::Integer(n) => *n,
Expr::BinaryOp { left, op, right } => {
let l = eval(left);
let r = eval(right);
match op.as_str() {
"+" => l + r,
"-" => l - r,
"*" => l * r,
"/" => l / r,
_ => panic!("Unknown operator: {}", op),
}
}
}
}
/// Parse and evaluate a simple expression (manual parsing for demo)
fn parse_expression(input: &str) -> Result<Expr, String> {
let input = input.trim();
// Try to parse as integer first
if let Ok(n) = input.parse::<i64>() {
return Ok(Expr::Integer(n));
}
// Binary operators (low to high precedence)
for op in ["+", "-", "*", "/"] {
// Find rightmost for left-assoc, leftmost for right-assoc
let idx = if op == "+" {
input.rfind(op)
} else {
input.find(op).or_else(|| input.rfind(op))
};
if let Some(idx) = idx {
let before = &input[..idx];
let after = &input[idx + 1..];
if !before.is_empty() && !after.is_empty() {
let left = parse_expression(before)?;
let right = parse_expression(after)?;
return Ok(Expr::BinaryOp {
left: Box::new(left),
op: op.to_string(),
right: Box::new(right),
});
}
}
}
Err(format!("Could not parse: {}", input))
}
/// Parse and evaluate assignments
fn parse_assignments(input: &str) -> Bindings {
let mut bindings = Bindings::new();
for line in input.lines() {
let line = line.trim();
if line.is_empty() {
continue;
}
if let Some(eq_pos) = line.find('=') {
let name = line[..eq_pos].trim();
let expr_str = line[eq_pos + 1..].trim();
match parse_expression(expr_str) {
Ok(expr) => {
let value = eval(&expr);
bindings.set(name, value);
println!(" {} = {} (from {})", name, value, expr_str);
}
Err(e) => {
println!(" Error parsing '{}': {}", expr_str, e);
}
}
}
}
bindings
}
fn main() {
println!("Precedence Calculator Example");
println!("==============================");
println!();
let input = r#"
a = 1
b = 2
c = 3 * 25
d = 100 + 3*4
e = 10 - 3 - 2
"#;
println!("Input:");
println!("{}", input);
println!();
println!("Parsing and evaluating:");
let bindings = parse_assignments(input);
println!();
println!("Final bindings:");
for (name, value) in bindings.vars.iter() {
println!(" {} = {}", name, value);
}
// Demonstrate precedence
println!();
println!("Precedence demonstration:");
let test_exprs = [
("1 + 2 * 3", 7), // 1 + (2 * 3) = 7
("2 * 3 + 4", 10), // (2 * 3) + 4 = 10
("10 - 3 - 2", 5), // (10 - 3) - 2 = 5 (left assoc)
("100 + 3 * 4", 112), // 100 + (3 * 4) = 112
];
for (expr_str, expected) in test_exprs {
match parse_expression(expr_str) {
Ok(expr) => {
let result = eval(&expr);
let status = if result == expected { "OK" } else { "FAIL" };
println!(
" {} = {} (expected {}) {}",
expr_str, result, expected, status
);
}
Err(e) => {
println!(" {} -> Error: {}", expr_str, e);
}
}
}
}
# frozen_string_literal: true
# A demonstration of the new precedence climbing infix expression parser.
$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../lib"
require "pp"
require "rspec"
require "parsanol/parslet"
require "parsanol/rig/rspec"
require "parsanol/convenience"
class InfixExpressionParser < Parsanol::Parser
root :variable_assignment_list
rule(:space) { match[" "] }
def cts(atom)
atom >> space.repeat
end
def infix(*)
Infix.new(*)
end
# This is the heart of the infix expression parser: real simple definitions
# for all the pieces we need.
rule(:mul_op) { cts match["*/"] }
rule(:add_op) { cts match["+-"] }
rule(:digit) { match["0-9"] }
rule(:integer) { cts digit.repeat(1).as(:int) }
rule(:expression) do
infix_expression(integer,
[mul_op, 2, :left],
[add_op, 1, :right])
end
# And now adding variable assignments to that, just to a) demonstrate this
# embedded in a bigger parser, and b) make the example interesting.
rule(:variable_assignment_list) do
variable_assignment.repeat(1)
end
rule(:variable_assignment) do
identifier.as(:ident) >> equal_sign >> expression.as(:exp) >> eol
end
rule(:identifier) do
cts(match["a-z"] >> match["a-zA-Z0-9"].repeat)
end
rule(:equal_sign) do
cts str("=")
end
rule(:eol) do
cts(str("\n")) | any.absent?
end
end
class InfixInterpreter < Parsanol::Transform
rule(int: simple(:int)) { Integer(int) }
rule(ident: simple(:ident), exp: simple(:result)) do |d|
d[:doc][d[:ident].to_s.strip.to_sym] = d[:result]
end
rule(l: simple(:l), o: /^\*/, r: simple(:r)) { l * r }
rule(l: simple(:l), o: /^\+/, r: simple(:r)) { l + r }
end
input = <<~ASSIGNMENTS
a = 1
b = 2
c = 3 * 25
d = 100 + 3*4
ASSIGNMENTS
puts input
int_tree = InfixExpressionParser.new.parse_with_debug(input)
bindings = {}
InfixInterpreter.new.apply(int_tree, doc: bindings)
pp bindings