view release on metacpan or search on metacpan
```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' );