Affix

 view release on metacpan or  search on metacpan

README.md  view on Meta::CPAN

```perl
use Affix qw[:all];     # Everything
use Affix qw[:types];   # Int, Float, Struct, Pointer, Enum...
use Affix qw[:memory];  # malloc, free, cast, dump, ptr_add...
use Affix qw[:lib];     # load_library, find_symbol, get_last_error_message...
use Affix qw[:pin];     # pin, unpin
```

# THE BASICS

Affix's API is designed to be expressive. Let's start at the beginning with the eponymous `affix( ... )` function.

## `affix( ... )`

Attaches a given symbol to a named perl sub in the current namespace.

```perl
affix libm, 'pow', [Double, Double] => Double;
warn pow( 3, 5 );

affix libc, 'puts', [String], Int;

infix/include/infix/infix.h  view on Meta::CPAN

        } named_reference;
    } meta;
};
/**
 * @struct infix_struct_member_t
 * @brief Describes a single member of a C struct or union.
 */
struct infix_struct_member_t {
    const char * name;  /**< The name of the member, or `nullptr` if anonymous. */
    infix_type * type;  /**< The `infix_type` of the member. */
    size_t offset;      /**< The byte offset of the member from the start of the aggregate. */
    uint8_t bit_width;  /**< The width of the bitfield in bits. 0 for standard members. */
    uint8_t bit_offset; /**< The bit offset within the byte (0-7). */
    bool is_bitfield;   /**< True if this member is a bitfield (even if width is 0). */
};
/**
 * @struct infix_function_argument_t
 * @brief Describes a single argument to a C function.
 */
struct infix_function_argument_t {
    const char * name; /**< The name of the argument, or `nullptr` if anonymous. */

infix/src/arch/aarch64/abi_arm64.c  view on Meta::CPAN

 *
 * @section platform_deviations Platform-Specific Deviations
 *
 * - **Variadic Calls (Apple macOS):** All variadic arguments are passed on the
 *   stack. Arguments smaller than 8 bytes are promoted to fill 8-byte stack slots.
 *
 * - **Variadic Calls (Windows on ARM):** The HFA rule is disabled for variadic
 *   arguments. Floating-point scalars are passed in GPRs, not VPRs.
 *
 * - **16-Byte Argument Alignment:**
 *   - **Standard/macOS:** 16-byte aggregates passed in GPRs must start in an
 *     even-numbered register (X0, X2, X4, X6).
 *   - **macOS Exception:** `__int128_t` does NOT require even-GPR alignment.
 *   - **Windows Exception:** Variadic 16-byte aggregates do NOT require even-GPR alignment.
 * @endinternal
 */
#include "arch/aarch64/abi_arm64_common.h"
#include "arch/aarch64/abi_arm64_emitters.h"
#include "common/infix_internals.h"
#include "common/utility.h"
#include <stdbool.h>

infix/src/arch/aarch64/abi_arm64.c  view on Meta::CPAN

                bool needs_alignment = true;
#if defined(INFIX_OS_MACOS)
                // macOS Deviation: `__int128_t` does not require even-GPR alignment.
                if (type->category == INFIX_TYPE_PRIMITIVE)
                    needs_alignment = false;
#elif defined(INFIX_OS_WINDOWS)
                // Windows Deviation: Variadic 16-byte arguments do not require even-GPR alignment.
                if (is_variadic_arg)
                    needs_alignment = false;
#endif
                // Standard rule: 16-byte args must start in an even-numbered GPR.
                if (needs_alignment && (gpr_count % 2 != 0))
                    gpr_count++;
                if (gpr_count + 1 < NUM_GPR_ARGS) {
                    layout->arg_locations[i].type = ARG_LOCATION_GPR_PAIR;
                    layout->arg_locations[i].reg_index = (uint8_t)gpr_count;
                    gpr_count += 2;
                    placed_in_register = true;
                }
            }
            else {  // Types <= 8 bytes passed in a single GPR.

infix/src/arch/aarch64/abi_arm64.c  view on Meta::CPAN

                                                                infix_reverse_t * context) {
    // If the return type is a large struct, the caller passes a hidden pointer in X8.
    // We must save this pointer into our return buffer location immediately, as X8 is volatile.
    bool ret_is_aggregate =
        (context->return_type->category == INFIX_TYPE_STRUCT || context->return_type->category == INFIX_TYPE_UNION ||
         context->return_type->category == INFIX_TYPE_ARRAY || context->return_type->category == INFIX_TYPE_COMPLEX);
    bool return_in_memory = ret_is_aggregate && context->return_type->size > 16;
    if (return_in_memory)  // str x8, [sp, #return_buffer_offset]
        emit_arm64_str_imm(buf, true, X8_REG, SP_REG, layout->return_buffer_offset);
    size_t gpr_idx = 0, vpr_idx = 0, current_saved_data_offset = 0;
    // Arguments passed on the caller's stack start at an offset from our new frame pointer.
    // [fp] points to old fp, [fp, #8] points to old lr. Args start at [fp, #16].
    size_t caller_stack_offset = 16;
    for (size_t i = 0; i < context->num_args; ++i) {
        infix_type * type = context->arg_types[i];
        bool is_variadic_arg = i >= context->num_fixed_args;
        int32_t arg_save_loc = (int32_t)(layout->saved_args_offset + current_saved_data_offset);
#if defined(INFIX_OS_MACOS)
        // On macOS, all variadic arguments are passed on the stack.
        // This is a special case that bypasses all register logic.
        if (is_variadic_arg) {
            // On macOS, variadic args smaller than 8 bytes are promoted to an 8-byte stack slot.

infix/src/arch/x64/abi_sysv_x64.c  view on Meta::CPAN


/**
 * @internal
 * @brief Recursively classifies the eightbytes of an aggregate type.
 * @details This is the core of the complex System V classification algorithm. It traverses
 * the fields of a struct/array, examining each 8-byte chunk ("eightbyte") and assigning it a
 * class (INTEGER, SSE, MEMORY). The classification is "merged" according to ABI rules
 * (e.g., if an eightbyte contains both INTEGER and SSE parts, it becomes INTEGER).
 *
 * @param type The type of the current member/element being examined.
 * @param offset The byte offset of this member from the start of the aggregate.
 * @param[in,out] classes An array of two `arg_class_t` that is updated during classification.
 * @param depth The current recursion depth (to prevent stack overflow on malicious input).
 * @param field_count A counter to prevent DoS from excessively complex types.
 * @return `true` if a condition forcing MEMORY classification is found, `false` otherwise.
 */
static bool classify_recursive(
    const infix_type * type, size_t offset, arg_class_t classes[2], int depth, size_t * field_count) {
    // A recursive call can be made with a NULL type (e.g., from a malformed array from fuzzer).
    if (type == nullptr)
        return false;  // Terminate recusion path.

infix/src/arch/x64/abi_sysv_x64.c  view on Meta::CPAN

    // If a struct is packed, its layout is explicit and should not be inferred
    // by recursive classification. Treat it as an opaque block of memory.
    // For classification purposes, this is equivalent to an integer array.
    if (type->category == INFIX_TYPE_PRIMITIVE) {
        (*field_count)++;
        // `long double` is a special case. It is passed in memory on the stack, not x87 registers.
        if (is_long_double(type)) {
            classes[0] = MEMORY;
            return true;
        }
        // Consider all eightbytes that the primitive occupies, not just the starting offset.
        size_t start_offset = offset;
        // Check for overflow before calculating end_offset
        if (type->size == 0)
            return false;
        if (start_offset > SIZE_MAX - (type->size - 1)) {
            classes[0] = MEMORY;
            return true;
        }
        size_t end_offset = start_offset + type->size - 1;
        size_t start_eightbyte = start_offset / 8;
        size_t end_eightbyte = end_offset / 8;
        arg_class_t new_class = (is_float(type) || is_double(type)) ? SSE : INTEGER;
        for (size_t index = start_eightbyte; index <= end_eightbyte && index < 2; ++index) {
            // Merge the new class with the existing class for this eightbyte.
            // The rule is: if an eightbyte contains both SSE and INTEGER parts, it is classified as INTEGER.
            if (classes[index] != new_class)
                classes[index] = (classes[index] == NO_CLASS) ? new_class : INTEGER;
        }
        return false;
    }
    if (type->category == INFIX_TYPE_POINTER) {
        (*field_count)++;
        size_t index = offset / 8;
        if (index < 2 && classes[index] != INTEGER)
            classes[index] = INTEGER;  // Pointers are always INTEGER class. Merge with existing class.
        return false;
    }
    if (type->category == INFIX_TYPE_ARRAY) {
        if (type->meta.array_info.element_type == nullptr)
            return false;
        // If the array elements have no size, iterating over them is pointless
        // and can cause a timeout if num_elements is large, as the offset never advances.
        // We only need to classify the element type once at the starting offset.
        if (type->meta.array_info.element_type->size == 0) {
            if (type->meta.array_info.num_elements > 0)
                // Classify the zero-sized element just once.
                return classify_recursive(type->meta.array_info.element_type, offset, classes, depth + 1, field_count);
            return false;  // An empty array of zero-sized structs has no effect on classification.
        }
        for (size_t i = 0; i < type->meta.array_info.num_elements; ++i) {
            // Check count *before* each recursive call inside the loop.
            if (*field_count > MAX_AGGREGATE_FIELDS_TO_CLASSIFY) {
                classes[0] = MEMORY;

infix/src/arch/x64/abi_sysv_x64.c  view on Meta::CPAN

                return true;
            }
            infix_struct_member * member = &type->meta.aggregate_info.members[i];
            // A generated type can have a NULL member type.
            // This is invalid, and the aggregate must be passed in memory.
            if (member->type == nullptr) {
                classes[0] = MEMORY;
                return true;
            }
            size_t member_offset = offset + member->offset;
            // If this member starts at or after the 16-byte boundary,
            // it cannot influence register classification, so we can skip it.
            if (member_offset >= 16)
                continue;
            if (classify_recursive(member->type, member_offset, classes, depth + 1, field_count))
                return true;  // Propagate unaligned discovery
        }
        return false;
    }
    return false;
}

infix/src/arch/x64/abi_sysv_x64.c  view on Meta::CPAN

            if (num_ret_classes > 0 && ret_classes[0] == MEMORY)
                return_in_memory = true;
        }
    }
    // The long double primitive is a special case that does not use the hidden pointer.
    if (is_long_double(ret_type))
        return_in_memory = false;
    // If the return value is passed by reference, save the pointer from RDI.
    if (return_in_memory)
        emit_mov_mem_reg(buf, RBP_REG, layout->return_buffer_offset, GPR_ARGS[gpr_idx++]);  // mov [rbp + offset], rdi
    // Stack arguments passed by the caller start at [rbp + 16].
    size_t stack_arg_offset = 16;
    for (size_t i = 0; i < context->num_args; i++) {
        int32_t arg_save_loc = layout->saved_args_offset + current_saved_data_offset;
        infix_type * current_type = context->arg_types[i];
        arg_class_t classes[2];
        size_t num_classes;
        classify_aggregate_sysv(current_type, classes, &num_classes);
        bool is_from_stack = false;
        // Determine if the argument is in registers or on the stack.
        if (classes[0] == MEMORY)

infix/src/arch/x64/abi_win_x64.c  view on Meta::CPAN

                // The value in the GPR save area IS the pointer we need. Load it directly.
                emit_mov_reg_mem(buf, RAX_REG, RSP_REG, source_offset);
            else
                // The value is the data itself. Get a pointer TO the saved data.
                emit_lea_reg_mem(buf, RAX_REG, RSP_REG, source_offset);
            // Store the final pointer into the args_array.
            emit_mov_mem_reg(buf, RSP_REG, layout->args_array_offset + i * sizeof(void *), RAX_REG);
        }
        else {
            // Argument was passed on the caller's stack.
            // After our prologue, caller stack args start at [rbp + 16 (ret addr + old rbp) + 32 (shadow space)].
            int32_t caller_stack_offset = 16 + SHADOW_SPACE + (stack_slot_offset * 8);
            if (passed_by_ref)
                // The value on the stack IS the pointer we need. Load it.
                emit_mov_reg_mem(buf, RAX_REG, RBP_REG, caller_stack_offset);
            else
                // The value on the stack is the data. Get a pointer TO it.
                emit_lea_reg_mem(buf, RAX_REG, RBP_REG, caller_stack_offset);
            // Store the final pointer into the args_array.
            emit_mov_mem_reg(buf, RSP_REG, layout->args_array_offset + i * sizeof(void *), RAX_REG);
            // Advance our offset into the caller's stack frame for the next argument.

infix/src/common/double_tap.h  view on Meta::CPAN

#define DBLTAP_PRINTF_FORMAT(fmt_index, arg_index)
#endif

// Public Test Harness Functions (wrapped by macros for convenience)
void tap_init(void);
void tap_plan(size_t count);
int tap_done(void);
void tap_bail_out(const char * reason, ...) DBLTAP_PRINTF_FORMAT(1, 2);
bool tap_ok(bool condition, const char * file, int line, const char * func, const char * expr, const char * name, ...)
    DBLTAP_PRINTF_FORMAT(6, 7);
bool tap_subtest_start(const char * name);
bool tap_subtest_end(void);
void tap_todo_start(const char * reason, ...) DBLTAP_PRINTF_FORMAT(1, 2);
void tap_todo_end(void);
void tap_skip(size_t count, const char * reason, ...) DBLTAP_PRINTF_FORMAT(2, 3);
void tap_skip_all(const char * reason, ...) DBLTAP_PRINTF_FORMAT(1, 2);
void diag(const char * fmt, ...) DBLTAP_PRINTF_FORMAT(1, 2);
void tap_note(const char * fmt, ...) DBLTAP_PRINTF_FORMAT(1, 2);

// Public Test Harness Macros
/** @brief Declares the total number of tests to be run in the current scope. Must be called before any tests. */
#define plan(count) tap_plan(count)
/** @brief Concludes testing, validates the plan, and returns an exit code based on success or failure. */

infix/src/common/double_tap.h  view on Meta::CPAN

#define bail_out(...) tap_bail_out(__VA_ARGS__)
/** @brief The core assertion macro. Checks a condition and prints an "ok" or "not ok" TAP line with diagnostics on
 * failure. */
#define ok(cond, ...) tap_ok(!!(cond), __FILE__, __LINE__, __func__, #cond, __VA_ARGS__)
/** @brief A convenience macro that always passes. Equivalent to `ok(true, ...)`. */
#define pass(...) ok(true, __VA_ARGS__)
/** @brief A convenience macro that always fails. Equivalent to `ok(false, ...)`. */
#define fail(...) ok(false, __VA_ARGS__)
/** @brief Defines a block of tests as a nested subtest, which gets its own plan and pass/fail status. */
#define subtest(name) \
    for (bool _tap_subtest_once = tap_subtest_start(name); _tap_subtest_once; _tap_subtest_once = tap_subtest_end())
/** @brief Marks a specified number of subsequent tests as skipped with a given reason. */
#define skip(count, ...) tap_skip(count, __VA_ARGS__)
/** @brief Marks all subsequent tests in the current scope as skipped. */
#define skip_all(...) tap_skip_all(__VA_ARGS__)
/** @brief Defines a block of tests that are expected to fail. A failure in a TODO block does not fail the overall
 * suite. */
#define TODO(reason) \
    for (int _tap_todo_once = (tap_todo_start(reason), 1); _tap_todo_once; _tap_todo_once = (tap_todo_end(), 0))
/** @brief Prints a diagnostic message to stderr, prefixed with '#'. Standard TAP practice for auxiliary information. */
#define diag(...) diag(__VA_ARGS__)
/** @brief Prints a diagnostic message (a note) to stdout, prefixed with '#'. */
#ifndef note
#define note(...) tap_note(__VA_ARGS__)
#endif
/** @brief Defines the main test function body where all tests are written. */
#define TEST void test_body(void)
void test_body(void);

infix/src/common/double_tap.h  view on Meta::CPAN


bool tap_ok(bool condition, const char * file, int line, const char * func, const char * expr, const char * name, ...) {
    _tap_ensure_initialized();
    if (current_state->skipping) {
        current_state->count++;
        return true;
    }
    char name_buffer[256] = {0};
    if (name && name[0] != '\0') {
        va_list args;
        va_start(args, name);
        vsnprintf(name_buffer, sizeof(name_buffer), name, args);
        va_end(args);
    }
    current_state->count++;
    if (!condition) {
        if (current_state->todo)
            current_state->failed_todo++;
        else {
            current_state->failed++;
            if (current_state == &state_stack[0])  // Only increment global fail count for top-level tests

infix/src/common/double_tap.h  view on Meta::CPAN

        fprintf(stdout, "#   ...\n");
    }
    fflush(stdout);
    return condition;
}

void tap_skip(size_t count, const char * reason, ...) {
    _tap_ensure_initialized();
    char buffer[256];
    va_list args;
    va_start(args, reason);
    vsnprintf(buffer, sizeof(buffer), reason, args);
    va_end(args);
    for (size_t i = 0; i < count; ++i) {
        current_state->count++;
        print_indent(stdout);
        printf("ok %llu # SKIP %s\n", (unsigned long long)current_state->count, buffer);
    }
    fflush(stdout);
}

void tap_skip_all(const char * reason, ...) {
    _tap_ensure_initialized();
    current_state->skipping = true;
    va_list args;
    va_start(args, reason);
    vsnprintf(current_state->skip_reason, sizeof(current_state->skip_reason), reason, args);
    va_end(args);
}

void tap_todo_start(const char * reason, ...) {
    _tap_ensure_initialized();
    current_state->todo = true;
    va_list args;
    va_start(args, reason);
    vsnprintf(current_state->todo_reason, sizeof(current_state->todo_reason), reason, args);
    va_end(args);
}

void tap_todo_end(void) {
    _tap_ensure_initialized();
    current_state->todo = false;
    current_state->todo_reason[0] = '\0';
}

void diag(const char * fmt, ...) {
    _tap_ensure_initialized();
    char buffer[1024];
    va_list args;
    va_start(args, fmt);
    vsnprintf(buffer, sizeof(buffer), fmt, args);
    va_end(args);
    print_indent(stderr);
    fprintf(stderr, "# %s\n", buffer);
    fflush(stderr);
}

void tap_note(const char * fmt, ...) {
    _tap_ensure_initialized();
    char buffer[1024];
    va_list args;
    va_start(args, fmt);
    vsnprintf(buffer, sizeof(buffer), fmt, args);
    va_end(args);
    print_indent(stdout);
    fprintf(stdout, "# %s\n", buffer);
    fflush(stdout);
}

void tap_bail_out(const char * reason, ...) {
    fprintf(stderr, "Bail out! ");
    va_list args;
    va_start(args, reason);
    vfprintf(stderr, reason, args);
    va_end(args);
    fprintf(stderr, "\n");
    fflush(stderr);
    exit(1);
}

bool tap_subtest_start(const char * name) {
    _tap_ensure_initialized();
    print_indent(stdout);
    fprintf(stdout, "# Subtest: %s\n", name);
    fflush(stdout);
    push_state();
    snprintf(current_state->subtest_name, sizeof(current_state->subtest_name), "%s", name);
    return true;  // Enters the `for` loop body.
}

bool tap_subtest_end(void) {

infix/src/common/utility.h  view on Meta::CPAN

    } while (0)
#endif  // DBLTAP_ENABLE
/**
 * @internal
 * @brief Declares the function prototype for `infix_dump_hex` for use in debug builds.
 * @details This function is an invaluable tool for inspecting the raw machine code generated
 *          by the JIT compiler or examining the memory layout of complex structs. It prints
 *          a detailed hexadecimal and ASCII dump of a memory region to the standard
 *          output, formatted for readability.
 *
 * @param data A pointer to the start of the memory block to dump.
 * @param size The number of bytes to dump.
 * @param title A descriptive title to print before and after the hex dump.
 */
void infix_dump_hex(const void * data, size_t size, const char * title);
#else  // INFIX_DEBUG_ENABLED is NOT defined or is zero (Release Mode)
/**
 * @internal
 * @def INFIX_DEBUG_PRINTF(...)
 * @brief A no-op macro for printing debug messages in release builds.
 * @details In release builds, this macro is defined as `((void)0)`, a standard C idiom

infix/src/core/error.c  view on Meta::CPAN

    g_infix_last_error.category = category;
    g_infix_last_error.code = code;
    g_infix_last_error.position = position;
    g_infix_last_error.system_error_code = 0;
    // Check if we can generate a rich parser error message.
    if (category == INFIX_CATEGORY_PARSER && g_infix_last_signature_context != nullptr) {
        // Generate a rich, GCC-style error message for parser failures.
        const char * signature = g_infix_last_signature_context;
        size_t sig_len = strlen(signature);
        const size_t radius = 20;  // Number of characters to show around the error position.
        // Calculate the start and end of the snippet to display.
        size_t start = (position > radius) ? (position - radius) : 0;
        size_t end = (position + radius < sig_len) ? (position + radius) : sig_len;
        // Add indicators if the snippet is truncated.
        const char * start_indicator = (start > 0) ? "... " : "";
        const char * end_indicator = (end < sig_len) ? " ..." : "";
        size_t start_indicator_len = (start > 0) ? 4 : 0;
        // Create the code snippet line.
        char snippet[128];
        snprintf(snippet,
                 sizeof(snippet),
                 "%s%.*s%s",
                 start_indicator,
                 (int)(end - start),
                 signature + start,
                 end_indicator);
        // Create the pointer line with a caret '^' under the error.
        char pointer[128];
        size_t caret_pos = position - start + start_indicator_len;
        snprintf(pointer, sizeof(pointer), "%*s^", (int)caret_pos, "");
        // Build the final multi-line message piece by piece to avoid buffer overflows.
        char * p = g_infix_last_error.message;
        size_t remaining = sizeof(g_infix_last_error.message);
        int written;
        // Write the snippet and pointer lines.
        written = snprintf(p, remaining, "\n\n  %s\n  %s", snippet, pointer);
        if (written < 0 || (size_t)written >= remaining) {
            // Fallback to a simple message on snprintf failure or buffer overflow.
            const char * msg = _get_error_message_for_code(code);

infix/src/core/signature.c  view on Meta::CPAN

extern INFIX_TLS const char * g_infix_last_signature_context;
/** @internal A safeguard against stack overflows from malicious or deeply nested signatures (e.g., `{{{{...}}}}`). */
#define MAX_RECURSION_DEPTH 32
/**
 * @internal
 * @struct parser_state
 * @brief Holds the complete state of the recursive descent parser during a single parse operation.
 */
typedef struct {
    const char * p;        /**< The current read position (cursor) in the signature string. */
    const char * start;    /**< The beginning of the signature string, used for calculating error positions. */
    infix_arena_t * arena; /**< The temporary arena for allocating the raw, unresolved type graph. */
    int depth;             /**< The current recursion depth, to prevent stack overflows. */
} parser_state;
// Forward Declarations for Mutually Recursive Parser Functions
static infix_type * parse_type(parser_state * state);
static infix_status parse_function_signature_details(parser_state * state,
                                                     infix_type ** out_ret_type,
                                                     infix_function_argument ** out_args,
                                                     size_t * out_num_args,
                                                     size_t * out_num_fixed_args);
// Parser Helper Functions
/**
 * @internal
 * @brief Sets a detailed parser error, capturing the current position in the string.
 * @param[in,out] state The current parser state.
 * @param[in] code The error code to set.
 */
static void set_parser_error(parser_state * state, infix_error_code_t code) {
    _infix_set_error(INFIX_CATEGORY_PARSER, code, (size_t)(state->p - state->start));
}
/**
 * @internal
 * @brief Advances the parser's cursor past any whitespace or C-style line comments.
 * @param[in,out] state The parser state to modify.
 */
static void skip_whitespace(parser_state * state) {
    while (true) {
        while (isspace((unsigned char)*state->p))
            state->p++;

infix/src/core/signature.c  view on Meta::CPAN

    }
}
/**
 * @internal
 * @brief Parses an unsigned integer from the string, used for array/vector sizes.
 * @param[in,out] state The parser state.
 * @param[out] out_val A pointer to store the parsed value.
 * @return `true` on success, `false` on failure.
 */
static bool parse_size_t(parser_state * state, size_t * out_val) {
    const char * start = state->p;
    char * end;
    unsigned long long val = strtoull(start, &end, 10);
    if (end == start) {
        set_parser_error(state, INFIX_CODE_UNEXPECTED_TOKEN);
        return false;
    }
    *out_val = (size_t)val;
    state->p = end;
    return true;
}
/**
 * @internal
 * @brief Parses a C-style identifier from the string.
 * @details This is used for member names, named types, and function argument names.
 * It handles simple identifiers (`my_var`) and C++-style namespaces (`NS::Name`).
 * @param[in,out] state The parser state.
 * @return An arena-allocated string for the identifier, or `nullptr` on failure.
 */
static const char * parse_identifier(parser_state * state) {
    skip_whitespace(state);
    const char * start = state->p;
    if (!isalpha((unsigned char)*start) && *start != '_')
        return nullptr;
    while (isalnum((unsigned char)*state->p) || *state->p == '_' || *state->p == ':') {
        if (*state->p == ':' && state->p[1] != ':')
            break;  // A single ':' is not part of an identifier.
        if (*state->p == ':')
            state->p++;  // Consume first ':' of '::'
        state->p++;
    }
    size_t len = state->p - start;
    if (len == 0)
        return nullptr;
    char * name = infix_arena_calloc(state->arena, 1, len + 1, 1);
    if (!name) {
        _infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, (size_t)(state->p - state->start));
        return nullptr;
    }
    infix_memcpy((void *)name, start, len);
    name[len] = '\0';
    return name;
}
/**
 * @internal
 * @brief Consumes a specific keyword from the string (e.g., "int", "struct").
 * @details This function is careful to match whole words only. For example, it will
 * successfully consume "int" from "int x", but will fail on "integer", preventing
 * false positives.
 * @param[in,out] state The parser state.

infix/src/core/signature.c  view on Meta::CPAN

                    set_parser_error(state, INFIX_CODE_TYPE_TOO_LARGE);
                    return nullptr;
                }
                bit_width = (uint8_t)width_val;
                is_bitfield = true;
            }

            member_node * node = infix_arena_calloc(state->arena, 1, sizeof(member_node), _Alignof(member_node));
            if (!node) {
                _infix_set_error(
                    INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, (size_t)(state->p - state->start));
                return nullptr;
            }
            // The member offset is not calculated here; it will be done later
            // by `infix_type_create_struct` or `_infix_type_recalculate_layout`.
            if (is_bitfield)
                node->m = infix_type_create_bitfield_member(name, member_type, 0, bit_width);
            else
                node->m = infix_type_create_member(name, member_type, 0);

            node->next = nullptr;

infix/src/core/signature.c  view on Meta::CPAN

            }
        }
    }
    *out_num_members = num_members;
    if (num_members == 0)
        return nullptr;
    // Convert the temporary linked list to a flat array in the arena.
    infix_struct_member * members =
        infix_arena_calloc(state->arena, num_members, sizeof(infix_struct_member), _Alignof(infix_struct_member));
    if (!members) {
        _infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, (size_t)(state->p - state->start));
        return nullptr;
    }
    member_node * current = head;
    for (size_t i = 0; i < num_members; i++) {
        members[i] = current->m;
        current = current->next;
    }
    return members;
}
/**
 * @internal
 * @brief Parses a struct (`{...}`) or union (`<...>`).
 * @param[in,out] state The parser state.
 * @param[in] start_char The opening delimiter ('{' or '<').
 * @param[in] end_char The closing delimiter ('}' or '>').
 * @return A pointer to the new `infix_type`, or `nullptr` on failure.
 */
static infix_type * parse_aggregate(parser_state * state, char start_char, char end_char) {
    if (state->depth >= MAX_RECURSION_DEPTH) {
        set_parser_error(state, INFIX_CODE_RECURSION_DEPTH_EXCEEDED);
        return nullptr;
    }
    state->depth++;
    if (*state->p != start_char) {
        set_parser_error(state, INFIX_CODE_UNEXPECTED_TOKEN);
        state->depth--;
        return nullptr;
    }
    state->p++;
    size_t num_members = 0;
    infix_struct_member * members = parse_aggregate_members(state, end_char, &num_members);
    // If member parsing failed, an error is already set. Propagate the failure.
    if (!members && infix_get_last_error().code != INFIX_CODE_SUCCESS) {
        state->depth--;
        return nullptr;
    }
    if (*state->p != end_char) {
        set_parser_error(state, INFIX_CODE_UNTERMINATED_AGGREGATE);
        state->depth--;
        return nullptr;
    }
    state->p++;
    infix_type * agg_type = nullptr;
    infix_status status = (start_char == '{') ? infix_type_create_struct(state->arena, &agg_type, members, num_members)
                                              : infix_type_create_union(state->arena, &agg_type, members, num_members);
    if (status != INFIX_SUCCESS) {
        state->depth--;
        return nullptr;
    }
    state->depth--;
    return agg_type;
}
/**
 * @internal

infix/src/core/signature.c  view on Meta::CPAN

 * @param[in,out] state The parser state.
 * @return A pointer to the parsed `infix_type`, or `nullptr` on failure.
 */
static infix_type * parse_type(parser_state * state) {
    if (state->depth >= MAX_RECURSION_DEPTH) {
        set_parser_error(state, INFIX_CODE_RECURSION_DEPTH_EXCEEDED);
        return nullptr;
    }
    state->depth++;
    skip_whitespace(state);
    // Capture the offset from the start of the signature string *before* parsing the type.
    size_t current_offset = state->p - state->start;
    infix_type * result_type = nullptr;
    const char * p_before_type = state->p;
    if (*state->p == '@') {  // Named type reference: `@MyStruct`
        state->p++;
        const char * name = parse_identifier(state);
        if (!name) {
            set_parser_error(state, INFIX_CODE_UNEXPECTED_TOKEN);
            state->depth--;
            return nullptr;
        }

infix/src/core/signature.c  view on Meta::CPAN

            size_t num_args = 0, num_fixed = 0;
            if (parse_function_signature_details(state, &ret_type, &args, &num_args, &num_fixed) != INFIX_SUCCESS) {
                state->depth--;
                return nullptr;
            }
            // Manually construct a function pointer type object.
            // This is represented internally as a pointer-like type with extra metadata.
            infix_type * func_type = infix_arena_calloc(state->arena, 1, sizeof(infix_type), _Alignof(infix_type));
            if (!func_type) {
                _infix_set_error(
                    INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, (size_t)(state->p - state->start));
                state->depth--;
                return nullptr;
            }
            func_type->size = sizeof(void *);
            func_type->alignment = _Alignof(void *);
            func_type->is_arena_allocated = true;
            func_type->category = INFIX_TYPE_REVERSE_TRAMPOLINE;  // Special category for function types.
            func_type->meta.func_ptr_info.return_type = ret_type;
            func_type->meta.func_ptr_info.args = args;
            func_type->meta.func_ptr_info.num_args = num_args;

infix/src/core/signature.c  view on Meta::CPAN

            skip_whitespace(state);
            if (*state->p == ')' || *state->p == ';')
                break;
            const char * name = parse_optional_name_prefix(state);
            infix_type * arg_type = parse_type(state);
            if (!arg_type)
                return INFIX_ERROR_INVALID_ARGUMENT;
            arg_node * node = infix_arena_calloc(state->arena, 1, sizeof(arg_node), _Alignof(arg_node));
            if (!node) {
                _infix_set_error(
                    INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, (size_t)(state->p - state->start));
                return INFIX_ERROR_ALLOCATION_FAILED;
            }
            node->arg.type = arg_type;
            node->arg.name = name;
            node->next = nullptr;
            if (!head)
                head = tail = node;
            else {
                tail->next = node;
                tail = node;

infix/src/core/signature.c  view on Meta::CPAN

                skip_whitespace(state);
                if (*state->p == ')')
                    break;
                const char * name = parse_optional_name_prefix(state);
                infix_type * arg_type = parse_type(state);
                if (!arg_type)
                    return INFIX_ERROR_INVALID_ARGUMENT;
                arg_node * node = infix_arena_calloc(state->arena, 1, sizeof(arg_node), _Alignof(arg_node));
                if (!node) {
                    _infix_set_error(
                        INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, (size_t)(state->p - state->start));
                    return INFIX_ERROR_ALLOCATION_FAILED;
                }
                node->arg.type = arg_type;
                node->arg.name = name;
                node->next = nullptr;
                if (!head)
                    head = tail = node;
                else {
                    tail->next = node;
                    tail = node;

infix/src/core/signature.c  view on Meta::CPAN

    }
    state->p += 2;
    *out_ret_type = parse_type(state);
    if (!*out_ret_type)
        return INFIX_ERROR_INVALID_ARGUMENT;
    // Convert linked list of args to a flat array.
    infix_function_argument * args = (num_args > 0)
        ? infix_arena_calloc(state->arena, num_args, sizeof(infix_function_argument), _Alignof(infix_function_argument))
        : nullptr;
    if (num_args > 0 && !args) {
        _infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, (size_t)(state->p - state->start));
        return INFIX_ERROR_ALLOCATION_FAILED;
    }
    arg_node * current = head;
    for (size_t i = 0; i < num_args; i++) {
        args[i] = current->arg;
        current = current->next;
    }
    *out_args = args;
    *out_num_args = num_args;
    return INFIX_SUCCESS;

infix/src/core/signature.c  view on Meta::CPAN

    if (!signature || *signature == '\0') {
        _infix_set_error(INFIX_CATEGORY_PARSER, INFIX_CODE_EMPTY_SIGNATURE, 0);
        return INFIX_ERROR_INVALID_ARGUMENT;
    }
    // The top-level public API is responsible for setting g_infix_last_signature_context.
    *out_arena = infix_arena_create(4096);
    if (!*out_arena) {
        _infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, 0);
        return INFIX_ERROR_ALLOCATION_FAILED;
    }
    parser_state state = {.p = signature, .start = signature, .arena = *out_arena, .depth = 0};
    infix_type * type = parse_type(&state);
    if (type) {
        skip_whitespace(&state);
        // After successfully parsing a type, ensure there is no trailing junk.
        if (state.p[0] != '\0') {
            set_parser_error(&state, INFIX_CODE_UNEXPECTED_TOKEN);
            type = nullptr;
        }
    }
    if (!type) {

infix/src/core/signature.c  view on Meta::CPAN

 * @brief A safe `vsnprintf` wrapper for building the signature string.
 * Updates the printer state and sets an error on buffer overflow.
 * @param[in,out] state The printer state.
 * @param[in] fmt The `printf`-style format string.
 * @param[in] ... Arguments for the format string.
 */
static void _print(printer_state * state, const char * fmt, ...) {
    if (state->status != INFIX_SUCCESS)
        return;
    va_list args;
    va_start(args, fmt);
    int written = vsnprintf(state->p, state->remaining, fmt, args);
    va_end(args);
    if (written < 0 || (size_t)written >= state->remaining)
        // If snprintf failed or would have overflowed, mark an error.
        state->status = INFIX_ERROR_INVALID_ARGUMENT;
    else {
        state->p += written;
        state->remaining -= written;
    }
}

infix/src/core/type_registry.c  view on Meta::CPAN

/**
 * @internal
 * @struct _registry_parser_state_t
 * @brief A minimal parser state for processing registry definition strings.
 * @details This is distinct from the main signature `parser_state` because the
 * grammar for definitions (`@Name=...;`) is much simpler and can be handled
 * with a simpler, non-recursive parser.
 */
typedef struct {
    const char * p;     /**< The current position in the definition string. */
    const char * start; /**< The start of the definition string for error reporting. */
} _registry_parser_state_t;
/**
 * @internal
 * @brief Skips whitespace and C++-style line comments in a definition string.
 * @param state The parser state to advance.
 */
static void _registry_parser_skip_whitespace(_registry_parser_state_t * state) {
    while (1) {
        while (isspace((unsigned char)*state->p))
            state->p++;

infix/src/core/type_registry.c  view on Meta::CPAN

/**
 * @internal
 * @brief Parses a type name (e.g., `MyType`, `NS::MyType`) from the definition string.
 * @param state The parser state to advance.
 * @param buffer A buffer to store the parsed name.
 * @param buf_size The size of the buffer.
 * @return A pointer to the `buffer` on success, or `nullptr` if no valid identifier is found.
 */
static char * _registry_parser_parse_name(_registry_parser_state_t * state, char * buffer, size_t buf_size) {
    _registry_parser_skip_whitespace(state);
    const char * name_start = state->p;
    while (isalnum((unsigned char)*state->p) || *state->p == '_' || *state->p == ':') {
        if (*state->p == ':' && state->p[1] != ':')
            break;  // Handle single colon as non-identifier char.
        if (*state->p == ':')
            state->p++;  // Skip the first ':' of '::'
        state->p++;
    }
    size_t len = state->p - name_start;
    if (len == 0 || len >= buf_size)
        return nullptr;
    infix_memcpy(buffer, name_start, len);
    buffer[len] = '\0';
    return buffer;
}
/**
 * @brief Parses a string of type definitions and adds them to a registry.
 *
 * @details This function uses a robust **three-pass approach** to handle complex dependencies,
 * including out-of-order and mutually recursive definitions.
 *
 * - **Pass 1 (Scan & Index):** The entire definition string is scanned. The parser

infix/src/core/type_registry.c  view on Meta::CPAN

 * @param[in] registry The registry to populate.
 * @param[in] definitions A semicolon-separated string of definitions.
 * @return `INFIX_SUCCESS` on success, or an error code on failure.
 */
c23_nodiscard infix_status infix_register_types(infix_registry_t * registry, const char * definitions) {
    _infix_clear_error();
    if (!registry || !definitions) {
        _infix_set_error(INFIX_CATEGORY_GENERAL, INFIX_CODE_NULL_POINTER, 0);
        return INFIX_ERROR_INVALID_ARGUMENT;
    }
    _registry_parser_state_t state = {.p = definitions, .start = definitions};
    g_infix_last_signature_context = definitions;
    // A temporary structure to hold information about each definition found in Pass 1.
    struct def_info {
        _infix_registry_entry_t * entry;
        const char * def_body_start;
        size_t def_body_len;
    };
    size_t defs_capacity = 64;  // Start with an initial capacity.
    struct def_info * defs_found = infix_malloc(sizeof(struct def_info) * defs_capacity);
    if (!defs_found) {
        _infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, 0);
        return INFIX_ERROR_ALLOCATION_FAILED;
    }
    size_t num_defs_found = 0;
    infix_status final_status = INFIX_SUCCESS;

    // Pass 1: Scan & Index all names and their definition bodies.
    while (*state.p != '\0') {
        _registry_parser_skip_whitespace(&state);
        if (*state.p == '\0')
            break;
        if (*state.p != '@') {
            _infix_set_error(INFIX_CATEGORY_PARSER, INFIX_CODE_UNEXPECTED_TOKEN, state.p - state.start);
            final_status = INFIX_ERROR_INVALID_ARGUMENT;
            goto cleanup;
        }
        state.p++;
        char name_buffer[256];
        if (!_registry_parser_parse_name(&state, name_buffer, sizeof(name_buffer))) {
            _infix_set_error(INFIX_CATEGORY_PARSER, INFIX_CODE_UNEXPECTED_TOKEN, state.p - state.start);
            final_status = INFIX_ERROR_INVALID_ARGUMENT;
            goto cleanup;
        }
        _infix_registry_entry_t * entry = _registry_lookup(registry, name_buffer);
        _registry_parser_skip_whitespace(&state);
        if (*state.p == '=') {  // This is a full definition.
            state.p++;
            _registry_parser_skip_whitespace(&state);
            // It's an error to redefine a type that wasn't a forward declaration.
            if (entry && !entry->is_forward_declaration) {
                _infix_set_error(INFIX_CATEGORY_PARSER, INFIX_CODE_UNEXPECTED_TOKEN, state.p - state.start);
                final_status = INFIX_ERROR_INVALID_ARGUMENT;
                goto cleanup;
            }
            if (!entry) {  // If it doesn't exist, create it.
                entry = _registry_insert(registry, name_buffer);
                if (!entry) {
                    final_status = INFIX_ERROR_ALLOCATION_FAILED;
                    goto cleanup;
                }
            }
            if (num_defs_found >= defs_capacity) {
                size_t new_capacity = defs_capacity * 2;
                struct def_info * new_defs = infix_realloc(defs_found, sizeof(struct def_info) * new_capacity);
                if (!new_defs) {
                    _infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, state.p - state.start);
                    final_status = INFIX_ERROR_ALLOCATION_FAILED;
                    goto cleanup;
                }
                defs_found = new_defs;
                defs_capacity = new_capacity;
            }
            // Find the end of the type definition body (the next top-level ';').
            defs_found[num_defs_found].entry = entry;
            defs_found[num_defs_found].def_body_start = state.p;
            int nest_level = 0;
            const char * body_end = state.p;
            while (*body_end != '\0' &&
                   !(*body_end == ';' &&
                     nest_level == 0)) {  // Explicitly check for and skip over the '->' token as a single unit.
                if (*body_end == '-' && body_end[1] == '>') {
                    body_end += 2;  // Advance the pointer past the entire token.
                    continue;       // Continue to the next character in the loop.
                }
                if (*body_end == '{' || *body_end == '<' || *body_end == '(' || *body_end == '[')

infix/src/core/type_registry.c  view on Meta::CPAN

                body_end++;
            }
            defs_found[num_defs_found].def_body_len = body_end - state.p;
            state.p = body_end;
            num_defs_found++;
        }
        else if (*state.p == ';') {  // This is a forward declaration.
            if (entry) {
                // If the type is already fully defined, re-declaring it as a forward decl is an error.
                if (!entry->is_forward_declaration) {
                    _infix_set_error(INFIX_CATEGORY_PARSER, INFIX_CODE_UNEXPECTED_TOKEN, state.p - state.start);
                    final_status = INFIX_ERROR_INVALID_ARGUMENT;
                    goto cleanup;
                }
                // If it's already a forward declaration, this is a no-op.
            }
            else {
                entry = _registry_insert(registry, name_buffer);
                if (!entry) {
                    final_status = INFIX_ERROR_ALLOCATION_FAILED;
                    goto cleanup;

infix/src/core/type_registry.c  view on Meta::CPAN

                entry->type->is_incomplete = true;  // Mark as incomplete
                entry->type->arena = registry->arena;
                entry->type->category = INFIX_TYPE_STRUCT;
                entry->type->size = 0;
                entry->type->alignment = 1;  // Minimal alignment to pass basic validation
                entry->type->name = entry->name;
            }
            entry->is_forward_declaration = true;
        }
        else {
            _infix_set_error(INFIX_CATEGORY_PARSER, INFIX_CODE_UNEXPECTED_TOKEN, state.p - state.start);
            final_status = INFIX_ERROR_INVALID_ARGUMENT;
            goto cleanup;
        }
        if (*state.p == ';')
            state.p++;
    }
    // Pass 2: Parse the bodies of all found definitions into the registry.
    for (size_t i = 0; i < num_defs_found; ++i) {
        _infix_registry_entry_t * entry = defs_found[i].entry;
        // Make a temporary, null-terminated copy of the definition body substring.
        char * body_copy = infix_malloc(defs_found[i].def_body_len + 1);
        if (!body_copy) {
            _infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, 0);
            final_status = INFIX_ERROR_ALLOCATION_FAILED;
            goto cleanup;
        }
        infix_memcpy(body_copy, defs_found[i].def_body_start, defs_found[i].def_body_len);
        body_copy[defs_found[i].def_body_len] = '\0';
        // "Parse" step: parse into a temporary arena.
        infix_type * raw_type = nullptr;
        infix_arena_t * parser_arena = nullptr;
        infix_status status = _infix_parse_type_internal(&raw_type, &parser_arena, body_copy);
        infix_free(body_copy);
        if (status != INFIX_SUCCESS) {
            // Adjust the error position to be relative to the full definition string.
            infix_error_details_t err = infix_get_last_error();
            _infix_set_error(err.category, err.code, (defs_found[i].def_body_start - definitions) + err.position);
            final_status = INFIX_ERROR_INVALID_ARGUMENT;
            infix_arena_destroy(parser_arena);
            goto cleanup;
        }
        const infix_type * type_to_alias = raw_type;
        // If the RHS is another named type (e.g., @MyAlias = @ExistingType),
        // we need to resolve it first to get the actual type we're aliasing.
        if (raw_type->category == INFIX_TYPE_NAMED_REFERENCE) {
            _infix_registry_entry_t * existing_entry = _registry_lookup(registry, raw_type->meta.named_reference.name);
            if (existing_entry && existing_entry->type)
                type_to_alias = existing_entry->type;
            else {
                size_t relative_pos = raw_type->source_offset;
                _infix_set_error(INFIX_CATEGORY_PARSER,
                                 INFIX_CODE_UNRESOLVED_NAMED_TYPE,
                                 (size_t)(defs_found[i].def_body_start - definitions) + relative_pos);
                final_status = INFIX_ERROR_INVALID_ARGUMENT;
                infix_arena_destroy(parser_arena);
                goto cleanup;
            }
        }
        // Prepare the new definition.
        infix_type * new_def = nullptr;
        if (!type_to_alias->is_arena_allocated) {
            // This is a static type (e.g., primitive). We MUST create a mutable
            // copy in the registry's arena before we can attach a name to it.

infix/src/core/type_registry.c  view on Meta::CPAN

    }
    // Pass 3: Resolve and layout all the newly defined types.
    for (size_t i = 0; i < num_defs_found; ++i) {
        _infix_registry_entry_t * entry = defs_found[i].entry;
        if (entry->type) {
            // "Resolve" and "Layout" steps.
            if (_infix_resolve_type_graph_inplace(&entry->type, registry) != INFIX_SUCCESS) {
                // The error was set inside resolve (relative to body).
                // We need to re-base it to the full definitions string.
                infix_error_details_t err = infix_get_last_error();
                size_t body_offset = defs_found[i].def_body_start - definitions;
                _infix_set_error(err.category, err.code, body_offset + err.position);
                final_status = INFIX_ERROR_INVALID_ARGUMENT;
                goto cleanup;
            }
            _infix_type_recalculate_layout(entry->type);
        }
    }
cleanup:
    infix_free(defs_found);
    return final_status;

infix/src/core/type_registry.c  view on Meta::CPAN

 * @brief Advances the iterator to the next defined type in the registry.
 *
 * @param[in,out] iterator The iterator to advance.
 * @return `true` if the iterator was advanced to a valid type, or `false` if there are
 *         no more types to visit.
 */
c23_nodiscard bool infix_registry_iterator_next(infix_registry_iterator_t * iter) {
    if (!iter || !iter->registry)
        return false;
    const _infix_registry_entry_t * entry = iter->current_entry;
    // If we have a current entry, start from the next one in the chain.
    if (iter->current_entry)
        entry = entry->next;
    // Otherwise, if we are starting, begin with the head of the current bucket.
    else if (iter->current_bucket < iter->registry->num_buckets)
        entry = iter->registry->buckets[iter->current_bucket];
    while (true) {
        // Traverse the current chain looking for a valid entry.
        while (entry) {
            if (entry->type && !entry->is_forward_declaration) {
                // Found one. Update the iterator and return successfully.
                iter->current_entry = entry;
                return true;
            }

infix/src/core/types.c  view on Meta::CPAN

c23_nodiscard infix_type * infix_type_create_pointer(void) { return &_infix_type_pointer; }
/**
 * @brief Creates a static descriptor for the `void` type.
 * @return A pointer to the static `infix_type` descriptor. Does not need to be freed.
 */
c23_nodiscard infix_type * infix_type_create_void(void) { return &_infix_type_void; }
/**
 * @brief A factory function to create an `infix_struct_member`.
 * @param[in] name The name of the member (optional, can be `nullptr`).
 * @param[in] type The `infix_type` of the member.
 * @param[in] offset The byte offset of the member from the start of its parent aggregate.
 * @return An initialized `infix_struct_member` object.
 */
infix_struct_member infix_type_create_member(const char * name, infix_type * type, size_t offset) {
    return (infix_struct_member){name, type, offset, 0, 0, false};
}
/**
 * @brief A factory function to create a bitfield `infix_struct_member`.
 * @param[in] name The name of the member.
 * @param[in] type The integer `infix_type` of the bitfield.
 * @param[in] offset The byte offset (usually 0 for automatic layout).

infix/src/core/types.c  view on Meta::CPAN

                member->bit_offset = 0;

                if (align > max_alignment)
                    max_alignment = align;
                continue;
            }

            // Standard Bitfield
            // Simplified System V packing: pack into current byte if it fits.
            if (current_bit_offset + member->bit_width > 8) {
                // Overflow: move to start of next byte
                if (current_byte_offset == SIZE_MAX) {
                    _infix_set_error(INFIX_CATEGORY_PARSER, INFIX_CODE_INTEGER_OVERFLOW, 0);
                    return false;
                }
                current_byte_offset++;
                current_bit_offset = 0;
            }

            member->offset = current_byte_offset;
            member->bit_offset = current_bit_offset;

infix/src/core/types.c  view on Meta::CPAN

            type->alignment = type->meta.array_info.element_type->alignment;
            type->size = type->meta.array_info.element_type->size * type->meta.array_info.num_elements;
        }
    }
}
/**
 * @internal
 * @brief Public-internal wrapper for the recursive layout recalculation function.
 *
 * @details This function serves as the entry point for the "Layout" stage. It initializes
 * the cycle detection mechanism and starts the recursive traversal of the type graph.
 *
 * @param[in,out] type The root of the type graph to recalculate. The graph is modified in-place.
 */
void _infix_type_recalculate_layout(infix_type * type) {
    // Create a temporary arena solely for the visited list's lifetime.
    infix_arena_t * temp_arena = infix_arena_create(1024);
    if (!temp_arena)
        return;
    recalc_visited_node_t * visited_head = nullptr;
    _infix_type_recalculate_layout_recursive(temp_arena, type, &visited_head);

lib/Affix.c  view on Meta::CPAN

        }
    }
    else if (!SvOK(sv))
        *(void **)p = nullptr;
    else
        croak("Argument for a callback must be a code reference or undef.");
}
static SV * _format_parse_error(pTHX_ const char * context_msg, const char * signature, infix_error_details_t err) {
    STRLEN sig_len = strlen(signature);
    int radius = 20;
    size_t start = (err.position > radius) ? (err.position - radius) : 0;
    size_t end = (err.position + radius < sig_len) ? (err.position + radius) : sig_len;
    const char * start_indicator = (start > 0) ? "... " : "";
    const char * end_indicator = (end < sig_len) ? " ..." : "";
    int start_indicator_len = (start > 0) ? 4 : 0;
    char snippet[128];
    snprintf(
        snippet, sizeof(snippet), "%s%.*s%s", start_indicator, (int)(end - start), signature + start, end_indicator);
    char pointer[128];
    int caret_pos = err.position - start + start_indicator_len;
    snprintf(pointer, sizeof(pointer), "%*s^", caret_pos, "");
    return sv_2mortal(newSVpvf("Failed to parse signature %s:\n\n  %s\n  %s\n\nError: %s (at position %zu)",
                               context_msg,
                               snippet,
                               pointer,
                               err.message,
                               err.position));
}
XS_INTERNAL(Affix_Lib_as_string) {
    dVAR;

lib/Affix.pm  view on Meta::CPAN

            #~ https://github.com/bennoleslie/haskell-shared-example
            #~ https://www.hobson.space/posts/haskell-foreign-library/
            field $haskell : reader : param //= _can_run qw[ghc cabal];

            #~ https://peterme.net/dynamic-libraries-in-nim.html
            field $nim : reader : param //= _can_run qw[nim];    # .nim => .c

            #~ https://odin-lang.org/news/calling-odin-from-python/
            field $odin : reader : param //= _can_run qw[odin];

            #~ https://p-org.github.io/P/getstarted/install/#step-4-recommended-ide-optional
            #~ https://p-org.github.io/P/getstarted/usingP/#compiling-a-p-program
            field $p : reader : param //= _can_run qw[p];    # .p => C#

            #~ https://blog.asleson.org/2021/02/23/how-to-writing-a-c-shared-library-in-rust/
            field $rust : reader : param //= _can_run qw[cargo];

            #~ swiftc point.swift -emit-module -emit-library
            #~ https://forums.swift.org/t/creating-a-c-accessible-shared-library-in-swift/45329/5
            #~ https://theswiftdev.com/building-static-and-dynamic-swift-libraries-using-the-swift-compiler/#should-i-choose-dynamic-or-static-linking
            field $swift : reader : param //= _can_run qw[swiftc];

lib/Affix.pod  view on Meta::CPAN

No functions are exported by default. You may import them individually or using tags.

    use Affix qw[:all];     # Everything
    use Affix qw[:types];   # Int, Float, Struct, Pointer, Enum...
    use Affix qw[:memory];  # malloc, free, cast, dump, ptr_add...
    use Affix qw[:lib];     # load_library, find_symbol, get_last_error_message...
    use Affix qw[:pin];     # pin, unpin

=head1 THE BASICS

Affix's API is designed to be expressive. Let's start at the beginning with the eponymous C<affix( ... )> function.

=head2 C<affix( ... )>

Attaches a given symbol to a named perl sub in the current namespace.

    affix libm, 'pow', [Double, Double] => Double;
    warn pow( 3, 5 );

    affix libc, 'puts', [String], Int;
    puts( 'Hello' );



( run in 0.346 second using v1.01-cache-2.11-cpan-ff066701436 )