chipi-core 0.9.1

Core library for chipi: parser, IR, and code generation backends for instruction decoder generation
Documentation
use std::path::PathBuf;

use chipi_core::bindings::parser::{parse, parse_file_with_includes};
use chipi_core::bindings::types::TargetKind;

fn fixtures_dir() -> PathBuf {
    PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures")
}

#[test]
fn parse_basic_rust_decoder() {
    let path = fixtures_dir().join("test_basic.bindings.chipi");
    let file = parse_file_with_includes(&path).expect("parse");
    assert_eq!(file.targets.len(), 1);
    assert_eq!(file.targets[0].kind, TargetKind::Rust);
    assert_eq!(file.targets[0].rust_decoders.len(), 1);
    assert_eq!(file.targets[0].rust_dispatches.len(), 1);

    let dec = &file.targets[0].rust_decoders[0];
    assert_eq!(dec.decoder_name, "TestDsp");
    assert_eq!(dec.output, "out/test_dsp.rs");

    let disp = &file.targets[0].rust_dispatches[0];
    assert_eq!(disp.decoder_name, "TestDsp");
    assert_eq!(disp.context.as_deref(), Some("crate::cpu::Cpu"));
    assert_eq!(disp.handlers.as_deref(), Some("crate::cpu::interpreter"));
    assert_eq!(
        disp.invalid_handler.as_deref(),
        Some("crate::cpu::interpreter::invalid")
    );
    assert!(disp.handler_groups.is_empty());
}

#[test]
fn parse_grouped_dispatch() {
    let path = fixtures_dir().join("test_grouped.bindings.chipi");
    let file = parse_file_with_includes(&path).expect("parse");
    let disp = &file.targets[0].rust_dispatches[0];
    assert_eq!(disp.handler_groups.len(), 1);
    let g = &disp.handler_groups[0];
    assert_eq!(g.handler_name, "control");
    let names: Vec<&str> = g.instructions.iter().map(|(n, _)| n.as_str()).collect();
    assert_eq!(names, vec!["nop", "halt"]);
}

#[test]
fn parse_ida_target() {
    let path = fixtures_dir().join("test_ida.bindings.chipi");
    let file = parse_file_with_includes(&path).expect("parse");
    let target = &file.targets[0];
    assert_eq!(target.kind, TargetKind::Ida);
    let proc = &target.ida_processors[0];
    assert_eq!(proc.decoder_name, "TestDsp");
    assert_eq!(proc.id, Some(0x8001));
    assert_eq!(proc.address_size, Some(16));
    assert_eq!(proc.bytes_per_unit, Some(2));
    assert!(proc.registers.contains(&"ar0".to_string()));
    assert_eq!(proc.flow.calls.len(), 1);
    assert_eq!(proc.flow.stops.len(), 1);
}

#[test]
fn parse_binja_target() {
    let path = fixtures_dir().join("test_binja.bindings.chipi");
    let file = parse_file_with_includes(&path).expect("parse");
    let target = &file.targets[0];
    assert_eq!(target.kind, TargetKind::Binja);
    let arch = &target.binja_architectures[0];
    assert_eq!(arch.decoder_name, "TestDsp");
    assert_eq!(arch.address_size, Some(2));
    assert_eq!(
        arch.endianness.as_ref().map(|(s, _)| s.as_str()),
        Some("big")
    );
}

#[test]
fn parse_combined_file() {
    let path = fixtures_dir().join("test_combined.bindings.chipi");
    let file = parse_file_with_includes(&path).expect("parse");
    assert_eq!(file.targets.len(), 3);
    let kinds: Vec<TargetKind> = file.targets.iter().map(|t| t.kind).collect();
    assert!(kinds.contains(&TargetKind::Rust));
    assert!(kinds.contains(&TargetKind::Ida));
    assert!(kinds.contains(&TargetKind::Binja));
}

#[test]
fn parse_unknown_target_kind() {
    let src = r#"target frob { }"#;
    let p = std::path::Path::new("inline.bindings.chipi");
    let result = parse(src, p);
    let errs = result.expect_err("should fail");
    assert!(errs.iter().any(|e| matches!(
        e.kind,
        chipi_core::error::ErrorKind::UnknownTargetKind(ref s) if s == "frob"
    )));
}

#[test]
fn parse_invalid_strategy() {
    let src = r#"
include "test_dsp.chipi"
target rust {
    dispatch TestDsp {
        output "x.rs"
        context crate::Cpu
        handlers crate::interp
        strategy frob
        invalid_handler crate::interp::invalid
    }
}
"#;
    let p = std::path::Path::new("inline.bindings.chipi");
    let errs = parse(src, p).expect_err("should fail");
    assert!(
        errs.iter()
            .any(|e| matches!(e.kind, chipi_core::error::ErrorKind::InvalidStrategy(_)))
    );
}