Sign in

    moonbindgen

    Generate MoonBit native C FFI declarations from C headers with Clang.

    ffi
    bindings
    clang
    code-generation
    Download zip
    Author
    Version
    0.2.0
    License
    MIT
    Last updated
    25 days ago
    Downloads
    27

    #MoonBindgen

    MoonBindgen is a pure MoonBit Native command-line generator for conservative C FFI declarations. It delegates C parsing to Clang 23, converts the JSON AST into a typed declaration model, lowers only verified direct-ABI types, and writes deterministic MoonBit declarations together with an auditable report.

    The project deliberately does not pretend that syntax implies ownership. It rejects callbacks, by-value structs, variadic functions, and pointers whose lifetime is unclear. Explicit configuration enables one status-and-output parameter or a borrowed/owned UTF-8 return; these functions receive generated C bridges. A configured call-scoped const char * input becomes Bytes with #borrow.

    #Why this project

    C interoperability is an ecosystem multiplier: one trustworthy generator can reduce the repetitive and error-prone first step of binding databases, compression libraries, parsers, and operating-system APIs. The hard part is not printing extern "C"; it is preserving declaration provenance, resolving typedefs without importing unrelated functions, making unsafe gaps visible, and producing output that reviewers can reproduce.

    klaseca/moonbit-bindgen is an important existing reference. It is a TypeScript framework with configurable source processing and stub generation. MoonBindgen takes a different technical route: the generator itself is MoonBit, Clang's AST is the source of declaration facts, unsupported ABI cases are rejected conservatively, every decision appears in a versioned report, and Native compile/link/call fixtures plus SQLite exercise the result. The rationale is therefore auditability and implementation diversity, not merely whether another project has been published to Mooncakes.

    #Architecture

    C header + config-v1 │ ▼ Clang 23 JSON AST ──► provenance-aware declaration model │ typedef/enum/opaque-type environment │ ▼ conservative ABI lowering │ ┌──────────────┼────────────────┐ ▼ ▼ ▼ bindings.mbt optional C shim report.json (v2)

    The main header controls which declarations are emitted. Included headers may supply typedef and enum facts, but their functions are not emitted. Clang nodes that omit a repeated filename inherit the most recent explicit provenance; malformed or contradictory declaration structures stop generation instead of being guessed.

    #Requirements and installation

    • MoonBit 0.1.20260920 (914d7da) with Moonc/Core 0.10.14+7d59c7ec9
    • Clang 23.x; CI and local evidence use LLVM 23.1.1
    • Windows x64 is the currently verified environment, not a cross-platform promise

    The checked release toolchain is recorded in toolchain.json. CI rejects a different compiler or core version instead of silently accepting formatter or diagnostic drift.

    To consume the generator as a library:

    moon add shop1111/moonbindgen@0.2.0

    Add the root package to moon.pkg:

    import { "shop1111/moonbindgen" @bindgen, }

    The public entry points are generate, generate_with_config, generate_with_metadata, and parse_config. They accept Clang AST JSON; invoking Clang remains the responsibility of the CLI or the embedding application.

    let config_text =
    #|{"schema":"moonbindgen-config-v1","unsupported_policy":"report"}
    let config = @bindgen.parse_config(config_text)
    let result = @bindgen.generate_with_config(clang_ast_json, "library.h", config)
    println(result.bindings)

    Windows users can also download moonbindgen-v0.2.0-windows-x86_64.zip from the GitHub Release, extract moonbindgen.exe, and keep Clang 23 available separately. The executable is not a general C/C++ compiler bundle.

    #CLI quick start

    moon run -q cmd/main -- generate fixtures/basic.h --out _build/basic --clang 'C:\Program Files\LLVM\bin\clang.exe' moon run -q cmd/main -- generate fixtures/basic.h --out _build/basic --clang 'C:\Program Files\LLVM\bin\clang.exe' --check

    Arguments after -- go directly to Clang, for example -- -Ivendor/include -DFEATURE=1. --check compares all generated artifacts and writes nothing; drift exits with code 6. Run --help for the complete interface. Exit codes distinguish usage/configuration (2), Clang (3), generation policy (4), output I/O (5), and drift (6).

    #Configuration v1

    --config <json> accepts only the versioned schema below. Function filters are exact names. Renames are normalized as MoonBit identifiers and collision-checked. Type overrides are restricted to Bool, Byte, Int, UInt, Int64, UInt64, Float, and Double; they cannot inject source text.

    { "schema": "moonbindgen-config-v1", "include_functions": ["library_open"], "exclude_functions": [], "renames": { "library_open": "open_library" }, "type_overrides": { "library_size_t": "UInt64" }, "utf8_inputs": ["library_open.path"], "unsupported_policy": "error" }

    unsupported_policy is report by default. With error, any unsupported declaration rejects the whole generation before official output files are replaced.

    #Explicit C bridges

    The SQLite example uses the additional config-v1 fields below:

    { "schema": "moonbindgen-config-v1", "utf8_inputs": ["sqlite3_open.filename", "sqlite3_prepare_v2.zSql"], "output_params": ["sqlite3_open.ppDb", "sqlite3_prepare_v2.ppStmt"], "null_inputs": ["sqlite3_prepare_v2.pzTail"], "utf8_borrowed_returns": ["sqlite3_libversion"] }

    output_params selects exactly one scalar T* or opaque-handle T** output for a function returning an int status; its MoonBit wrapper returns (Int, T). The status and output value are both preserved on failure, so callers must follow the C library's cleanup contract. null_inputs omits an explicitly nullable pointer argument. Other output pointers remain unsupported.

    utf8_borrowed_returns copies a borrowed char* / const char* before its owner can change. utf8_owned_returns maps an owned char* function to a declared void free_name(char*) or void free_name(void*) function; the bridge copies first and calls that function once. Both yield String?: C NULL becomes None, and malformed UTF-8 raises a decoding error. An owned return without a declared compatible release function is rejected; the caller must confirm the library's ownership contract.

    When a bridge is needed, generation also writes bindings_shim.c. Place it alongside the C header, or provide the header's directory to the C compiler; include the generated file in the consumer package's native-stub list and import moonbitlang/core/encoding/utf8 in moon.pkg for string wrappers. For example, examples/sqlite/moon.pkg uses "native-stub": [ "sqlite3.c", "bindings_shim.c" ]. --check compares all generated artifacts, including the presence or absence of this C file.

    #Supported ABI surface

    C declarationMoonBit outputPolicy
    char, signed/unsigned fixed mappingsByte, Int, UInt, Int64, UInt64Direct value ABI
    float, doubleFloat, DoubleDirect value ABI
    typedef chains and 32-bit enumsCanonical scalar / IntResolved from Clang facts
    pointer to an opaque struct typedef#external typeRaw pointer; caller owns lifetime rules
    configured input const char *Bytes plus #borrowValid only for the duration of the call
    configured int status plus one scalar T* or opaque T** output(Int, T) plus generated C bridgeExplicit output parameter; no inferred cleanup
    configured UTF-8 char* returnString? plus generated C bridgeBorrowed copy or copy and configured release
    other pointers, callbacks, variadics, by-value structsnoneReported and skipped, or rejected in strict mode

    MoonBindgen emits raw FFI declarations and narrowly configured bridges. It does not generate callback trampolines, general resource-safe wrappers, C++, or broad platform guarantees in version 0.2.x.

    #Auditable report v2

    report.json uses moonbindgen-report-v2. It records the Clang version and target, referenced include files, declaration kind and source line, original C signature, emitted MoonBit declaration, status, stable reason code, and summary counts. Bridged declarations include bridge: true, and a generated C file sets has_shim: true. Existing v2 fields and reason codes are preserved.

    { "schema": "moonbindgen-report-v2", "clang": { "version": "clang version 23.1.1 ...", "target": "x86_64-pc-windows-msvc" }, "summary": { "functions_generated": 4, "functions_skipped": 0, "unsupported": 0 }, "declarations": [{ "c_name": "abi_add", "status": "generated", "reason_code": "generated" }] }

    #Verification evidence

    ./scripts/verify.ps1

    The gate runs strict Native check/build/test, coverage analysis, two-run byte determinism, report schema assertions, include provenance, invalid-header rejection, three-artifact drift and rollback checks, a generated C fixture that exercises scalar and handle outputs plus borrowed and owned strings, and the SQLite query below.

    examples/sqlite vendors the official SQLite 3.53.4 amalgamation. On Clang 23.1.1, MoonBindgen generates 131 functions and reports 167 skipped functions. Generated bridges call sqlite3_open, sqlite3_prepare_v2, and sqlite3_libversion; generated declarations then execute SELECT 42 through sqlite3_step, sqlite3_column_int, sqlite3_finalize, and sqlite3_close.

    moon run -q examples/sqlite # SELECT 42 => 42

    Exact upstream provenance, checksums, licensing, and the one comment-only local edit are recorded in THIRD_PARTY.md.

    #Release hygiene

    .moonignore keeps fixtures, examples, scripts, CI configuration, tests, generated release assets, the local competition charter, and submission/ out of the Mooncakes package. The SQLite amalgamation is marked vendored for GitHub language statistics. Run the full verification gate, moon info, moon doc, and moon package --list before publishing. scripts/package-release.ps1 builds the stripped Windows CLI, creates deterministic release archives, and writes SHA256SUMS.txt.

    ConfigError

    pub(all) suberror ConfigError {
    InvalidConfig(String)
    } derive(Eq,
    Debug
    )

    An error raised when generation configuration is malformed or unsupported.

    ConfigError::equal

    #deprecated("`ConfigError::equal` is deprecated, use `Eq::equal` instead.")
    fn ConfigError::equal(ConfigError, ConfigError) -> Bool

    ConfigError::not_equal

    #deprecated("`ConfigError::not_equal` is deprecated, use `Eq::not_equal` instead.")
    fn ConfigError::not_equal(x : ConfigError, y : ConfigError) -> Bool

    ConfigError::to_repr

    #deprecated("`ConfigError::to_repr` is deprecated, use `@moonbitlang/core/debug.Debug::to_repr` instead.")
    fn ConfigError::to_repr(ConfigError) ->
    Repr

    GenerateConfig

    pub struct GenerateConfig {
    include_functions : Map[String, Bool]
    exclude_functions : Map[String, Bool]
    renames : Map[String, String]
    type_overrides : Map[String, String]
    utf8_inputs : Map[String, Bool]
    output_params : Map[String, Bool]
    null_inputs : Map[String, Bool]
    utf8_borrowed_returns : Map[String, Bool]
    utf8_owned_returns : Map[String, String]
    unsupported_policy : UnsupportedPolicy
    }

    Versioned generation settings parsed from moonbindgen-config-v1 JSON.

    Use GenerateConfig::default for report-only generation, or parse_config when loading a user-supplied configuration file.

    GenerateConfig::default

    Returns the default configuration with no filters or overrides and with unsupported declarations reported instead of rejected.

    GenerateResult

    pub struct GenerateResult {
    bindings : String
    report : String
    shim : String?
    generated : Int
    skipped : Int
    }

    Generated MoonBit declarations, their audit report, and function counts.

    ToolMetadata

    pub struct ToolMetadata {
    clang_version : String
    clang_target : String
    }

    Clang identity recorded in moonbindgen-report-v2 output.

    ToolMetadata::new

    fn ToolMetadata::new(clang_version : String, clang_target : String) -> ToolMetadata

    Creates metadata from the first Clang version line and target triple.

    ToolMetadata::unknown

    fn ToolMetadata::unknown() -> ToolMetadata

    Creates placeholder metadata for callers that already have AST JSON but do not know which Clang executable produced it.

    UnsupportedPolicy

    pub(all) enum UnsupportedPolicy {
    Report
    Error
    } derive(Eq,
    Debug
    )

    Controls how generation handles declarations outside MoonBindgen's conservative ABI surface.

    UnsupportedPolicy::equal

    #deprecated("`UnsupportedPolicy::equal` is deprecated, use `Eq::equal` instead.")
    fn UnsupportedPolicy::equal(UnsupportedPolicy, UnsupportedPolicy) -> Bool

    UnsupportedPolicy::not_equal

    #deprecated("`UnsupportedPolicy::not_equal` is deprecated, use `Eq::not_equal` instead.")
    fn UnsupportedPolicy::not_equal(x : UnsupportedPolicy, y : UnsupportedPolicy) -> Bool

    UnsupportedPolicy::to_repr

    #deprecated("`UnsupportedPolicy::to_repr` is deprecated, use `@moonbitlang/core/debug.Debug::to_repr` instead.")
    fn UnsupportedPolicy::to_repr(UnsupportedPolicy) ->
    Repr

    generate

    fn generate(ast_text : String, header : String) -> GenerateResult raise

    Generates bindings with the default configuration and unknown Clang metadata.

    Raises when ast_text is not a recognized Clang translation-unit AST or when a contradictory declaration structure cannot be interpreted safely.

    generate_with_config

    fn generate_with_config(ast_text : String, header : String, config : GenerateConfig) -> GenerateResult raise

    Generates bindings with explicit configuration and unknown Clang metadata.

    header identifies the main header whose functions are eligible for emission; included headers may still contribute typedef and enum facts.

    generate_with_metadata

    fn generate_with_metadata(ast_text : String, header : String, config : GenerateConfig, metadata : ToolMetadata) -> GenerateResult raise

    Generates deterministic bindings and a moonbindgen-report-v2 audit report with explicit configuration and Clang metadata.

    Raises for malformed AST JSON, unsupported declarations under strict policy, name collisions that cannot be represented safely, or contradictory Clang declaration structures.

    parse_config

    fn parse_config(text : String) -> GenerateConfig raise ConfigError

    Parses and validates a moonbindgen-config-v1 JSON document.

    Unknown fields, unsafe type overrides, and schema mismatches raise ConfigError::InvalidConfig.