inauguration 0.2.0

.in language and general compiler CLI (Core IR, hybrid SIL, staging, plugins)
Documentation
pub mod c_header;
pub mod manifest;
pub mod probes;
pub mod swift_wrapper;

pub use c_header::emit_c_header;
pub use manifest::{emit_abi_manifest, emit_abi_manifest_with_package, emit_component_metadata};
pub use probes::{LayoutProbes, emit_layout_probes};
pub use swift_wrapper::{
    SwiftBoundaryEmit, emit_swift_boundary, emit_swift_layout_probes, emit_swift_wrapper,
};

use crate::boundary_ir::BoundaryRepr;

pub(crate) fn guard_token(module: &str) -> String {
    let upper = module
        .chars()
        .map(|c| {
            if c.is_ascii_alphanumeric() {
                c.to_ascii_uppercase()
            } else {
                '_'
            }
        })
        .collect::<String>();
    format!("IN_BOUNDARY_{upper}_H")
}

pub(crate) fn c_type_name(typ: &str) -> &'static str {
    match typ {
        "u8" => "uint8_t",
        "i8" => "int8_t",
        "u16" => "uint16_t",
        "i16" => "int16_t",
        "u32" => "uint32_t",
        "i32" => "int32_t",
        "u64" => "uint64_t",
        "i64" => "int64_t",
        "f32" | "float" => "float",
        "f64" => "double",
        "bool" => "uint8_t",
        "InSliceU8" => "InSliceU8",
        "InBufU8" => "InBufU8",
        "InBorrowToken" => "InBorrowToken",
        "InArenaHandle" => "InArenaHandle",
        _ => "uint64_t",
    }
}

pub(crate) fn rust_type_name(typ: &str) -> String {
    match typ {
        "u8" => "u8".to_string(),
        "i8" => "i8".to_string(),
        "u16" => "u16".to_string(),
        "i16" => "i16".to_string(),
        "u32" => "u32".to_string(),
        "i32" => "i32".to_string(),
        "u64" => "u64".to_string(),
        "i64" => "i64".to_string(),
        "f32" | "float" => "f32".to_string(),
        "f64" => "f64".to_string(),
        "bool" => "bool".to_string(),
        "InSliceU8" => "InSliceU8".to_string(),
        "InBufU8" => "InBufU8".to_string(),
        "InBorrowToken" => "InBorrowToken".to_string(),
        "InArenaHandle" => "InArenaHandle".to_string(),
        other => other.to_string(),
    }
}

pub(crate) fn swift_type_name(typ: &str) -> String {
    match typ {
        "u8" => "UInt8".to_string(),
        "i8" => "Int8".to_string(),
        "u16" => "UInt16".to_string(),
        "i16" => "Int16".to_string(),
        "u32" => "UInt32".to_string(),
        "i32" => "Int32".to_string(),
        "u64" => "UInt64".to_string(),
        "i64" => "Int64".to_string(),
        "f32" | "float" => "Float".to_string(),
        "f64" => "Double".to_string(),
        "bool" => "UInt8".to_string(),
        "InSliceU8" => "InSliceU8".to_string(),
        "InBufU8" => "InBufU8".to_string(),
        "InBorrowToken" => "InBorrowToken".to_string(),
        "InArenaHandle" => "InArenaHandle".to_string(),
        other => other.to_string(),
    }
}

pub(crate) fn swift_c_import_name(module: &str) -> String {
    let mut out = String::from("InBoundary");
    for segment in module.split(|c: char| !c.is_ascii_alphanumeric()) {
        if segment.is_empty() {
            continue;
        }
        let mut chars = segment.chars();
        if let Some(first) = chars.next() {
            out.extend(first.to_uppercase());
            for c in chars {
                out.push(c.to_ascii_lowercase());
            }
        }
    }
    out
}

pub(crate) fn swift_namespace_name(module: &str) -> String {
    swift_c_import_name(module)
        .trim_start_matches("InBoundary")
        .to_string()
}

pub(crate) fn swift_probe_class_name(module: &str) -> String {
    format!("{}LayoutProbes", swift_c_import_name(module))
}

pub(crate) fn swift_probe_method_prefix(layout_name: &str) -> String {
    let mut out = String::new();
    let mut upper_next = true;
    for c in layout_name.chars() {
        if upper_next {
            out.extend(c.to_uppercase());
            upper_next = false;
        } else {
            out.push(c);
        }
    }
    out
}

pub(crate) fn swift_symbol_name(c_name: &str) -> String {
    let mut out = String::new();
    let mut upper_next = false;
    for c in c_name.chars() {
        if c == '_' {
            upper_next = true;
        } else if upper_next {
            out.extend(c.to_uppercase());
            upper_next = false;
        } else {
            out.push(c);
        }
    }
    out
}

pub(crate) fn zig_type_name(typ: &str) -> &'static str {
    match typ {
        "u8" => "u8",
        "i8" => "i8",
        "u16" => "u16",
        "i16" => "i16",
        "u32" => "u32",
        "i32" => "i32",
        "u64" => "u64",
        "i64" => "i64",
        "f32" | "float" => "f32",
        "f64" => "f64",
        "bool" => "bool",
        "InSliceU8" => "InSliceU8",
        "InBufU8" => "InBufU8",
        "InBorrowToken" => "InBorrowToken",
        "InArenaHandle" => "InArenaHandle",
        _ => "u64",
    }
}

pub(crate) fn repr_attribute(repr: Option<&BoundaryRepr>) -> &'static str {
    match repr {
        Some(BoundaryRepr::Packed) => "packed",
        Some(BoundaryRepr::Transparent) => "transparent",
        Some(BoundaryRepr::C) | None => "c",
    }
}

pub(crate) fn prepared_module(
    module: &crate::boundary_ir::BoundaryModule,
) -> crate::boundary_ir::BoundaryModule {
    if module.layout_hash.is_empty() {
        module.clone().with_layout_hash()
    } else {
        module.clone()
    }
}

#[cfg(test)]
pub(crate) fn sample_module() -> crate::boundary_ir::BoundaryModule {
    use crate::boundary_ir::{
        BoundaryField, BoundaryLayout, BoundaryModule, BoundaryOwnership, BoundaryRepr,
        BoundarySymbol, BoundaryTransfer, IN_ABI_VERSION,
    };

    BoundaryModule {
        abi_version: IN_ABI_VERSION,
        module: "sample.person".to_string(),
        layouts: vec![BoundaryLayout {
            name: "Person".to_string(),
            kind: "struct".to_string(),
            repr: Some(BoundaryRepr::C),
            size: 24,
            align: 8,
            stride: 24,
            fields: vec![
                BoundaryField {
                    name: "name".to_string(),
                    offset: 0,
                    typ: "InSliceU8".to_string(),
                    transfer: Some(BoundaryTransfer::Borrow),
                },
                BoundaryField {
                    name: "age".to_string(),
                    offset: 16,
                    typ: "u32".to_string(),
                    transfer: Some(BoundaryTransfer::Copy),
                },
            ],
        }],
        symbols: vec![BoundarySymbol {
            name: "person_new".to_string(),
            signature_hash: "person_new_v1".to_string(),
            ownership: BoundaryOwnership::ReturnsOwnedHandle,
            calling_convention: "c".to_string(),
        }],
        allocators: vec![],
        layout_hash: String::new(),
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn emit_pipeline_roundtrip_for_sample_module() {
        let module = sample_module();
        let manifest = emit_abi_manifest(&module);
        assert!(manifest.contains("\"layout_hash\""));
        assert!(manifest.contains("\"Person\""));

        let header = emit_c_header(&module).expect("c header");
        assert!(header.contains("typedef struct Person"));
        assert!(header.contains("InSliceU8 name"));
        assert!(header.contains("uint32_t age"));

        let probes = emit_layout_probes(&module).expect("layout probes");
        assert!(probes.rust.contains("struct Person"));
        assert!(probes.rust.contains("offset_of!(Person, age) == 16"));
        assert!(probes.zig.contains("const Person = extern struct"));
        assert!(probes.zig.contains("@offsetOf(Person, .age) != 16"));

        let swift = emit_swift_boundary(&module).expect("swift boundary");
        assert!(swift.wrapper.contains("import InBoundarySamplePerson"));
        assert!(
            swift
                .wrapper
                .contains("public static func personNew() -> UInt64")
        );
        assert!(
            swift
                .layout_probes
                .contains("XCTAssertEqual(MemoryLayout<Person>.offset(of: \\.age), 16)")
        );
    }
}