libperl-macrogen 0.1.5

Generate Rust FFI bindings from C macro functions in Perl headers
Documentation

libperl-macrogen

Rust library and CLI tool for generating Rust FFI bindings from C macro functions and inline functions in Perl header files.

Pre-alpha Release: This project is in early development. APIs may change without notice.

Overview

libperl-macrogen parses C header files (particularly Perl's internal headers) and generates Rust wrapper functions for:

  • C macro functions - Converted to Rust unsafe fn with type inference
  • Inline functions - Extracted and converted to Rust equivalents

This tool is designed to complement rust-bindgen, which cannot handle C macros.

Installation

cargo install libperl-macrogen

Or add to your Cargo.toml:

[build-dependencies]
libperl-macrogen = "0.1"

Apidoc data

The crate bundles a pre-extracted snapshot of perlapi documentation (apidoc.tar.gz, ~1.9 MiB) that the type inferencer needs to disambiguate macro return types. This means no network access is required at build time — works under docs.rs's --network none sandbox, in air-gapped CI, etc.

  • When building from a checkout of this repository (with the apidoc/ directory present), build.rs re-tars locally each build so edits to apidoc/v5.X.json propagate.
  • When building from crates.io, the bundled apidoc.tar.gz is copied into OUT_DIR directly.
  • Advanced override: set LIBPERL_APIDOC_URL to download a different apidoc dataset (offline mirror, pre-release data, ...) instead.

Usage

CLI

# Generate Rust wrapper functions from Perl headers
libperl-macrogen --auto --gen-rust \
    --bindings path/to/bindings.rs \
    -o macro_fns.rs \
    wrapper.h

# Output to stdout (for inspection)
libperl-macrogen --auto --gen-rust \
    --bindings path/to/bindings.rs \
    wrapper.h

# With rustfmt validation
libperl-macrogen --auto --gen-rust \
    --bindings path/to/bindings.rs \
    --strict-rustfmt \
    wrapper.h

Options

Option Description
--auto Auto-detect Perl include paths and defines from Config.pm
--gen-rust Generate Rust code for macros and inline functions
--bindings <FILE> Path to bindgen-generated Rust bindings (for type inference)
-o <FILE> Output file (stdout if omitted)
--strict-rustfmt Fail if generated code doesn't pass rustfmt
-I <DIR> Add include directory
-D <MACRO> Define a macro

Library API

The library provides a Pipeline API with three phases:

  1. Preprocess - Parse C header files with macro expansion
  2. Infer - Perform type inference on macros and inline functions
  3. Generate - Generate Rust code

Simple Usage (build.rs)

use std::fs::File;
use libperl_macrogen::Pipeline;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let out_dir = std::env::var("OUT_DIR")?;
    let bindings_path = format!("{}/bindings.rs", out_dir);
    let output_path = format!("{}/macro_fns.rs", out_dir);

    // One-shot execution with Pipeline builder
    let mut output = File::create(&output_path)?;
    let result = Pipeline::builder("wrapper.h")
        .with_auto_perl_config()?
        .with_bindings(&bindings_path)
        .with_codegen_defaults()       // Required for proper assert handling
        .build()?
        .generate(&mut output)?;

    println!("cargo:warning=Generated {} macro + {} inline functions",
        result.stats.macro_success, result.stats.inline_success);

    Ok(())
}

Step-by-Step Execution

For more control, you can execute each phase separately:

use std::fs::File;
use libperl_macrogen::Pipeline;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Phase 1: Preprocess
    let preprocessed = Pipeline::builder("wrapper.h")
        .with_auto_perl_config()?
        .with_codegen_defaults()       // Required for proper assert handling
        .build()?
        .preprocess()?;

    println!("Preprocessed {} macros", preprocessed.macro_count());

    // Phase 2: Infer types
    let inferred = preprocessed
        .with_bindings("bindings.rs")
        .infer()?;

    println!("Inferred {} macro functions", inferred.result().macro_infos.len());

    // Phase 3: Generate Rust code
    let mut output = File::create("macro_fns.rs")?;
    let generated = inferred
        .with_strict_rustfmt()
        .generate(&mut output)?;

    println!("Generated {} functions", generated.stats.total_success());

    Ok(())
}

Pipeline Builder Options

Pipeline::builder("wrapper.h")
    // Preprocessor options
    .with_auto_perl_config()?      // Auto-detect from Perl's Config.pm
    .add_include_path("/usr/include")
    .add_define("DEBUG", Some("1"))
    .with_target_dir("/usr/lib64/perl5/CORE")

    // Inference options
    .with_bindings("bindings.rs")  // bindgen output for type info
    .with_apidoc_path("embed.fnc") // Perl API documentation

    // Codegen options
    .with_strict_rustfmt()         // Fail if rustfmt fails
    .with_codegen_defaults()       // Apply default codegen settings

    .build()?
    .generate(&mut output)?;

Features

  • C preprocessor with full macro expansion
  • C parser for declarations, expressions, and inline function bodies
  • Type inference from function call context and Perl's apidoc (embed.fnc)
  • GCC extensions (__attribute__, __typeof__, statement expressions, etc.)
  • Automatic Perl configuration detection via Config.pm

Acknowledgments

This project was inspired by and references the implementation of TinyCC, originally created by Fabrice Bellard. The preprocessor and parser design draws from TinyCC's elegant approach to C compilation.

Special thanks to the TinyCC community for continuing the development and maintenance of TinyCC. Their work served as a valuable reference throughout this project's development.

License

This project is licensed under the GNU Lesser General Public License v2.1 or later (LGPL-2.1-or-later), following TinyCC's licensing.

Author

hkoba (CPAN ID: HKOBA)

Related Projects