alef-backend-zig 0.15.26

Zig backend for alef
Documentation
//! Helper functions for Zig code generation.
//!
//! Provides utilities for FFI introspection and documentation emission.

use alef_codegen::c_consumer;
use alef_core::config::Language;
use alef_docs::clean_doc;

/// Emit the two standard helpers every generated file needs:
///
/// - `_free_string`: wraps the C `{prefix}_free_string` symbol to release
///   FFI-allocated strings. Caller must NOT use the pointer after this call.
/// - `_last_error`: reads the thread-local last-error state set by the FFI
///   layer. Returns a Zig slice pointing into thread-local storage; the
///   pointer is valid until the next FFI call.
pub(crate) fn emit_helpers(prefix: &str, out: &mut String) {
    let free_symbol = c_consumer::free_string_symbol(prefix);
    let error_code_symbol = c_consumer::last_error_code_symbol(prefix);
    let error_context_symbol = c_consumer::last_error_context_symbol(prefix);

    out.push_str("/// Free a string allocated by the FFI layer.\n");
    out.push_str(&crate::template_env::render(
        "helper_free_string_doc1.jinja",
        minijinja::context! {
            prefix => prefix,
        },
    ));
    out.push_str("/// Do NOT call this twice on the same pointer.\n");
    out.push_str("pub fn _free_string(ptr: [*c]u8) void {\n");
    out.push_str(&crate::template_env::render(
        "helper_free_string_doc2.jinja",
        minijinja::context! {
            free_symbol => free_symbol,
        },
    ));
    out.push_str("}\n\n");

    out.push_str("/// Retrieve the last error set by the FFI layer, if any.\n");
    out.push_str("/// Returns a slice into thread-local storage valid until the next FFI call.\n");
    out.push_str("pub fn _last_error() ?[]const u8 {\n");
    out.push_str(&crate::template_env::render(
        "helper_last_error_code.jinja",
        minijinja::context! {
            symbol => error_code_symbol,
        },
    ));
    out.push_str("    if (_code == 0) return null;\n");
    out.push_str(&crate::template_env::render(
        "helper_last_error_ctx.jinja",
        minijinja::context! {
            symbol => error_context_symbol,
        },
    ));
    out.push_str("    if (_ctx == null) return null;\n");
    out.push_str("    return std.mem.sliceTo(_ctx, 0);\n");
    out.push_str("}\n\n");

    out.push_str("/// Map the last FFI error to a typed error from the given error set.\n");
    out.push_str("/// Coarse fallback: returns the first declared variant. Per-code dispatch\n");
    out.push_str("/// will replace this once the IR exposes per-variant numeric codes.\n");
    out.push_str("inline fn _first_error(comptime E: type) E {\n");
    out.push_str("    const fields = @typeInfo(E).error_set orelse return @as(E, error.Unknown);\n");
    out.push_str("    if (fields.len == 0) unreachable;\n");
    out.push_str("    return @field(E, fields[0].name);\n");
    out.push_str("}\n");
}

/// Emit cleaned Zig documentation for a declaration.
///
/// Cleans Rust-specific doc strings and formats as Zig doc comments (/// ...).
pub(crate) fn emit_cleaned_zig_doc(out: &mut String, doc: &str, indent: &str) {
    if doc.is_empty() {
        return;
    }
    let cleaned = clean_doc(doc, Language::Zig);
    alef_codegen::doc_emission::emit_zig_doc(out, &cleaned, indent);
}