vyre 0.4.0

GPU compute intermediate representation with a standard operation library
Documentation
use vyre::ir::{self, DataType};
use vyre::lower::wgsl;
use vyre::ops::string::tokenize_gpu;
use vyre::ops::{AlgebraicLaw, Category, Compose, OpSpec};

fn assert_category_a(spec: &OpSpec) {
    assert!(matches!(spec.category(), Category::A));
    assert!(matches!(spec.compose(), Compose::Composition(_)));
    let program = spec
        .program()
        .expect("Category A specs must build programs");
    assert!(
        !program.entry().is_empty(),
        "{} must not return an empty program",
        spec.id()
    );
}

#[test]
fn spec_is_category_a_composition() {
    assert_category_a(&tokenize_gpu::Tokenize::SPEC);
}

#[test]
fn spec_declares_correct_types() {
    assert_eq!(tokenize_gpu::Tokenize::SPEC.inputs(), &[DataType::Bytes]);
    assert_eq!(tokenize_gpu::Tokenize::SPEC.outputs(), &[DataType::U32]);
}

#[test]
fn spec_declares_expected_laws() {
    assert!(tokenize_gpu::Tokenize::SPEC
        .laws()
        .contains(&AlgebraicLaw::Bounded { lo: 0, hi: 7 }));
    assert!(!tokenize_gpu::Tokenize::SPEC.inlinable());
}

#[test]
fn program_lowers_to_wgsl() {
    let program = tokenize_gpu::Tokenize::SPEC
        .program()
        .expect("program must exist");
    // Note: IR validation currently rejects DataType::Bytes loads in binops.
    // This is a pre-existing limitation shared by all byte-string compositions.
    // The lowering path is the authority for tokenize_gpu.
    let shader = wgsl::lower(&program).unwrap_or_else(|e| panic!("lowering failed: {e}"));
    assert!(shader.contains("@compute"));
    assert!(shader.contains("source"));
    assert!(shader.contains("tokens"));
}

#[test]
fn tokens_buffer_is_marked_output() {
    let program = tokenize_gpu::Tokenize::program();
    let tokens_buf = program.buffer("tokens").expect("tokens buffer must exist");
    assert!(tokens_buf.is_output());
}

#[test]
fn op_is_registered_as_non_inlinable() {
    let program = ir::Program::new(
        vec![],
        [1, 1, 1],
        vec![ir::Node::let_bind(
            "x",
            ir::Expr::call("string.tokenize_gpu", vec![ir::Expr::u32(0)]),
        )],
    );
    let err = vyre::ir::inline_calls(&program).expect_err("tokenize_gpu must not inline");
    assert!(matches!(err, vyre::error::Error::InlineNonInlinable { .. }));
}

#[test]
fn host_helpers_filter_matches_by_token() {
    use vyre::ir::engine::token_match_filter::{filter_matches_by_token, TokenType};

    let tokens = [
        TokenType::Identifier, // 0
        TokenType::Operator,   // 1
        TokenType::String,     // 2
        TokenType::String,     // 3
        TokenType::Whitespace, // 4
    ];
    let matches = vec![
        vyre::Match::new(0, 0, 1), // identifier — allowed
        vyre::Match::new(0, 1, 2), // operator — allowed
        vyre::Match::new(0, 2, 4), // string — allowed
        vyre::Match::new(0, 4, 5), // whitespace — NOT allowed
    ];
    let allowed = &[
        TokenType::Identifier,
        TokenType::Operator,
        TokenType::String,
    ];
    let filtered = filter_matches_by_token(&tokens, &matches, allowed);
    assert_eq!(filtered.len(), 3);
    assert_eq!(filtered[0].start, 0);
    assert_eq!(filtered[1].start, 1);
    assert_eq!(filtered[2].start, 2);
}

#[test]
fn host_helpers_filter_code_matches_excludes_comments_and_whitespace() {
    use vyre::ir::engine::token_match_filter::{filter_code_matches, TokenType};

    let tokens = [
        TokenType::Identifier, // 0
        TokenType::Whitespace, // 1
        TokenType::Comment,    // 2
        TokenType::Number,     // 3
    ];
    let matches = vec![
        vyre::Match::new(0, 0, 1), // identifier — code
        vyre::Match::new(0, 1, 2), // whitespace — excluded
        vyre::Match::new(0, 2, 3), // comment — excluded
        vyre::Match::new(0, 3, 4), // number — code
    ];
    let filtered = filter_code_matches(&tokens, &matches);
    assert_eq!(filtered.len(), 2);
    assert_eq!(filtered[0].start, 0);
    assert_eq!(filtered[1].start, 3);
}