vyre-conform 0.1.0

Conformance suite for vyre backends — proves byte-identical output to CPU reference
Documentation
//! Mutation enum, MutationClass, catalog.

pub mod applied;
pub mod arithmetic;
pub mod bitwise;
pub mod buffer;
pub(crate) mod catalog;
pub mod comparison;
pub mod control;
pub mod ir_specific;
pub mod law;
pub(crate) mod lexical;
mod mutation;

pub use crate::spec::mutation_class::MutationClass;
pub use catalog::MUTATION_CATALOG;
pub use mutation::{apply, class_of, Mutation};

/// Binary operator kind used by mutations.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BinOpKind {
    /// `+`
    Add,
    /// `-`
    Sub,
    /// `*`
    Mul,
    /// `/`
    Div,
    /// `<<`
    Shl,
    /// `>>`
    Shr,
    /// `&`
    And,
    /// `|`
    Or,
    /// `^`
    Xor,
    /// `<`
    Lt,
    /// `<=`
    Le,
    /// `>`
    Gt,
    /// `>=`
    Ge,
    /// `==`
    Eq,
    /// `!=`
    Ne,
}

/// Control-flow branch target.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Branch {
    /// `if` branch.
    If,
    /// `else` branch.
    Else,
}

/// Error produced when a mutation cannot be applied.
#[derive(Debug, Clone, PartialEq)]
pub enum MutationError {
    /// The mutation is not supported on the given source.
    UnsupportedMutation,
    /// A low-level apply failure.
    ApplyError(String),
}

impl core::fmt::Display for MutationError {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        match self {
            MutationError::UnsupportedMutation => {
                f.write_str("mutation not supported for this source")
            }
            MutationError::ApplyError(s) => write!(f, "apply error: {s}"),
        }
    }
}

impl std::error::Error for MutationError {}

/// Replace up to `count` occurrences of a binary operator.
#[inline]
pub(crate) fn replace_operator(source: &str, old_op: &str, new_op: &str, count: usize) -> String {
    let mut result = String::with_capacity(source.len());
    let mut replaced = 0;
    let mut i = 0;
    while i < source.len() {
        if replaced < count
            && crate::adversarial::mutations::catalog::lexical::is_code_index(source, i)
            && source[i..].starts_with(old_op)
        {
            let left_ok =
                crate::adversarial::mutations::catalog::lexical::previous_non_ws(source, i)
                    .map(|c| c.is_alphanumeric() || c == '_' || c == ')' || c == ']')
                    .unwrap_or(false);
            let right_idx = i + old_op.len();
            let right_ok =
                crate::adversarial::mutations::catalog::lexical::next_non_ws(source, right_idx)
                    .map(|c| c.is_alphanumeric() || c == '_' || c == '(' || c == '[')
                    .unwrap_or(false);
            if left_ok && right_ok {
                result.push_str(new_op);
                i += old_op.len();
                replaced += 1;
                continue;
            }
        }
        if let Some(c) = source[i..].chars().next() {
            result.push(c);
            i += c.len_utf8();
        } else {
            break;
        }
    }
    result
}