NAME

Affix::Wrap - Frictionless C Header Introspection and FFI Wrapper Generation

SYNOPSIS

use v5.40;
use Affix;
use Affix::Wrap;

# Parse headers and install symbols into the current package
Affix::Wrap->new(
    project_files => [ 'include/mylib.h' ],
    include_dirs  => [ '/usr/local/include' ],
    types         => {
        'git_repository' => Pointer[Void], # Treat opaque struct as void pointer
        'git_off_t'      => Int64,         # Force specific integer width
    }
)->wrap( load_library('demo') );

# Use the C functions immediately
my $val = mylib_function(10);

DESCRIPTION

Affix::Wrap acts as a bridge between C/C++ header files and Affix. It parses C header files to produce a structured object model (AST) representing functions, structs, enums, typedefs, macros, and variables.

This module is designed to facilitate two primary workflows:

1. Runtime Wrapping

Parsing headers on the fly to wrap libraries dynamically via wrap(). This is ideal for rapid prototyping, private tooling, or when you don't want to maintain a separate XS/FFI module file.

2. Static Generation

Creating .pm files to be distributed on CPAN via parse(). This provides the fastest load times for end users, as the parsing happens only once during development.

Please be aware that this is experimental software!

Drivers

Affix::Wrap utilizes a dual-driver approach to parsing:

  • Clang driver

    The preferred driver. It uses the system's clang executable to dump the AST in JSON format. It is extremely accurate, handles C preprocessor logic (macros, includes), and resolves complex typedef chains correctly.

  • Regex driver

    A fallback driver implemented in pure Perl. It scans headers using regular expressions. While zero-dependency, it may struggle with heavily macro-laden code or complex C++ templates.

CONSTRUCTOR

new( ... )

my $binder = Affix::Wrap->new(
    project_files => [ 'lib.h' ],
    include_dirs  => [ '/usr/include' ],
    types         => {
        'my_opaque_t' => Pointer[Void],
        'my_int_t'    => Int64
    },
    driver        => 'Clang'
);
project_files

Required. An array reference of paths to the C header files (.h, .hpp, .hxx) you wish to parse.

include_dirs

Optional. An array reference of paths to search for #include "..." directives. The directory of every file listed in project_files is automatically added to this list.

types

Optional. A hash reference mapping type names to Affix type objects or definition strings.

These are registered via typedef before parsing begins. This is useful for:

  • Defining opaque types (e.g. mapping a complex C struct to Pointer[Void]).

  • Fixing integer widths (e.g. forcing off_t to Int64 across platforms).

  • Overriding definitions that the parser might misinterpret.

If the parser encounters a struct, enum, or typedef in the C header with the same name as an entry in this hash, the C definition is skipped to prevent redefinition warnings and ensure your override takes precedence.

driver

Optional. Explicitly select the parser driver. Values are 'Clang' or 'Regex'. If omitted, Affix::Wrap attempts to find the clang executable and falls back to Regex if unavailable.

METHODS

wrap( $lib, [$target] )

$binder->wrap( $lib );
$binder->wrap( $lib, 'My::Package' );

Parses the project files and immediately binds all found entities (functions, variables, constants, types) to the target package.

$lib

An instance of an Affix library object (created via load_library).

$target

Optional. The package name to install symbols into. Defaults to the calling package.

parse( [$entry_point] )

my @nodes = $binder->parse;

Parses the project files and returns a list of Node objects (see Data Model below). Use this if you want to inspect the C header structure or generate code strings for a static Perl module.

The nodes are sorted by file name and line number to ensure deterministic output order.

$entry_point

Optional. The specific file to start parsing from. Defaults to the first file in project_files.

Data Model

The parse() method returns a list of objects inheriting from Affix::Wrap::Entity.

All nodes provide at least two key methods:

  • affix_type: Returns a string of Perl code representing the type or declaration (e.g., "Int", "typedef Foo = Int">). Used for code generation.

  • affix( $lib, $pkg ): Performs the actual binding at runtime. Installs symbols into $pkg using $lib.

Affix::Wrap::Type

Represents a generic C type (e.g., int, void, size_t).

Affix::Wrap::Type::Pointer

Represents T* types. Wraps another type object.

Affix::Wrap::Type::Array

Represents T[N] fixed-size arrays. Wraps a type object and a count.

Affix::Wrap::Type::CodeRef

Represents function pointers (callbacks), e.g., void (*)(int).

  • ret: Return type object.

  • params: ArrayRef of argument type objects.

  • affix_type: Returns string Callback[[Args] => Ret].

Affix::Wrap::Argument

Function arguments. Stringifies to "Type Name".

Affix::Wrap::Member

Struct/Union members.

  • definition: If the member defines a nested struct/union inline, this holds that definition object.

  • affix_type: Returns the signature of the type OR the nested definition.

Affix::Wrap::Function

A C function declaration.

  • affix_type: Returns a complete Perl string to bind this function (e.g., affix $lib, name => ...).

  • affix( $lib, $pkg ): Installs the function into $pkg.

Affix::Wrap::Struct

A C struct or union definition.

  • tag: Either 'struct' or 'union'.

  • affix_type: Returns signature string Struct[ ... ] or Union[ ... ].

Affix::Wrap::Typedef

A name alias for another type.

  • underlying: The type object being aliased.

  • affix_type: Returns string typedef Name => UnderlyingType.

Note: In C, typedef struct { ... } Name; results in a Typedef object where underlying is the Struct object.

Affix::Wrap::Enum

An enumeration.

  • affix_type: Returns signature string Enum[ Name => Val, ... ]. String values/expressions in enums are quoted automatically to prevent eval errors.

Affix::Wrap::Variable

A global extern variable.

  • affix_type: Returns string pin my $var, $lib, name => Type.

  • affix( $lib, $pkg ): Installs the variable accessor into $pkg.

Affix::Wrap::Macro

A preprocessor #define. Only simple value macros are captured.

  • affix_type: Returns string use constant Name => Value. Expressions (e.g., A + B) are quoted as strings, while literals are preserved.

  • affix( undef, $pkg ): Installs the constant into $pkg.

Tutorials

Runtime Library Wrappers

If you want to use a C library immediately without creating a separate Perl module file, use the wrap method.

use Affix;
use Affix::Wrap;

my $lib = load_library('demo');

# This parses demo.h and installs subroutines, constants,
# and types directly into the calling package.
Affix::Wrap->new( project_files => ['demo.h'] )->wrap($lib);

# Now you can use them:
my $obj = Demo_CreateStruct();

Manual Control

If you need to filter which functions are bound or rename them, you can iterate over the AST manually instead of calling wrap:

my $binder = Affix::Wrap->new( project_files => ['demo.h'] );
for my $node ( $binder->parse ) {
    next if $node->name =~ m[^Internal_]; # Skip internal functions
    # Manually bind
    if ( $node->can('affix') ) {
        $node->affix($lib);
    }
}

Generating Affix Modules for CPAN

To create a distributable module (e.g., My::Lib.pm), use the affix_type method. This returns Perl source code strings.

use Affix::Wrap;
use Path::Tiny;

my $binder = Affix::Wrap->new( project_files => ['mylib.h'] );
my $ast    = $binder->parse;

my $code = <<~'PERL';
    package My::Lib;
    use v5.40;
    use Affix;

    # Load the library (user must ensure this exists)
    my $lib = load_library('mylib');

    PERL

for my $node (@$ast) {
    # Add POD documentation derived from C comments
    if ( defined $node->doc ) {
        $code .= "\n=head2 " . $node->name . "\n\n";
        $code .= $node->doc . "\n\n=cut\n";
    }

    # Generate Perl code
    if ( $node isa Affix::Wrap::Function ) {
        # e.g. "affix $lib, 'my_func', [Int], Void;"
        $code .= $node->affix_type . "\n";
    }
    elsif ( $node isa Affix::Wrap::Typedef ) {
        # e.g. "typedef MyStruct => Struct[ ... ];"
        $code .= $node->affix_type . ";\n";
    }
    elsif ( $node isa Affix::Wrap::Macro ) {
        # e.g. "use constant MAX_VAL => 100;"
        $code .= $node->affix_type . ";\n";
    }
    elsif ( $node isa Affix::Wrap::Variable ) {
        # e.g. "pin my $var, $lib, ..."
        $code .= $node->affix_type . ";\n";
    }
}

$code .= "\n1;\n";

path('lib/My/Lib.pm')->spew_utf8($code);

AUTHOR

Sanko Robinson <sanko@cpan.org>

COPYRIGHT

Copyright (C) 2026 by Sanko Robinson.

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.