use std::sync::Arc;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum SymbolicStep {
RenameSort {
old: Arc<str>,
new: Arc<str>,
},
RenameOp {
old: Arc<str>,
new: Arc<str>,
},
AddSort(Arc<str>),
DropSort(Arc<str>),
AddOp(Arc<str>),
DropOp(Arc<str>),
Opaque(String),
}
#[must_use]
pub fn simplify_steps(steps: Vec<SymbolicStep>) -> Vec<SymbolicStep> {
let mut current = steps;
for _ in 0..100 {
let next = apply_rules(¤t);
if next == current {
break;
}
current = next;
}
current
}
fn apply_rules(steps: &[SymbolicStep]) -> Vec<SymbolicStep> {
let mut result = Vec::with_capacity(steps.len());
let mut i = 0;
while i < steps.len() {
if i + 1 < steps.len() {
if let Some(replacement) = try_pairwise_rule(&steps[i], &steps[i + 1]) {
result.extend(replacement);
i += 2;
continue;
}
}
result.push(steps[i].clone());
i += 1;
}
result
}
fn try_pairwise_rule(a: &SymbolicStep, b: &SymbolicStep) -> Option<Vec<SymbolicStep>> {
match (a, b) {
(
SymbolicStep::RenameSort {
old: a_old,
new: a_new,
},
SymbolicStep::RenameSort {
old: b_old,
new: b_new,
},
) if a_new == b_old && b_new == a_old => Some(vec![]),
(
SymbolicStep::RenameSort {
old: a_old,
new: a_new,
},
SymbolicStep::RenameSort {
old: b_old,
new: b_new,
},
) if a_new == b_old => Some(vec![SymbolicStep::RenameSort {
old: Arc::clone(a_old),
new: Arc::clone(b_new),
}]),
(
SymbolicStep::RenameOp {
old: a_old,
new: a_new,
},
SymbolicStep::RenameOp {
old: b_old,
new: b_new,
},
) if a_new == b_old && b_new == a_old => Some(vec![]),
(
SymbolicStep::RenameOp {
old: a_old,
new: a_new,
},
SymbolicStep::RenameOp {
old: b_old,
new: b_new,
},
) if a_new == b_old => Some(vec![SymbolicStep::RenameOp {
old: Arc::clone(a_old),
new: Arc::clone(b_new),
}]),
(SymbolicStep::AddSort(added), SymbolicStep::DropSort(dropped)) if added == dropped => {
Some(vec![])
}
(SymbolicStep::AddOp(added), SymbolicStep::DropOp(dropped)) if added == dropped => {
Some(vec![])
}
_ => None,
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
fn rename_sort(old: &str, new: &str) -> SymbolicStep {
SymbolicStep::RenameSort {
old: Arc::from(old),
new: Arc::from(new),
}
}
fn rename_op(old: &str, new: &str) -> SymbolicStep {
SymbolicStep::RenameOp {
old: Arc::from(old),
new: Arc::from(new),
}
}
#[test]
fn inverse_cancellation_sorts() {
let steps = vec![rename_sort("A", "B"), rename_sort("B", "A")];
let simplified = simplify_steps(steps);
assert!(simplified.is_empty());
}
#[test]
fn rename_fusion_sorts() {
let steps = vec![rename_sort("A", "B"), rename_sort("B", "C")];
let simplified = simplify_steps(steps);
assert_eq!(simplified, vec![rename_sort("A", "C")]);
}
#[test]
fn inverse_cancellation_ops() {
let steps = vec![rename_op("f", "g"), rename_op("g", "f")];
let simplified = simplify_steps(steps);
assert!(simplified.is_empty());
}
#[test]
fn rename_fusion_ops() {
let steps = vec![rename_op("f", "g"), rename_op("g", "h")];
let simplified = simplify_steps(steps);
assert_eq!(simplified, vec![rename_op("f", "h")]);
}
#[test]
fn add_drop_cancellation_sort() {
let steps = vec![
SymbolicStep::AddSort(Arc::from("X")),
SymbolicStep::DropSort(Arc::from("X")),
];
let simplified = simplify_steps(steps);
assert!(simplified.is_empty());
}
#[test]
fn add_drop_cancellation_op() {
let steps = vec![
SymbolicStep::AddOp(Arc::from("f")),
SymbolicStep::DropOp(Arc::from("f")),
];
let simplified = simplify_steps(steps);
assert!(simplified.is_empty());
}
#[test]
fn opaque_steps_preserved() {
let steps = vec![SymbolicStep::Opaque("custom".into()), rename_sort("A", "B")];
let simplified = simplify_steps(steps.clone());
assert_eq!(simplified, steps);
}
#[test]
fn multi_step_fusion_chain() {
let steps = vec![
rename_sort("A", "B"),
rename_sort("B", "C"),
rename_sort("C", "D"),
];
let simplified = simplify_steps(steps);
assert_eq!(simplified, vec![rename_sort("A", "D")]);
}
#[test]
fn non_adjacent_steps_not_cancelled() {
let steps = vec![
rename_sort("A", "B"),
SymbolicStep::Opaque("barrier".into()),
rename_sort("B", "A"),
];
let simplified = simplify_steps(steps.clone());
assert_eq!(simplified, steps);
}
}