vyre-conform 0.1.0

Conformance suite for vyre backends — proves byte-identical output to CPU reference
Documentation
//! Engine-level conformance specifications.
//!
//! Defines `EngineSpec` instances for the composed vyre engines:
//! DFA scanner, bytecode evaluator, and match-to-rule scatter.

/// Dfa module.
pub mod dfa;
/// Eval module.
pub mod eval;
/// Scatter module.
pub mod scatter;

use crate::spec::EngineSpec;

/// All registered engine specs.
#[inline]
pub fn specs() -> Vec<EngineSpec> {
    vec![dfa::spec(), eval::spec(), scatter::spec()]
}

#[cfg(test)]
mod invariant_tests {
    use super::specs;
    use crate::spec::EngineInvariant;

    // ── Deterministic ────────────────────────────────────────────

    #[test]
    fn all_cpu_references_are_deterministic() {
        for spec in specs() {
            if !spec.invariants.contains(&EngineInvariant::Deterministic) {
                continue;
            }
            let Some(cpu) = spec.cpu_fn else {
                panic!("{} declares Deterministic but has no cpu_fn", spec.id);
            };
            let input = hand_crafted_input_for(spec.id);
            let out1 = cpu(&input);
            let out2 = cpu(&input);
            assert_eq!(
                out1, out2,
                "{} violates Deterministic: same input produced different outputs",
                spec.id
            );
        }
    }

    // ── OutputOrdered ────────────────────────────────────────────

    #[test]
    fn dfa_output_is_ordered() {
        let spec = specs().into_iter().find(|s| s.id == "engine.dfa").unwrap();
        let cpu = spec.cpu_fn.unwrap();
        let input = hand_crafted_input_for("engine.dfa");
        let out = cpu(&input);
        // Each match is 12 bytes; extract start offsets and verify monotonic.
        assert_eq!(out.len() % 12, 0, "DFA output not 12-byte aligned");
        let starts: Vec<u32> = out
            .chunks_exact(12)
            .map(|c| u32::from_le_bytes([c[4], c[5], c[6], c[7]]))
            .collect();
        assert!(
            starts.windows(2).all(|w| w[0] <= w[1]),
            "DFA output starts are not ordered: {:?}",
            starts
        );
    }

    // ── NoOutputLost ─────────────────────────────────────────────

    #[test]
    fn dfa_no_output_lost() {
        let spec = specs().into_iter().find(|s| s.id == "engine.dfa").unwrap();
        let cpu = spec.cpu_fn.unwrap();
        let input = hand_crafted_input_for("engine.dfa");
        let out = cpu(&input);
        // Hand-crafted input has 2 expected matches for "ab" and "cd".
        assert_eq!(
            out.len(),
            24,
            "DFA lost output: expected 2 matches (24 bytes)"
        );
    }

    #[test]
    fn scatter_no_output_lost() {
        let spec = specs()
            .into_iter()
            .find(|s| s.id == "engine.scatter")
            .unwrap();
        let cpu = spec.cpu_fn.unwrap();
        let input = hand_crafted_input_for("engine.scatter");
        let out = cpu(&input);
        let word = u32::from_le_bytes([out[0], out[1], out[2], out[3]]);
        assert!(
            word & (1 << 3) != 0,
            "Scatter lost output: expected bit 3 to be set in rule 0"
        );
    }

    // ── NoDuplicateOutput ────────────────────────────────────────

    #[test]
    fn scatter_is_idempotent() {
        let spec = specs()
            .into_iter()
            .find(|s| s.id == "engine.scatter")
            .unwrap();
        let cpu = spec.cpu_fn.unwrap();
        let input = hand_crafted_input_for("engine.scatter");
        let out1 = cpu(&input);
        let out2 = cpu(&input);
        assert_eq!(
            out1, out2,
            "Scatter violates NoDuplicateOutput: output changed on identical input"
        );
    }

    // ── BoundedResources ─────────────────────────────────────────

    #[test]
    fn eval_bounded_resources() {
        let spec = specs().into_iter().find(|s| s.id == "engine.eval").unwrap();
        let cpu = spec.cpu_fn.unwrap();
        let input = hand_crafted_input_for("engine.eval");
        // Must not panic or overflow stack on hand-crafted input.
        let out = cpu(&input);
        assert_eq!(out.len(), 1, "Eval output length unexpected");
    }

    // ── Helpers ──────────────────────────────────────────────────

    fn hand_crafted_input_for(id: &str) -> Vec<u8> {
        match id {
            "engine.dfa" => super::dfa::tests::build_dfa_input_for_invariants(),
            "engine.eval" => super::eval::tests::build_eval_input_for_invariants(),
            "engine.scatter" => super::scatter::tests::build_scatter_input_for_invariants(),
            _ => panic!("unknown engine id: {id}"),
        }
    }
}