vyre-conform 0.1.0

Conformance suite for vyre backends — proves byte-identical output to CPU reference
Documentation
//! Generator binary entry point.

use std::fs;
use std::path::{Path, PathBuf};

use clap::Parser;

use crate::generate::archetypes::ALL_ARCHETYPES;
use crate::generate::emit::{generate, GenerationPlan, TemplateKind};
use crate::op_registry::all_specs;
use crate::OpSpec;

/// Arguments for generated-test materialization.
#[derive(Debug, Parser)]
pub struct Args {
    /// Generate for one op id or final id segment.
    #[arg(long, conflicts_with = "all")]
    pub op: Option<String>,
    /// Generate for every registered op.
    #[arg(long)]
    pub all: bool,
    /// Read simple TOML keys: op, seed.
    #[arg(long)]
    pub plan: Option<PathBuf>,
    /// Output directory.
    #[arg(long)]
    pub out: PathBuf,
}

/// Generate tests from the selected operation plan.
pub fn run(args: Args) -> Result<(), Box<dyn std::error::Error>> {
    let mut seed = 0x5659_5245_0005_0001u64;
    let mut op_filter = args.op;

    if let Some(plan_path) = args.plan {
        let plan = PlanFile::read(&plan_path)?;
        seed = plan.seed.unwrap_or(seed);
        if op_filter.is_none() {
            op_filter = plan.op;
        }
    }

    let ops = select_ops(op_filter.as_deref(), args.all)?;
    let plan = GenerationPlan {
        ops,
        archetypes: ALL_ARCHETYPES.to_vec(),
        templates: all_templates(),
        seed,
    };
    let report = generate(&plan, &args.out).map_err(|err: crate::generate::emit::GenError| {
        format!("Fix: generated-test emission failed: {err}")
    })?;
    println!(
        "generated {} file(s) into {}",
        report.files.len(),
        args.out.display()
    );
    Ok(())
}

struct PlanFile {
    op: Option<String>,
    seed: Option<u64>,
}

impl PlanFile {
    fn read(path: &Path) -> Result<Self, String> {
        let text = fs::read_to_string(path)
            .map_err(|err| format!("Fix: failed to read plan `{}`: {err}", path.display()))?;
        let mut op = None;
        let mut seed = None;

        for raw in text.lines() {
            let line = raw.split('#').next().map(str::trim).unwrap_or("");
            if line.is_empty() || line.starts_with('[') {
                continue;
            }
            let Some((key, value)) = line.split_once('=') else {
                return Err(format!(
                    "Fix: malformed plan line `{raw}`. Use key = value."
                ));
            };
            match key.trim() {
                "op" => op = Some(unquote(value.trim()).to_string()),
                "seed" => {
                    seed =
                        Some(value.trim().parse::<u64>().map_err(|err| {
                            format!("Fix: plan seed must be a u64 integer: {err}")
                        })?);
                }
                _ => {}
            }
        }

        Ok(Self { op, seed })
    }
}

fn select_ops(filter: Option<&str>, all: bool) -> Result<Vec<&'static OpSpec>, String> {
    if filter.is_none() && !all {
        return Err(
            "Fix: specify --op NAME, --all, or --plan PATH with op = \"NAME\".".to_string(),
        );
    }
    let specs = all_specs();
    let selected: Vec<OpSpec> = if all {
        specs
    } else {
        let Some(needle) = filter else {
            return Err(
                "Fix: plan file must contain `op = \"NAME\"` unless --all is used.".to_string(),
            );
        };
        specs
            .into_iter()
            .filter(|spec| spec.id == needle || spec.id.rsplit('.').next() == Some(needle))
            .collect()
    };

    if selected.is_empty() {
        return Err(format!(
            "Fix: no operation matched `{}` in op_registry::all_specs.",
            filter.unwrap_or("<all>")
        ));
    }

    Ok(selected
        .into_iter()
        .map(|spec| Box::leak(Box::new(spec)) as &'static OpSpec)
        .collect())
}

fn unquote(value: &str) -> &str {
    value
        .strip_prefix('"')
        .and_then(|rest| rest.strip_suffix('"'))
        .unwrap_or(value)
}

fn all_templates() -> Vec<TemplateKind> {
    vec![
        TemplateKind::OpCorrectness,
        TemplateKind::Law,
        TemplateKind::BackendEquiv,
        TemplateKind::Archetype,
        TemplateKind::Validation,
        TemplateKind::MutationKill,
    ]
}