oxilean_std/abstract_rewriting/types.rs
1//! Types for Abstract Rewriting Systems (ARS) and term rewriting.
2
3use std::collections::HashMap;
4
5/// A conditional rewrite rule over a term type `T`.
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct RewriteRule<T: Clone + Eq> {
8 /// Left-hand side pattern.
9 pub lhs: T,
10 /// Right-hand side result.
11 pub rhs: T,
12 /// Human-readable name for the rule.
13 pub name: String,
14 /// Conditions that must hold (as terms that must reduce to True/normal form).
15 pub conditions: Vec<T>,
16}
17
18impl<T: Clone + Eq> RewriteRule<T> {
19 /// Creates a new unconditional rewrite rule.
20 pub fn new(name: impl Into<String>, lhs: T, rhs: T) -> Self {
21 RewriteRule {
22 lhs,
23 rhs,
24 name: name.into(),
25 conditions: Vec::new(),
26 }
27 }
28
29 /// Creates a new conditional rewrite rule.
30 pub fn conditional(name: impl Into<String>, lhs: T, rhs: T, conditions: Vec<T>) -> Self {
31 RewriteRule {
32 lhs,
33 rhs,
34 name: name.into(),
35 conditions,
36 }
37 }
38
39 /// Returns whether this rule has conditions.
40 pub fn is_conditional(&self) -> bool {
41 !self.conditions.is_empty()
42 }
43}
44
45/// The strategy used when applying rewrite rules.
46#[derive(Debug, Clone, PartialEq, Eq)]
47pub enum RewriteStrategy {
48 /// Reduce innermost redexes first (call-by-value).
49 Innermost,
50 /// Reduce outermost redexes first (call-by-name / lazy).
51 Outermost,
52 /// Leftmost-innermost: standard reduction order for functional languages.
53 LeftmostInnermost,
54 /// Leftmost-outermost: used in lambda calculus normal order.
55 LeftmostOutermost,
56 /// Reduce all redexes simultaneously.
57 Parallel,
58}
59
60/// A term rewriting system with a collection of rules and a reduction strategy.
61#[derive(Debug, Clone)]
62pub struct RewriteSystem<T: Clone + Eq> {
63 /// The set of rewrite rules.
64 pub rules: Vec<RewriteRule<T>>,
65 /// The reduction strategy to use.
66 pub strategy: RewriteStrategy,
67}
68
69impl<T: Clone + Eq> RewriteSystem<T> {
70 /// Creates a new rewrite system with the given strategy.
71 pub fn new(strategy: RewriteStrategy) -> Self {
72 RewriteSystem {
73 rules: Vec::new(),
74 strategy,
75 }
76 }
77
78 /// Adds a rule to the system.
79 pub fn add_rule(&mut self, rule: RewriteRule<T>) {
80 self.rules.push(rule);
81 }
82}
83
84/// The result of a rewriting computation.
85#[derive(Debug, Clone)]
86pub struct RewriteResult<T> {
87 /// The final (possibly normal) term.
88 pub term: T,
89 /// Trace of (rule_name, resulting_term) pairs.
90 pub steps: Vec<(String, T)>,
91 /// Whether the term reached a normal form (no more rules apply).
92 pub converged: bool,
93}
94
95impl<T> RewriteResult<T> {
96 /// Returns the number of rewrite steps taken.
97 pub fn num_steps(&self) -> usize {
98 self.steps.len()
99 }
100}
101
102/// A concrete tree-structured term.
103#[derive(Debug, Clone, PartialEq, Eq, Hash)]
104pub enum TermTree {
105 /// A leaf node (constant or variable). Variables are uppercase by convention.
106 Leaf(String),
107 /// A node with a symbol and children (function application).
108 Node {
109 /// The function symbol.
110 symbol: String,
111 /// The children (arguments).
112 children: Vec<TermTree>,
113 },
114}
115
116impl TermTree {
117 /// Convenience constructor for a leaf node.
118 pub fn leaf(s: impl Into<String>) -> Self {
119 TermTree::Leaf(s.into())
120 }
121
122 /// Convenience constructor for a node.
123 pub fn node(symbol: impl Into<String>, children: Vec<TermTree>) -> Self {
124 TermTree::Node {
125 symbol: symbol.into(),
126 children,
127 }
128 }
129
130 /// Returns true if this term is a variable (leaf with uppercase first char).
131 pub fn is_variable(&self) -> bool {
132 match self {
133 TermTree::Leaf(s) => s.starts_with(|c: char| c.is_uppercase()),
134 TermTree::Node { .. } => false,
135 }
136 }
137
138 /// Returns true if this is a leaf node.
139 pub fn is_leaf(&self) -> bool {
140 matches!(self, TermTree::Leaf(_))
141 }
142
143 /// Returns the root symbol, if any.
144 pub fn root_symbol(&self) -> Option<&str> {
145 match self {
146 TermTree::Leaf(s) => Some(s),
147 TermTree::Node { symbol, .. } => Some(symbol),
148 }
149 }
150}
151
152impl std::fmt::Display for TermTree {
153 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
154 match self {
155 TermTree::Leaf(s) => write!(f, "{s}"),
156 TermTree::Node { symbol, children } => {
157 if children.is_empty() {
158 write!(f, "{symbol}")
159 } else {
160 let args: Vec<String> = children.iter().map(|c| c.to_string()).collect();
161 write!(f, "{symbol}({})", args.join(", "))
162 }
163 }
164 }
165 }
166}
167
168/// A critical pair representing a potential confluence failure.
169#[derive(Debug, Clone, PartialEq, Eq)]
170pub struct CriticalPair {
171 /// Name of the first rule involved.
172 pub rule1: String,
173 /// Name of the second rule involved.
174 pub rule2: String,
175 /// The term where the overlap occurs.
176 pub overlap: TermTree,
177 /// Result after applying rule1.
178 pub result1: TermTree,
179 /// Result after applying rule2.
180 pub result2: TermTree,
181}
182
183impl CriticalPair {
184 /// Returns true if this critical pair is trivial (results are equal).
185 pub fn is_trivial(&self) -> bool {
186 self.result1 == self.result2
187 }
188}
189
190/// The result of a confluence analysis.
191#[derive(Debug, Clone, PartialEq, Eq)]
192pub enum ConfluenceResult {
193 /// The system is confluent (all critical pairs are joinable).
194 Confluent,
195 /// The system is not confluent; the given critical pair witnesses this.
196 NotConfluent(CriticalPair),
197 /// Confluence could not be determined (e.g., non-terminating system).
198 Unknown,
199}
200
201/// The normalization order used when comparing term weights.
202#[derive(Debug, Clone, PartialEq, Eq)]
203pub enum NormalizationOrder {
204 /// Compare subterms left to right.
205 LeftToRight,
206 /// Prefer terms with greater weight first.
207 GreaterFirst,
208 /// Weighted path order using symbol weights.
209 WeightedPath {
210 /// Weight map from symbol names to numeric weights.
211 weights: HashMap<String, u32>,
212 },
213}