Skip to main content

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}