1use egg::{Analysis, DidMerge, EGraph, Extractor, Id, Language, RecExpr, Runner};
7use omena_lawvere::{
8 AbstractDomainTagV0, LawvereSaturationExecutionV0, summarize_lawvere_saturation_execution_v0,
9};
10
11use crate::{
12 CssRewriteLanguage, EggRewriteCandidateV0, EggRewriteExecutionV0, MdlExtractionCostV0,
13 blocked_execution, decide_egg_rewrite, rewrite_rules_for_pass,
14};
15
16#[derive(Debug, Default, Clone)]
17pub struct LawvereAnalysis;
18
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct LawvereAnalysisDataV0 {
21 pub abstract_domain_tags: Vec<AbstractDomainTagV0>,
22 pub enode_count: usize,
23 pub contains_terminal_projection: bool,
24}
25
26impl LawvereAnalysisDataV0 {
27 fn from_tag(tag: AbstractDomainTagV0) -> Self {
28 Self {
29 abstract_domain_tags: vec![tag],
30 enode_count: 1,
31 contains_terminal_projection: tag == AbstractDomainTagV0::TerminalEmission,
32 }
33 }
34}
35
36impl Analysis<CssRewriteLanguage> for LawvereAnalysis {
37 type Data = LawvereAnalysisDataV0;
38
39 fn make(
40 egraph: &mut EGraph<CssRewriteLanguage, Self>,
41 enode: &CssRewriteLanguage,
42 _id: Id,
43 ) -> Self::Data {
44 let tag = match enode {
45 CssRewriteLanguage::Num(_)
46 | CssRewriteLanguage::Symbol(_)
47 | CssRewriteLanguage::Add(_)
48 | CssRewriteLanguage::Sub(_)
49 | CssRewriteLanguage::Mul(_)
50 | CssRewriteLanguage::Div(_)
51 | CssRewriteLanguage::Calc(_)
52 | CssRewriteLanguage::Unit(_) => AbstractDomainTagV0::TokenValue,
53 CssRewriteLanguage::Is(_)
54 | CssRewriteLanguage::Where(_)
55 | CssRewriteLanguage::List(_) => AbstractDomainTagV0::SelectorShape,
56 };
57 let mut data = LawvereAnalysisDataV0::from_tag(tag);
58 for child in enode.children() {
59 let child_data = &egraph[*child].data;
60 merge_domain_tags(
61 &mut data.abstract_domain_tags,
62 &child_data.abstract_domain_tags,
63 );
64 data.contains_terminal_projection |= child_data.contains_terminal_projection;
65 }
66 data
67 }
68
69 fn merge(&mut self, a: &mut Self::Data, b: Self::Data) -> DidMerge {
70 let before = a.clone();
71 merge_domain_tags(&mut a.abstract_domain_tags, &b.abstract_domain_tags);
72 a.enode_count = a.enode_count.max(b.enode_count);
73 a.contains_terminal_projection |= b.contains_terminal_projection;
74 DidMerge(before != *a, *a != b)
75 }
76
77 fn allow_ematching_cycles(&self) -> bool {
78 false
79 }
80}
81
82fn merge_domain_tags(target: &mut Vec<AbstractDomainTagV0>, source: &[AbstractDomainTagV0]) {
83 for tag in source {
84 if !target.contains(tag) {
85 target.push(*tag);
86 }
87 }
88 target.sort();
89}
90
91pub fn execute_egg_rewrite_with_lawvere_analysis(
92 candidate: EggRewriteCandidateV0,
93) -> (EggRewriteExecutionV0, LawvereSaturationExecutionV0) {
94 let decision = decide_egg_rewrite(candidate.clone());
95 if !decision.accepted {
96 let execution = blocked_execution(candidate.clone(), decision.blocked_reason);
97 let saturation =
98 summarize_lawvere_saturation_execution_v0(candidate.pass_id, 0, 0, 0, 0, false);
99 return (execution, saturation);
100 }
101
102 let expression = match candidate.before.parse::<RecExpr<CssRewriteLanguage>>() {
103 Ok(expression) => expression,
104 Err(_) => {
105 let execution = blocked_execution(
106 candidate.clone(),
107 Some("rewrite expression could not parse"),
108 );
109 let saturation =
110 summarize_lawvere_saturation_execution_v0(candidate.pass_id, 0, 0, 0, 0, false);
111 return (execution, saturation);
112 }
113 };
114 let Some(rules) = rewrite_rules_for_pass::<LawvereAnalysis>(candidate.pass_id) else {
115 let execution = blocked_execution(
116 candidate.clone(),
117 Some("pass is not managed by omena-transform-egg"),
118 );
119 let saturation =
120 summarize_lawvere_saturation_execution_v0(candidate.pass_id, 0, 0, 0, 0, false);
121 return (execution, saturation);
122 };
123
124 let iteration_limit = 8;
125 let runner = Runner::default()
126 .with_expr(&expression)
127 .with_iter_limit(iteration_limit)
128 .run(rules.as_slice());
129 let root = runner.roots[0];
130 let extractor = Extractor::new(&runner.egraph, MdlExtractionCostV0::default_ast_size());
131 let (_, extracted) = extractor.find_best(root);
132 let after = extracted.to_string();
133 let after_matches_candidate = after == candidate.after;
134 let iteration_count = runner.iterations.len();
135 let eclass_count = runner.egraph.number_of_classes();
136 let enode_count = runner.egraph.total_size();
137 let saturation = summarize_lawvere_saturation_execution_v0(
138 candidate.pass_id,
139 iteration_limit,
140 iteration_count,
141 eclass_count,
142 enode_count,
143 after_matches_candidate,
144 );
145
146 let execution = EggRewriteExecutionV0 {
147 schema_version: "0",
148 product: "omena-transform-egg.execution",
149 pass_id: candidate.pass_id,
150 accepted: after_matches_candidate,
151 blocked_reason: (!after_matches_candidate)
152 .then_some("lawvere analysis extraction did not match candidate output"),
153 before: candidate.before,
154 after,
155 expected_after: candidate.after,
156 after_matches_candidate,
157 engine: "egg+lawvere-analysis",
158 iteration_limit,
159 iteration_count,
160 eclass_count,
161 enode_count,
162 mdl_bits: None,
163 mdl_residual_bits: None,
164 mdl_unit: None,
165 };
166 (execution, saturation)
167}
168
169#[cfg(test)]
170mod tests {
171 use omena_transform_cst::TransformPassKind;
172
173 use crate::{EggRewriteCandidateV0, EggRewriteProofV0};
174
175 use super::*;
176
177 #[test]
178 fn lawvere_analysis_fills_parallel_egg_analysis_slot() {
179 let (execution, saturation) =
180 execute_egg_rewrite_with_lawvere_analysis(EggRewriteCandidateV0 {
181 pass_id: TransformPassKind::CalcReduction.id(),
182 before: "(calc (+ (unit 1 px) (unit 2 px)))".to_string(),
183 after: "(unit 3 px)".to_string(),
184 proof: EggRewriteProofV0 {
185 specificity_preserved: false,
186 computed_value_preserved: true,
187 provenance_preserved: true,
188 cascade_safe_witness: "same-unit calc arithmetic preserves computed value"
189 .to_string(),
190 },
191 });
192
193 assert!(execution.accepted);
194 assert_eq!(execution.engine, "egg+lawvere-analysis");
195 assert_eq!(saturation.schema_version, "0");
196 assert_eq!(saturation.layer_marker, "enriched-algebraic");
197 assert_eq!(saturation.analysis_slot, "LawvereAnalysis");
198 assert!(saturation.original_unit_analysis_path_preserved);
199 assert_eq!(saturation.differential_fixture_count, 10);
200 }
201}