Skip to main content

omena_transform_egg/
lib.rs

1//! Optional e-graph rewrite boundary for Omena CSS transforms.
2//!
3//! Selector, shorthand, and computed-value rewrites are the current e-graph candidates.
4//! This crate keeps their proof requirements explicit without forcing an
5//! e-graph dependency into the core transform path.
6
7use std::fmt::Write as _;
8
9use egg::{
10    Analysis, Applier, EGraph, Extractor, Id, Pattern, PatternAst, RecExpr, Rewrite, Runner, Subst,
11    Symbol, Var, define_language, rewrite as egg_rewrite,
12};
13use omena_evidence_graph::ObligationFamilyIdV0;
14use omena_parser::StyleDialect;
15use omena_transform_cst::TransformPassKind;
16use omena_transform_passes::{
17    TransformPassPlanV0, collect_stale_vendor_prefix_removal_proof_candidates_from_source,
18    plan_transform_passes,
19};
20use serde::Serialize;
21
22mod mdl_cost;
23pub use mdl_cost::*;
24#[cfg(feature = "lawvere-saturation")]
25mod lawvere_analysis;
26#[cfg(feature = "lawvere-saturation")]
27pub use lawvere_analysis::*;
28
29define_language! {
30    enum CssRewriteLanguage {
31        Num(i64),
32        Symbol(Symbol),
33        "+" = Add([Id; 2]),
34        "-" = Sub([Id; 2]),
35        "*" = Mul([Id; 2]),
36        "/" = Div([Id; 2]),
37        "calc" = Calc(Id),
38        "unit" = Unit([Id; 2]),
39        "is" = Is(Id),
40        "where" = Where(Id),
41        "list" = List([Id; 2]),
42        "decl" = Declaration([Id; 3]),
43        "stale-prefix-decl" = StalePrefixDeclaration([Id; 4]),
44        "box1" = Box1(Id),
45        "box2" = Box2([Id; 2]),
46        "box3" = Box3([Id; 3]),
47        "box4" = Box4([Id; 4]),
48    }
49}
50
51#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
52#[serde(rename_all = "camelCase")]
53pub struct EggRewriteProofV0 {
54    pub specificity_preserved: bool,
55    #[serde(skip_serializing)]
56    obligation_family: ObligationFamilyIdV0,
57    pub computed_value_preserved: bool,
58    pub provenance_preserved: bool,
59    pub cascade_safe_witness: String,
60}
61
62impl EggRewriteProofV0 {
63    pub fn new(
64        specificity_preserved: bool,
65        obligation_family: ObligationFamilyIdV0,
66        provenance_preserved: bool,
67        cascade_safe_witness: impl Into<String>,
68    ) -> Self {
69        Self {
70            specificity_preserved,
71            obligation_family,
72            computed_value_preserved: obligation_family.preserves_computed_value(),
73            provenance_preserved,
74            cascade_safe_witness: cascade_safe_witness.into(),
75        }
76    }
77
78    pub const fn obligation_family(&self) -> ObligationFamilyIdV0 {
79        self.obligation_family
80    }
81}
82
83#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
84#[serde(rename_all = "camelCase")]
85pub struct EggRewriteCandidateV0 {
86    pub pass_id: &'static str,
87    pub before: String,
88    pub after: String,
89    pub proof: EggRewriteProofV0,
90}
91
92#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
93#[serde(rename_all = "camelCase")]
94pub struct EggRewriteDecisionV0 {
95    pub schema_version: &'static str,
96    pub product: &'static str,
97    pub pass_id: &'static str,
98    pub accepted: bool,
99    pub blocked_reason: Option<&'static str>,
100}
101
102#[derive(Debug, Clone, PartialEq, Serialize)]
103#[serde(rename_all = "camelCase")]
104pub struct EggRewriteExecutionV0 {
105    pub schema_version: &'static str,
106    pub product: &'static str,
107    pub pass_id: &'static str,
108    pub accepted: bool,
109    pub blocked_reason: Option<&'static str>,
110    pub before: String,
111    pub after: String,
112    pub expected_after: String,
113    pub after_matches_candidate: bool,
114    pub engine: &'static str,
115    pub iteration_limit: usize,
116    pub iteration_count: usize,
117    pub eclass_count: usize,
118    pub enode_count: usize,
119    #[serde(skip_serializing_if = "Option::is_none")]
120    pub mdl_bits: Option<f64>,
121    #[serde(skip_serializing_if = "Option::is_none")]
122    pub mdl_residual_bits: Option<f64>,
123    #[serde(skip_serializing_if = "Option::is_none")]
124    pub mdl_unit: Option<&'static str>,
125}
126
127#[derive(Debug, Clone, PartialEq, Serialize)]
128#[serde(rename_all = "camelCase")]
129pub struct EggRewriteSourceWitnessV0 {
130    pub pass_id: &'static str,
131    pub source_kind: &'static str,
132    pub byte_offset: usize,
133    pub css_before: String,
134    pub css_after: String,
135    pub execution: EggRewriteExecutionV0,
136}
137
138#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
139#[serde(rename_all = "camelCase")]
140pub struct TransformEggBoundarySummaryV0 {
141    pub schema_version: &'static str,
142    pub product: &'static str,
143    pub managed_pass_ids: Vec<&'static str>,
144    pub optional_engine: &'static str,
145    pub proof_obligations: Vec<&'static str>,
146    pub planner_surface: &'static str,
147}
148
149#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
150#[serde(rename_all = "camelCase")]
151pub struct TransformEggPlanV0 {
152    pub schema_version: &'static str,
153    pub product: &'static str,
154    pub requested_pass_ids: Vec<&'static str>,
155    pub planned_pass_ids: Vec<&'static str>,
156    pub pass_plan: TransformPassPlanV0,
157}
158
159#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
160#[serde(rename_all = "camelCase")]
161pub struct ContextualEqSatScaffoldV0 {
162    pub schema_version: &'static str,
163    pub product: &'static str,
164    pub claim_level: &'static str,
165    pub scaffold_kind: &'static str,
166    pub execution_view: &'static str,
167    pub current_engine: &'static str,
168    pub egg_engine_ready: bool,
169    pub egglog_binding_ready: bool,
170    pub external_datalog_host_ready: bool,
171    pub three_view_fusion_ready: bool,
172    pub theorem_claimed: bool,
173    pub public_safety_claim_ready: bool,
174    pub modal_witness_product: &'static str,
175    pub modal_bridge_claim_level: &'static str,
176    pub paper_substrate_claim_level: &'static str,
177    pub managed_pass_ids: Vec<&'static str>,
178    pub substrate_products: Vec<&'static str>,
179    pub supported_claims: Vec<&'static str>,
180    pub deferred_claims: Vec<&'static str>,
181}
182
183#[derive(Debug, Clone, Copy)]
184enum CalcFoldOperator {
185    Add,
186    Sub,
187}
188
189#[derive(Debug, Clone)]
190struct ConstFoldSameUnitApplier {
191    left_var: Var,
192    right_var: Var,
193    unit_var: Option<Var>,
194    operator: CalcFoldOperator,
195}
196
197impl ConstFoldSameUnitApplier {
198    fn new(operator: CalcFoldOperator, unit_var: Option<Var>) -> Option<Self> {
199        Some(Self {
200            left_var: "?a".parse().ok()?,
201            right_var: "?b".parse().ok()?,
202            unit_var,
203            operator,
204        })
205    }
206}
207
208impl<N> Applier<CssRewriteLanguage, N> for ConstFoldSameUnitApplier
209where
210    N: Analysis<CssRewriteLanguage>,
211{
212    fn apply_one(
213        &self,
214        egraph: &mut EGraph<CssRewriteLanguage, N>,
215        eclass: Id,
216        subst: &Subst,
217        _searcher_ast: Option<&PatternAst<CssRewriteLanguage>>,
218        _rule_name: Symbol,
219    ) -> Vec<Id> {
220        let Some(left) = numeric_value_from_eclass(egraph, subst[self.left_var]) else {
221            return Vec::new();
222        };
223        let Some(right) = numeric_value_from_eclass(egraph, subst[self.right_var]) else {
224            return Vec::new();
225        };
226        let value = match self.operator {
227            CalcFoldOperator::Add => left + right,
228            CalcFoldOperator::Sub => left - right,
229        };
230        let value_id = egraph.add(CssRewriteLanguage::Num(value));
231        let result_id = if let Some(unit_var) = self.unit_var {
232            egraph.add(CssRewriteLanguage::Unit([value_id, subst[unit_var]]))
233        } else {
234            value_id
235        };
236        egraph.union(eclass, result_id);
237        vec![eclass]
238    }
239
240    fn vars(&self) -> Vec<Var> {
241        let mut vars = vec![self.left_var, self.right_var];
242        if let Some(unit_var) = self.unit_var {
243            vars.push(unit_var);
244        }
245        vars
246    }
247}
248
249pub fn summarize_omena_transform_egg_boundary() -> TransformEggBoundarySummaryV0 {
250    TransformEggBoundarySummaryV0 {
251        schema_version: "0",
252        product: "omena-transform-egg.boundary",
253        managed_pass_ids: managed_egg_passes().iter().map(|pass| pass.id()).collect(),
254        optional_engine: "egg-compatible equality saturation engine",
255        proof_obligations: vec![
256            "selector rewrites preserve specificity",
257            "calc rewrites preserve computed value",
258            "shorthand rewrites preserve computed value",
259            "stale-prefix removals preserve an exact unprefixed declaration peer",
260            "all rewrites preserve provenance",
261            "all accepted rewrites carry a cascade-safe witness",
262        ],
263        planner_surface: "omena-transform-passes.plan",
264    }
265}
266
267pub fn plan_egg_rewrite_passes(include_selector: bool, include_calc: bool) -> TransformEggPlanV0 {
268    let mut requested_passes = Vec::new();
269    if include_selector {
270        requested_passes.push(TransformPassKind::SelectorIsWhereCompression);
271    }
272    if include_calc {
273        requested_passes.push(TransformPassKind::CalcReduction);
274    }
275    let pass_plan = plan_transform_passes(&requested_passes);
276
277    TransformEggPlanV0 {
278        schema_version: "0",
279        product: "omena-transform-egg.plan",
280        requested_pass_ids: requested_passes.iter().map(|pass| pass.id()).collect(),
281        planned_pass_ids: pass_plan.ordered_pass_ids.clone(),
282        pass_plan,
283    }
284}
285
286pub fn plan_egg_rewrite_passes_for_source(source: &str) -> TransformEggPlanV0 {
287    plan_egg_rewrite_passes(
288        source.contains(":is(") || source.contains(":where("),
289        source.contains("calc("),
290    )
291}
292
293pub fn summarize_contextual_eqsat_scaffold_v0() -> ContextualEqSatScaffoldV0 {
294    let boundary = summarize_omena_transform_egg_boundary();
295
296    ContextualEqSatScaffoldV0 {
297        schema_version: "0",
298        product: "omena-transform-egg.contextual-eqsat-scaffold",
299        claim_level: "m6ScaffoldOnlyNoEgglogBinding",
300        scaffold_kind: "contextualEqualitySaturationExecutionView",
301        execution_view: "m6BridgeNodeExecutionView",
302        current_engine: "egg",
303        egg_engine_ready: true,
304        egglog_binding_ready: false,
305        external_datalog_host_ready: false,
306        three_view_fusion_ready: false,
307        theorem_claimed: false,
308        public_safety_claim_ready: false,
309        modal_witness_product: "omena-cascade.modal-check-witness",
310        modal_bridge_claim_level: "dependencyDeclaredOnly",
311        paper_substrate_claim_level: "draftScaffoldOnly",
312        managed_pass_ids: boundary.managed_pass_ids,
313        substrate_products: vec![
314            "omena-transform-egg.boundary",
315            "omena-transform-egg.plan",
316            "omena-transform-egg.execution",
317            "omena-cascade.modal-check-witness",
318        ],
319        supported_claims: vec![
320            "optional egg equality-saturation rewrite boundary",
321            "selector, calc, and shorthand rewrite proof obligations",
322            "contextual equality-saturation scaffold for M6 positioning",
323            "modal witness dependency declaration for #66/#73 paper substrate",
324        ],
325        deferred_claims: vec![
326            "egglog Rust binding",
327            "external Datalog host execution",
328            "full three-view fusion",
329            "Contextual EqSat theorem",
330            "production research-tier execution view",
331        ],
332    }
333}
334
335pub fn decide_egg_rewrite(candidate: EggRewriteCandidateV0) -> EggRewriteDecisionV0 {
336    let blocked_reason = if !is_managed_egg_pass_id(candidate.pass_id) {
337        Some("pass is not managed by omena-transform-egg")
338    } else if candidate.proof.cascade_safe_witness.is_empty() {
339        Some("missing cascade-safe witness")
340    } else if !candidate.proof.provenance_preserved {
341        Some("rewrite does not preserve provenance")
342    } else if candidate.pass_id == TransformPassKind::SelectorIsWhereCompression.id()
343        && !candidate.proof.specificity_preserved
344    {
345        Some("selector rewrite does not preserve specificity")
346    } else if candidate.pass_id == TransformPassKind::CalcReduction.id()
347        && !candidate.proof.computed_value_preserved
348    {
349        Some("calc rewrite does not preserve computed value")
350    } else if candidate.pass_id == TransformPassKind::ShorthandCombining.id()
351        && !candidate.proof.computed_value_preserved
352    {
353        Some("shorthand rewrite does not preserve computed value")
354    } else if candidate.pass_id == TransformPassKind::StalePrefixRemoval.id()
355        && !candidate.proof.computed_value_preserved
356    {
357        Some("stale-prefix removal does not preserve computed value")
358    } else {
359        None
360    };
361
362    EggRewriteDecisionV0 {
363        schema_version: "0",
364        product: "omena-transform-egg.decision",
365        pass_id: candidate.pass_id,
366        accepted: blocked_reason.is_none(),
367        blocked_reason,
368    }
369}
370
371pub fn execute_egg_rewrite(candidate: EggRewriteCandidateV0) -> EggRewriteExecutionV0 {
372    let decision = decide_egg_rewrite(candidate.clone());
373    if !decision.accepted {
374        return blocked_execution(candidate, decision.blocked_reason);
375    }
376
377    let expression = match candidate.before.parse::<RecExpr<CssRewriteLanguage>>() {
378        Ok(expression) => expression,
379        Err(_) => return blocked_execution(candidate, Some("rewrite expression could not parse")),
380    };
381    let Some(rules) = rewrite_rules_for_pass::<()>(candidate.pass_id) else {
382        return blocked_execution(
383            candidate,
384            Some("pass is not managed by omena-transform-egg"),
385        );
386    };
387
388    let iteration_limit = 8;
389    let runner = Runner::default()
390        .with_expr(&expression)
391        .with_iter_limit(iteration_limit)
392        .run(rules.as_slice());
393    let root = runner.roots[0];
394    let extractor = Extractor::new(&runner.egraph, MdlExtractionCostV0::default_ast_size());
395    let (_, extracted) = extractor.find_best(root);
396    let after = extracted.to_string();
397    let after_matches_candidate = after == candidate.after;
398
399    EggRewriteExecutionV0 {
400        schema_version: "0",
401        product: "omena-transform-egg.execution",
402        pass_id: candidate.pass_id,
403        accepted: after_matches_candidate,
404        blocked_reason: (!after_matches_candidate)
405            .then_some("egg extraction did not match candidate output"),
406        before: candidate.before,
407        after,
408        expected_after: candidate.after,
409        after_matches_candidate,
410        engine: "egg",
411        iteration_limit,
412        iteration_count: runner.iterations.len(),
413        eclass_count: runner.egraph.number_of_classes(),
414        enode_count: runner.egraph.total_size(),
415        mdl_bits: None,
416        mdl_residual_bits: None,
417        mdl_unit: None,
418    }
419}
420
421pub fn execute_egg_rewrite_witnesses_for_css_source(
422    source: &str,
423    dialect: StyleDialect,
424    transformed_source: &str,
425    planned_pass_ids: &[&'static str],
426) -> Vec<EggRewriteSourceWitnessV0> {
427    let mut witnesses = Vec::new();
428    if planned_pass_ids.contains(&TransformPassKind::SelectorIsWhereCompression.id()) {
429        witnesses.extend(selector_rewrite_witnesses(source, transformed_source));
430    }
431    if planned_pass_ids.contains(&TransformPassKind::CalcReduction.id()) {
432        witnesses.extend(calc_rewrite_witnesses(source, transformed_source));
433    }
434    if planned_pass_ids.contains(&TransformPassKind::StalePrefixRemoval.id()) {
435        witnesses.extend(stale_prefix_removal_witnesses(
436            source,
437            dialect,
438            transformed_source,
439        ));
440    }
441    witnesses
442}
443
444fn managed_egg_passes() -> [TransformPassKind; 4] {
445    [
446        TransformPassKind::SelectorIsWhereCompression,
447        TransformPassKind::CalcReduction,
448        TransformPassKind::ShorthandCombining,
449        TransformPassKind::StalePrefixRemoval,
450    ]
451}
452
453fn is_managed_egg_pass_id(pass_id: &str) -> bool {
454    managed_egg_passes().iter().any(|pass| pass.id() == pass_id)
455}
456
457fn numeric_value_from_eclass<N>(egraph: &EGraph<CssRewriteLanguage, N>, id: Id) -> Option<i64>
458where
459    N: Analysis<CssRewriteLanguage>,
460{
461    egraph[id].nodes.iter().find_map(|node| match node {
462        CssRewriteLanguage::Num(value) => Some(*value),
463        _ => None,
464    })
465}
466
467fn selector_rewrite_witnesses(
468    source: &str,
469    transformed_source: &str,
470) -> Vec<EggRewriteSourceWitnessV0> {
471    let mut witnesses = Vec::new();
472    for (prefix, source_kind) in [(":is(", "selectorIs"), (":where(", "selectorWhere")] {
473        let mut cursor = 0usize;
474        while let Some(relative_start) = source[cursor..].find(prefix) {
475            let start = cursor + relative_start;
476            let inner_start = start + prefix.len();
477            let Some(relative_end) = source[inner_start..].find(')') else {
478                break;
479            };
480            let end = inner_start + relative_end;
481            let inner = source[inner_start..end].trim();
482            let css_before = source[start..=end].to_string();
483            let pseudo_name = prefix.trim_start_matches(':').trim_end_matches('(');
484            if let Some((source_kind, css_after, before, after, witness)) =
485                selector_witness_candidate(pseudo_name, source_kind, inner)
486                && transformed_source.contains(&css_after)
487                && !transformed_source.contains(&css_before)
488            {
489                let execution = execute_egg_rewrite(EggRewriteCandidateV0 {
490                    pass_id: TransformPassKind::SelectorIsWhereCompression.id(),
491                    before,
492                    after,
493                    proof: EggRewriteProofV0::new(
494                        true,
495                        ObligationFamilyIdV0::CascadeSafetyFloor,
496                        true,
497                        witness,
498                    ),
499                });
500                witnesses.push(EggRewriteSourceWitnessV0 {
501                    pass_id: TransformPassKind::SelectorIsWhereCompression.id(),
502                    source_kind,
503                    byte_offset: start,
504                    css_before,
505                    css_after,
506                    execution,
507                });
508            }
509            cursor = end + 1;
510        }
511    }
512    witnesses
513}
514
515fn calc_rewrite_witnesses(
516    source: &str,
517    transformed_source: &str,
518) -> Vec<EggRewriteSourceWitnessV0> {
519    let mut witnesses = Vec::new();
520    let mut cursor = 0usize;
521    while let Some(relative_start) = source[cursor..].find("calc(") {
522        let start = cursor + relative_start;
523        let inner_start = start + "calc(".len();
524        let Some(relative_end) = source[inner_start..].find(')') else {
525            break;
526        };
527        let end = inner_start + relative_end;
528        let inner = source[inner_start..end].trim();
529        let css_before = source[start..=end].to_string();
530        if let Some(candidate) = calc_rewrite_candidate(inner)
531            && transformed_source.contains(candidate.css_after.as_str())
532            && !transformed_source.contains(&css_before)
533        {
534            let execution = execute_egg_rewrite(EggRewriteCandidateV0 {
535                pass_id: TransformPassKind::CalcReduction.id(),
536                before: format!("(calc {})", candidate.before),
537                after: candidate.after,
538                proof: EggRewriteProofV0::new(
539                    false,
540                    ObligationFamilyIdV0::ComputedValuePreservation,
541                    true,
542                    candidate.witness,
543                ),
544            });
545            witnesses.push(EggRewriteSourceWitnessV0 {
546                pass_id: TransformPassKind::CalcReduction.id(),
547                source_kind: candidate.source_kind,
548                byte_offset: start,
549                css_before,
550                css_after: candidate.css_after,
551                execution,
552            });
553        }
554        cursor = end + 1;
555    }
556    witnesses
557}
558
559fn stale_prefix_removal_witnesses(
560    source: &str,
561    dialect: StyleDialect,
562    transformed_source: &str,
563) -> Vec<EggRewriteSourceWitnessV0> {
564    collect_stale_vendor_prefix_removal_proof_candidates_from_source(source, dialect)
565        .into_iter()
566        .filter_map(|candidate| {
567            let css_before =
568                source[candidate.source_span_start..candidate.source_span_end].to_string();
569            if transformed_source.contains(&css_before) {
570                return None;
571            }
572            let css_after = source
573                [candidate.unprefixed_peer_span_start..candidate.unprefixed_peer_span_end]
574                .to_string();
575            if !transformed_source.contains(&css_after) {
576                return None;
577            }
578
579            let prefixed_property = egg_safe_symbol(candidate.prefixed_property.as_str());
580            let unprefixed_property = egg_safe_symbol(candidate.unprefixed_property);
581            let value = egg_safe_symbol(candidate.value.as_str());
582            let importance = if candidate.important {
583                "important"
584            } else {
585                "normal"
586            };
587            let execution = execute_egg_rewrite(EggRewriteCandidateV0 {
588                pass_id: TransformPassKind::StalePrefixRemoval.id(),
589                before: format!(
590                    "(stale-prefix-decl {prefixed_property} {unprefixed_property} {value} {importance})"
591                ),
592                after: format!("(decl {unprefixed_property} {value} {importance})"),
593                proof: EggRewriteProofV0::new(
594                    false,
595                    ObligationFamilyIdV0::ComputedValuePreservation,
596                    true,
597                    format!(
598                        "{} has exact unprefixed declaration peer {} with the same value and importance",
599                        candidate.prefixed_property, candidate.unprefixed_property
600                    ),
601                ),
602            });
603            Some(EggRewriteSourceWitnessV0 {
604                pass_id: TransformPassKind::StalePrefixRemoval.id(),
605                source_kind: "stalePrefixExactPeer",
606                byte_offset: candidate.source_span_start,
607                css_before,
608                css_after,
609                execution,
610            })
611        })
612        .collect()
613}
614
615fn selector_witness_candidate(
616    pseudo_name: &str,
617    source_kind: &'static str,
618    inner: &str,
619) -> Option<(&'static str, String, String, String, String)> {
620    if pseudo_name == "is"
621        && let Some((symbol, css_ident)) = selector_single_argument_parts(inner)
622    {
623        return Some((
624            source_kind,
625            format!(".{css_ident}"),
626            format!("(is {symbol})"),
627            symbol,
628            "actual CSS selectorIs single-argument rewrite".to_string(),
629        ));
630    }
631
632    let args = split_simple_selector_arguments(inner)?;
633    let [left, right] = args.as_slice() else {
634        return None;
635    };
636    if left != right {
637        return None;
638    }
639    let (symbol, css_ident) = selector_single_argument_parts(left)?;
640    match pseudo_name {
641        "is" => Some((
642            "selectorIsDedup",
643            format!(".{css_ident}"),
644            format!("(is (list {symbol} {symbol}))"),
645            symbol,
646            "actual CSS selectorIs duplicate-argument rewrite".to_string(),
647        )),
648        "where" => Some((
649            "selectorWhereDedup",
650            format!(":where(.{css_ident})"),
651            format!("(where (list {symbol} {symbol}))"),
652            format!("(where {symbol})"),
653            "actual CSS selectorWhere duplicate-argument rewrite".to_string(),
654        )),
655        _ => None,
656    }
657}
658
659fn egg_safe_symbol(value: &str) -> String {
660    let mut symbol = String::with_capacity(value.len().max(1));
661    for byte in value.bytes() {
662        let character = byte as char;
663        if character.is_ascii_alphanumeric() {
664            symbol.push(character.to_ascii_lowercase());
665        } else {
666            let _ = write!(&mut symbol, "_{byte:02x}");
667        }
668    }
669    if symbol.is_empty() {
670        "empty".to_string()
671    } else if symbol
672        .as_bytes()
673        .first()
674        .is_some_and(|byte| byte.is_ascii_digit())
675    {
676        format!("v_{symbol}")
677    } else {
678        symbol
679    }
680}
681
682fn split_simple_selector_arguments(inner: &str) -> Option<Vec<String>> {
683    let args = inner
684        .split(',')
685        .map(str::trim)
686        .map(str::to_string)
687        .collect::<Vec<_>>();
688    (!args.is_empty() && args.iter().all(|arg| !arg.is_empty())).then_some(args)
689}
690
691fn selector_single_argument_parts(inner: &str) -> Option<(String, String)> {
692    let class_name = inner.trim().strip_prefix('.')?;
693    if class_name.is_empty()
694        || !class_name
695            .chars()
696            .all(|ch| ch.is_ascii_alphanumeric() || matches!(ch, '_' | '-'))
697    {
698        return None;
699    }
700    Some((symbol_for_css_ident(class_name), class_name.to_string()))
701}
702
703fn symbol_for_css_ident(value: &str) -> String {
704    value.replace('-', "_")
705}
706
707#[derive(Debug, Clone, PartialEq, Eq)]
708struct CalcRewriteCandidate {
709    before: String,
710    after: String,
711    css_after: String,
712    source_kind: &'static str,
713    witness: String,
714}
715
716#[derive(Debug, Clone, PartialEq, Eq)]
717struct CalcNumericValue {
718    value: i64,
719    unit: String,
720}
721
722fn calc_rewrite_candidate(inner: &str) -> Option<CalcRewriteCandidate> {
723    let parts = inner.split_whitespace().collect::<Vec<_>>();
724    let [left, operator, right] = parts.as_slice() else {
725        return None;
726    };
727    let left_value = parse_calc_numeric_value(left)?;
728    let right_value = parse_calc_numeric_value(right)?;
729    if left_value.unit != right_value.unit {
730        return None;
731    }
732    let term_left = calc_numeric_term(&left_value);
733    let term_right = calc_numeric_term(&right_value);
734    match *operator {
735        "+" => Some(calc_fold_candidate(
736            format!("(+ {term_left} {term_right})"),
737            left_value.value + right_value.value,
738            &left_value.unit,
739            "calcSameUnitAdd",
740            "actual CSS calc same-unit addition rewrite",
741        )),
742        "-" => Some(calc_fold_candidate(
743            format!("(- {term_left} {term_right})"),
744            left_value.value - right_value.value,
745            &left_value.unit,
746            "calcSameUnitSub",
747            "actual CSS calc same-unit subtraction rewrite",
748        )),
749        "*" if right_value.value == 1 && right_value.unit.is_empty() => {
750            Some(calc_passthrough_candidate(
751                format!("(* {term_left} 1)"),
752                &left_value,
753                "calcIdentity",
754                "actual CSS calc multiplicative identity rewrite",
755            ))
756        }
757        "*" if left_value.value == 1 && left_value.unit.is_empty() => {
758            Some(calc_passthrough_candidate(
759                format!("(* 1 {term_right})"),
760                &right_value,
761                "calcIdentity",
762                "actual CSS calc multiplicative identity rewrite",
763            ))
764        }
765        "*" if right_value.value == 0 && right_value.unit.is_empty() => Some(calc_fold_candidate(
766            format!("(* {term_left} 0)"),
767            0,
768            "",
769            "calcZero",
770            "actual CSS calc safe zero multiplication rewrite",
771        )),
772        "*" if left_value.value == 0 && left_value.unit.is_empty() => Some(calc_fold_candidate(
773            format!("(* 0 {term_right})"),
774            0,
775            "",
776            "calcZero",
777            "actual CSS calc safe zero multiplication rewrite",
778        )),
779        "/" if right_value.value == 1 && right_value.unit.is_empty() => {
780            Some(calc_passthrough_candidate(
781                format!("(/ {term_left} 1)"),
782                &left_value,
783                "calcIdentity",
784                "actual CSS calc division identity rewrite",
785            ))
786        }
787        _ => None,
788    }
789}
790
791fn parse_calc_numeric_value(text: &str) -> Option<CalcNumericValue> {
792    let split = text
793        .char_indices()
794        .find_map(|(index, ch)| (!matches!(ch, '-' | '+') && !ch.is_ascii_digit()).then_some(index))
795        .unwrap_or(text.len());
796    let (value, unit) = text.split_at(split);
797    let value = value.parse::<i64>().ok()?;
798    unit.chars()
799        .all(|ch| ch.is_ascii_alphabetic() || ch == '%')
800        .then_some(CalcNumericValue {
801            value,
802            unit: unit.to_string(),
803        })
804}
805
806fn calc_numeric_term(value: &CalcNumericValue) -> String {
807    if value.unit.is_empty() {
808        value.value.to_string()
809    } else {
810        format!("(unit {} {})", value.value, value.unit)
811    }
812}
813
814fn calc_fold_candidate(
815    before: String,
816    value: i64,
817    unit: &str,
818    source_kind: &'static str,
819    witness: &'static str,
820) -> CalcRewriteCandidate {
821    let result = CalcNumericValue {
822        value,
823        unit: unit.to_string(),
824    };
825    CalcRewriteCandidate {
826        before,
827        after: calc_numeric_term(&result),
828        css_after: format!("{}{}", result.value, result.unit),
829        source_kind,
830        witness: witness.to_string(),
831    }
832}
833
834fn calc_passthrough_candidate(
835    before: String,
836    value: &CalcNumericValue,
837    source_kind: &'static str,
838    witness: &'static str,
839) -> CalcRewriteCandidate {
840    CalcRewriteCandidate {
841        before,
842        after: calc_numeric_term(value),
843        css_after: format!("{}{}", value.value, value.unit),
844        source_kind,
845        witness: witness.to_string(),
846    }
847}
848
849fn rewrite_pattern(text: &str) -> Option<Pattern<CssRewriteLanguage>> {
850    text.parse().ok()
851}
852
853pub(crate) fn calc_const_fold_rule<N>(
854    name: &'static str,
855    search: &'static str,
856    operator: CalcFoldOperator,
857    unit_var: Option<Var>,
858) -> Option<Rewrite<CssRewriteLanguage, N>>
859where
860    N: Analysis<CssRewriteLanguage>,
861{
862    Rewrite::new(
863        name,
864        rewrite_pattern(search)?,
865        ConstFoldSameUnitApplier::new(operator, unit_var)?,
866    )
867    .ok()
868}
869
870fn egg_var(name: &str) -> Option<Var> {
871    name.parse().ok()
872}
873
874pub(crate) fn rewrite_rules_for_pass<N>(
875    pass_id: &'static str,
876) -> Option<Vec<Rewrite<CssRewriteLanguage, N>>>
877where
878    N: Analysis<CssRewriteLanguage>,
879{
880    if pass_id == TransformPassKind::SelectorIsWhereCompression.id() {
881        return Some(vec![
882            egg_rewrite!("single-is-selector"; "(is ?a)" => "?a"),
883            egg_rewrite!("nested-is-selector"; "(is (is ?a))" => "?a"),
884            egg_rewrite!("duplicate-is-selector"; "(is (list ?a ?a))" => "?a"),
885            egg_rewrite!("duplicate-where-selector"; "(where (list ?a ?a))" => "(where ?a)"),
886        ]);
887    }
888    if pass_id == TransformPassKind::CalcReduction.id() {
889        let mut rules = vec![
890            egg_rewrite!("unwrap-calc"; "(calc ?a)" => "?a"),
891            egg_rewrite!("add-zero-right"; "(+ ?a 0)" => "?a"),
892            egg_rewrite!("add-zero-left"; "(+ 0 ?a)" => "?a"),
893            egg_rewrite!("sub-zero-right"; "(- ?a 0)" => "?a"),
894            egg_rewrite!("self-sub"; "(- ?a ?a)" => "0"),
895            egg_rewrite!("mul-one-right"; "(* ?a 1)" => "?a"),
896            egg_rewrite!("mul-one-left"; "(* 1 ?a)" => "?a"),
897            egg_rewrite!("mul-zero-right"; "(* ?a 0)" => "0"),
898            egg_rewrite!("mul-zero-left"; "(* 0 ?a)" => "0"),
899            egg_rewrite!("div-one-right"; "(/ ?a 1)" => "?a"),
900        ];
901        if let Some(rule) = calc_const_fold_rule(
902            "constfold-add-number",
903            "(+ ?a ?b)",
904            CalcFoldOperator::Add,
905            None,
906        ) {
907            rules.push(rule);
908        }
909        if let Some(unit_var) = egg_var("?u")
910            && let Some(rule) = calc_const_fold_rule(
911                "constfold-add-same-unit",
912                "(+ (unit ?a ?u) (unit ?b ?u))",
913                CalcFoldOperator::Add,
914                Some(unit_var),
915            )
916        {
917            rules.push(rule);
918        }
919        if let Some(rule) = calc_const_fold_rule(
920            "constfold-sub-number",
921            "(- ?a ?b)",
922            CalcFoldOperator::Sub,
923            None,
924        ) {
925            rules.push(rule);
926        }
927        if let Some(unit_var) = egg_var("?u")
928            && let Some(rule) = calc_const_fold_rule(
929                "constfold-sub-same-unit",
930                "(- (unit ?a ?u) (unit ?b ?u))",
931                CalcFoldOperator::Sub,
932                Some(unit_var),
933            )
934        {
935            rules.push(rule);
936        }
937        return Some(rules);
938    }
939    if pass_id == TransformPassKind::ShorthandCombining.id() {
940        return Some(vec![
941            egg_rewrite!("box4-all-equal"; "(box4 ?a ?a ?a ?a)" => "(box1 ?a)"),
942            egg_rewrite!("box4-vertical-horizontal"; "(box4 ?a ?b ?a ?b)" => "(box2 ?a ?b)"),
943            egg_rewrite!("box4-horizontal-pair"; "(box4 ?a ?b ?c ?b)" => "(box3 ?a ?b ?c)"),
944        ]);
945    }
946    if pass_id == TransformPassKind::StalePrefixRemoval.id() {
947        return Some(vec![
948            egg_rewrite!("stale-prefix-exact-peer"; "(stale-prefix-decl ?p ?u ?v ?i)" => "(decl ?u ?v ?i)"),
949        ]);
950    }
951    None
952}
953
954fn blocked_execution(
955    candidate: EggRewriteCandidateV0,
956    blocked_reason: Option<&'static str>,
957) -> EggRewriteExecutionV0 {
958    EggRewriteExecutionV0 {
959        schema_version: "0",
960        product: "omena-transform-egg.execution",
961        pass_id: candidate.pass_id,
962        accepted: false,
963        blocked_reason,
964        before: candidate.before.clone(),
965        after: candidate.before,
966        expected_after: candidate.after,
967        after_matches_candidate: false,
968        engine: "egg",
969        iteration_limit: 0,
970        iteration_count: 0,
971        eclass_count: 0,
972        enode_count: 0,
973        mdl_bits: None,
974        mdl_residual_bits: None,
975        mdl_unit: None,
976    }
977}
978
979#[cfg(test)]
980mod tests {
981    use super::{
982        EggRewriteCandidateV0, EggRewriteProofV0, decide_egg_rewrite, execute_egg_rewrite,
983        execute_egg_rewrite_witnesses_for_css_source, plan_egg_rewrite_passes,
984        plan_egg_rewrite_passes_for_source, rewrite_rules_for_pass,
985        summarize_contextual_eqsat_scaffold_v0, summarize_mdl_extraction_mode,
986        summarize_omena_transform_egg_boundary,
987    };
988    use omena_evidence_graph::ObligationFamilyIdV0;
989    use omena_parser::StyleDialect;
990    use omena_transform_cst::TransformPassKind;
991
992    #[test]
993    fn exposes_selector_calc_and_shorthand_optional_egg_boundary() {
994        let boundary = summarize_omena_transform_egg_boundary();
995
996        assert_eq!(boundary.product, "omena-transform-egg.boundary");
997        assert_eq!(
998            boundary.managed_pass_ids,
999            vec![
1000                "selector-is-where-compression",
1001                "calc-reduction",
1002                "shorthand-combining",
1003                "stale-prefix-removal"
1004            ]
1005        );
1006        assert_eq!(boundary.proof_obligations.len(), 6);
1007    }
1008
1009    #[test]
1010    fn mdl_extraction_default_preserves_ast_size() {
1011        let summary = summarize_mdl_extraction_mode();
1012
1013        assert_eq!(summary.schema_version, "0");
1014        assert_eq!(summary.product, "omena-transform-egg.mdl-extraction");
1015        assert!(summary.default_preserves_ast_size);
1016        assert_eq!(summary.layer_marker, "mdl-bits");
1017        assert_eq!(summary.unit, "bit");
1018        assert_eq!(summary.feature_gate, "mdl");
1019    }
1020
1021    #[test]
1022    fn plans_requested_egg_passes_through_transform_pass_planner() {
1023        let plan = plan_egg_rewrite_passes(true, true);
1024
1025        assert_eq!(
1026            plan.planned_pass_ids,
1027            vec!["selector-is-where-compression", "calc-reduction"]
1028        );
1029        assert_eq!(plan.pass_plan.violated_dag_edge_count, 0);
1030    }
1031
1032    #[test]
1033    fn plans_egg_passes_from_css_source() {
1034        let plan = plan_egg_rewrite_passes_for_source(".a:is(.ready) { width: calc(7 + 0); }");
1035
1036        assert_eq!(
1037            plan.planned_pass_ids,
1038            vec!["selector-is-where-compression", "calc-reduction"]
1039        );
1040        assert_eq!(plan.pass_plan.violated_dag_edge_count, 0);
1041    }
1042
1043    #[test]
1044    fn contextual_eqsat_scaffold_stays_no_egglog_binding() {
1045        let scaffold = summarize_contextual_eqsat_scaffold_v0();
1046
1047        assert_eq!(scaffold.schema_version, "0");
1048        assert_eq!(
1049            scaffold.product,
1050            "omena-transform-egg.contextual-eqsat-scaffold"
1051        );
1052        assert_eq!(scaffold.claim_level, "m6ScaffoldOnlyNoEgglogBinding");
1053        assert_eq!(scaffold.current_engine, "egg");
1054        assert!(scaffold.egg_engine_ready);
1055        assert!(!scaffold.egglog_binding_ready);
1056        assert!(!scaffold.external_datalog_host_ready);
1057        assert!(!scaffold.three_view_fusion_ready);
1058        assert!(!scaffold.theorem_claimed);
1059        assert!(!scaffold.public_safety_claim_ready);
1060        assert_eq!(
1061            scaffold.modal_witness_product,
1062            "omena-cascade.modal-check-witness"
1063        );
1064        assert_eq!(scaffold.modal_bridge_claim_level, "dependencyDeclaredOnly");
1065        assert_eq!(scaffold.paper_substrate_claim_level, "draftScaffoldOnly");
1066        assert_eq!(
1067            scaffold.managed_pass_ids,
1068            vec![
1069                "selector-is-where-compression",
1070                "calc-reduction",
1071                "shorthand-combining",
1072                "stale-prefix-removal"
1073            ]
1074        );
1075        assert!(
1076            scaffold
1077                .supported_claims
1078                .contains(&"contextual equality-saturation scaffold for M6 positioning")
1079        );
1080        assert!(scaffold.deferred_claims.contains(&"egglog Rust binding"));
1081        assert!(scaffold.deferred_claims.contains(&"full three-view fusion"));
1082    }
1083
1084    #[test]
1085    fn accepts_selector_rewrite_only_with_specificity_and_provenance_witnesses() {
1086        let decision = decide_egg_rewrite(EggRewriteCandidateV0 {
1087            pass_id: TransformPassKind::SelectorIsWhereCompression.id(),
1088            before: ":is(.a, .b)".to_string(),
1089            after: ".a,.b".to_string(),
1090            proof: EggRewriteProofV0::new(
1091                true,
1092                ObligationFamilyIdV0::CascadeSafetyFloor,
1093                true,
1094                "specificity tuple preserved",
1095            ),
1096        });
1097
1098        assert!(decision.accepted);
1099        assert_eq!(decision.blocked_reason, None);
1100    }
1101
1102    #[test]
1103    fn rejects_calc_rewrite_without_computed_value_witness() {
1104        let decision = decide_egg_rewrite(EggRewriteCandidateV0 {
1105            pass_id: TransformPassKind::CalcReduction.id(),
1106            before: "calc(1rem + 2px)".to_string(),
1107            after: "1rem".to_string(),
1108            proof: EggRewriteProofV0::new(
1109                false,
1110                ObligationFamilyIdV0::CascadeSafetyFloor,
1111                true,
1112                "candidate generated",
1113            ),
1114        });
1115
1116        assert!(!decision.accepted);
1117        assert_eq!(
1118            decision.blocked_reason,
1119            Some("calc rewrite does not preserve computed value")
1120        );
1121    }
1122
1123    #[test]
1124    fn rejects_shorthand_rewrite_without_computed_value_witness() {
1125        let decision = decide_egg_rewrite(EggRewriteCandidateV0 {
1126            pass_id: TransformPassKind::ShorthandCombining.id(),
1127            before: "(box4 0 0 0 0)".to_string(),
1128            after: "(box1 0)".to_string(),
1129            proof: EggRewriteProofV0::new(
1130                false,
1131                ObligationFamilyIdV0::CascadeSafetyFloor,
1132                true,
1133                "candidate generated",
1134            ),
1135        });
1136
1137        assert!(!decision.accepted);
1138        assert_eq!(
1139            decision.blocked_reason,
1140            Some("shorthand rewrite does not preserve computed value")
1141        );
1142    }
1143
1144    #[test]
1145    fn egg_rewrite_family_derivation_preserves_legacy_json_contract()
1146    -> Result<(), serde_json::Error> {
1147        for (family, expected_preserved, witness) in [
1148            (
1149                ObligationFamilyIdV0::CascadeSafetyFloor,
1150                false,
1151                "candidate generated",
1152            ),
1153            (
1154                ObligationFamilyIdV0::ComputedValuePreservation,
1155                true,
1156                "computed value preserved",
1157            ),
1158        ] {
1159            let proof = EggRewriteProofV0::new(true, family, false, witness);
1160            let json = serde_json::to_value(&proof)?;
1161
1162            assert_eq!(
1163                json,
1164                serde_json::json!({
1165                    "specificityPreserved": true,
1166                    "computedValuePreserved": expected_preserved,
1167                    "provenancePreserved": false,
1168                    "cascadeSafeWitness": witness,
1169                })
1170            );
1171            assert!(json.get("obligationFamily").is_none());
1172            assert_eq!(proof.obligation_family(), family);
1173        }
1174
1175        Ok(())
1176    }
1177
1178    #[test]
1179    fn executes_selector_rewrite_through_egg_engine() {
1180        let execution = execute_egg_rewrite(EggRewriteCandidateV0 {
1181            pass_id: TransformPassKind::SelectorIsWhereCompression.id(),
1182            before: "(is buttonPrimary)".to_string(),
1183            after: "buttonPrimary".to_string(),
1184            proof: EggRewriteProofV0::new(
1185                true,
1186                ObligationFamilyIdV0::CascadeSafetyFloor,
1187                true,
1188                "single :is() argument keeps specificity",
1189            ),
1190        });
1191
1192        assert!(execution.accepted);
1193        assert_eq!(execution.product, "omena-transform-egg.execution");
1194        assert_eq!(execution.engine, "egg");
1195        assert_eq!(execution.after, "buttonPrimary");
1196        assert_eq!(execution.iteration_limit, 8);
1197        assert!(execution.iteration_count > 0);
1198        assert!(execution.eclass_count > 0);
1199        assert!(execution.enode_count > 0);
1200    }
1201
1202    #[test]
1203    fn executes_selector_dedup_rewrites_through_egg_engine() {
1204        let is_execution = execute_egg_rewrite(EggRewriteCandidateV0 {
1205            pass_id: TransformPassKind::SelectorIsWhereCompression.id(),
1206            before: "(is (list ready ready))".to_string(),
1207            after: "ready".to_string(),
1208            proof: EggRewriteProofV0::new(
1209                true,
1210                ObligationFamilyIdV0::CascadeSafetyFloor,
1211                true,
1212                "duplicate :is() argument keeps specificity",
1213            ),
1214        });
1215        let where_execution = execute_egg_rewrite(EggRewriteCandidateV0 {
1216            pass_id: TransformPassKind::SelectorIsWhereCompression.id(),
1217            before: "(where (list ready ready))".to_string(),
1218            after: "(where ready)".to_string(),
1219            proof: EggRewriteProofV0::new(
1220                true,
1221                ObligationFamilyIdV0::CascadeSafetyFloor,
1222                true,
1223                "duplicate :where() argument keeps zero specificity",
1224            ),
1225        });
1226
1227        assert!(is_execution.accepted);
1228        assert_eq!(is_execution.after, "ready");
1229        assert!(where_execution.accepted);
1230        assert_eq!(where_execution.after, "(where ready)");
1231    }
1232
1233    #[test]
1234    fn executes_calc_rewrite_through_egg_engine() {
1235        let execution = execute_egg_rewrite(EggRewriteCandidateV0 {
1236            pass_id: TransformPassKind::CalcReduction.id(),
1237            before: "(calc (+ width 0))".to_string(),
1238            after: "width".to_string(),
1239            proof: EggRewriteProofV0::new(
1240                false,
1241                ObligationFamilyIdV0::ComputedValuePreservation,
1242                true,
1243                "additive identity preserves computed value",
1244            ),
1245        });
1246
1247        assert!(execution.accepted);
1248        assert_eq!(execution.after, "width");
1249        assert!(execution.after_matches_candidate);
1250    }
1251
1252    #[test]
1253    fn executes_extended_calc_identity_rewrites_through_egg_engine() {
1254        for (before, after) in [
1255            ("(calc (- width 0))", "width"),
1256            ("(calc (/ width 1))", "width"),
1257            ("(calc (* width 0))", "0"),
1258            ("(calc (- width width))", "0"),
1259        ] {
1260            let execution = execute_egg_rewrite(EggRewriteCandidateV0 {
1261                pass_id: TransformPassKind::CalcReduction.id(),
1262                before: before.to_string(),
1263                after: after.to_string(),
1264                proof: EggRewriteProofV0::new(
1265                    false,
1266                    ObligationFamilyIdV0::ComputedValuePreservation,
1267                    true,
1268                    "calc algebra identity preserves computed value",
1269                ),
1270            });
1271
1272            assert!(execution.accepted, "{before} -> {after}");
1273            assert_eq!(execution.after, after);
1274        }
1275    }
1276
1277    #[test]
1278    fn executes_same_unit_calc_const_folding_through_egg_engine() {
1279        for (before, after) in [
1280            ("(calc (+ (unit 1 px) (unit 2 px)))", "(unit 3 px)"),
1281            ("(calc (- (unit 10 rem) (unit 2 rem)))", "(unit 8 rem)"),
1282            ("(calc (+ 1 2))", "3"),
1283        ] {
1284            let execution = execute_egg_rewrite(EggRewriteCandidateV0 {
1285                pass_id: TransformPassKind::CalcReduction.id(),
1286                before: before.to_string(),
1287                after: after.to_string(),
1288                proof: EggRewriteProofV0::new(
1289                    false,
1290                    ObligationFamilyIdV0::ComputedValuePreservation,
1291                    true,
1292                    "same-unit calc arithmetic preserves computed value",
1293                ),
1294            });
1295
1296            assert!(execution.accepted, "{before} -> {after}");
1297            assert_eq!(execution.after, after);
1298        }
1299    }
1300
1301    #[test]
1302    fn executes_box_shorthand_rewrites_through_egg_engine() {
1303        for (before, after) in [
1304            ("(box4 0 0 0 0)", "(box1 0)"),
1305            ("(box4 1 2 1 2)", "(box2 1 2)"),
1306            ("(box4 1 2 3 2)", "(box3 1 2 3)"),
1307        ] {
1308            let execution = execute_egg_rewrite(EggRewriteCandidateV0 {
1309                pass_id: TransformPassKind::ShorthandCombining.id(),
1310                before: before.to_string(),
1311                after: after.to_string(),
1312                proof: EggRewriteProofV0::new(
1313                    false,
1314                    ObligationFamilyIdV0::ComputedValuePreservation,
1315                    true,
1316                    "box shorthand expansion preserves computed value",
1317                ),
1318            });
1319
1320            assert!(execution.accepted, "{before} -> {after}");
1321            assert_eq!(execution.after, after);
1322        }
1323    }
1324
1325    #[test]
1326    fn exposes_shorthand_rewrite_rules_for_managed_pass() {
1327        let rules = rewrite_rules_for_pass::<()>(TransformPassKind::ShorthandCombining.id());
1328
1329        assert!(rules.is_some_and(|rules| rules.len() == 3));
1330    }
1331
1332    #[test]
1333    fn executes_css_source_witnesses_through_egg_engine() {
1334        let source = ".a:is(.ready) { width: calc(1px + 2px); } .b:is(.x, .x) { color: red; } .c:where(.y, .y) { color: blue; }";
1335        let transformed =
1336            ".a.ready { width: 3px; } .b.x { color: red; } .c:where(.y) { color: blue; }";
1337        let plan = plan_egg_rewrite_passes_for_source(source);
1338        let witnesses = execute_egg_rewrite_witnesses_for_css_source(
1339            source,
1340            StyleDialect::Css,
1341            transformed,
1342            &plan.planned_pass_ids,
1343        );
1344
1345        assert_eq!(witnesses.len(), 4);
1346        assert!(witnesses.iter().all(|witness| witness.execution.accepted));
1347        assert!(
1348            witnesses
1349                .iter()
1350                .any(|witness| witness.pass_id == "selector-is-where-compression")
1351        );
1352        assert!(
1353            witnesses
1354                .iter()
1355                .any(|witness| witness.pass_id == "calc-reduction")
1356        );
1357        assert!(witnesses.iter().any(|witness| {
1358            witness.source_kind == "selectorIsDedup" && witness.css_after == ".x"
1359        }));
1360        assert!(witnesses.iter().any(|witness| {
1361            witness.source_kind == "selectorWhereDedup" && witness.css_after == ":where(.y)"
1362        }));
1363        assert!(witnesses.iter().any(|witness| {
1364            witness.source_kind == "calcSameUnitAdd"
1365                && witness.css_after == "3px"
1366                && witness.execution.after == "(unit 3 px)"
1367        }));
1368    }
1369
1370    #[test]
1371    fn executes_stale_prefix_removal_source_witness_through_egg_engine() {
1372        let source = ".a { -webkit-user-select: none; user-select: none; }";
1373        let transformed = ".a {  user-select: none; }";
1374        let witnesses = execute_egg_rewrite_witnesses_for_css_source(
1375            source,
1376            StyleDialect::Css,
1377            transformed,
1378            &[TransformPassKind::StalePrefixRemoval.id()],
1379        );
1380
1381        assert_eq!(witnesses.len(), 1);
1382        let witness = &witnesses[0];
1383        assert_eq!(witness.pass_id, "stale-prefix-removal");
1384        assert_eq!(witness.source_kind, "stalePrefixExactPeer");
1385        assert_eq!(witness.css_before, "-webkit-user-select: none;");
1386        assert_eq!(witness.css_after, "user-select: none;");
1387        assert!(witness.execution.accepted);
1388        assert_eq!(witness.execution.engine, "egg");
1389        assert_eq!(witness.execution.after, witness.execution.expected_after);
1390    }
1391
1392    #[test]
1393    fn mdl_default_ast_size_matches_100_fixture_differential_corpus() {
1394        let selector_cases = (0..50).map(|index| {
1395            (
1396                TransformPassKind::SelectorIsWhereCompression.id(),
1397                format!("(is token{index})"),
1398                format!("token{index}"),
1399                true,
1400                false,
1401                "single :is() argument keeps specificity",
1402            )
1403        });
1404        let calc_cases = (0..50).map(|index| {
1405            let left = index + 1;
1406            let right = 50 - index;
1407            (
1408                TransformPassKind::CalcReduction.id(),
1409                format!("(calc (+ (unit {left} px) (unit {right} px)))"),
1410                format!("(unit {} px)", left + right),
1411                false,
1412                true,
1413                "same-unit calc arithmetic preserves computed value",
1414            )
1415        });
1416        let cases = selector_cases.chain(calc_cases).collect::<Vec<_>>();
1417
1418        assert_eq!(cases.len(), 100);
1419        for (
1420            pass_id,
1421            before,
1422            expected_after,
1423            specificity_preserved,
1424            computed_value_preserved,
1425            witness,
1426        ) in cases
1427        {
1428            let execution = execute_egg_rewrite(EggRewriteCandidateV0 {
1429                pass_id,
1430                before: before.clone(),
1431                after: expected_after.clone(),
1432                proof: EggRewriteProofV0::new(
1433                    specificity_preserved,
1434                    ObligationFamilyIdV0::from_computed_value_preservation(
1435                        computed_value_preserved,
1436                    ),
1437                    true,
1438                    witness,
1439                ),
1440            });
1441
1442            assert!(execution.accepted, "{before} -> {expected_after}");
1443            assert_eq!(execution.after, expected_after);
1444            assert!(execution.after_matches_candidate);
1445            assert_eq!(execution.mdl_bits, None);
1446            assert_eq!(execution.mdl_unit, None);
1447        }
1448    }
1449}