Affix

 view release on metacpan or  search on metacpan

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

/**
 * Copyright (c) 2025 Sanko Robinson
 *
 * This source code is dual-licensed under the Artistic License 2.0 or the MIT License.
 * You may choose to use this code under the terms of either license.
 *
 * SPDX-License-Identifier: (Artistic-2.0 OR MIT)
 *
 * The documentation blocks within this file are licensed under the
 * Creative Commons Attribution 4.0 International License (CC BY 4.0).
 *
 * SPDX-License-Identifier: CC-BY-4.0
 */
/**
 * @file signature.c
 * @brief Implements the `infix` signature string parser and type printer.
 * @ingroup internal_core
 *
 * @details This module is responsible for two key functionalities that form the
 * user-facing API of the library:
 *
 * 1.  **Parsing:** It contains a hand-written recursive descent parser that transforms a
 *     human-readable signature string (e.g., `"({int, *char}) -> void"`) into an
 *     unresolved `infix_type` object graph. This is the **"Parse"** stage of the core
 *     data pipeline. The internal entry point for the "Parse" stage is `_infix_parse_type_internal`.
 *
 * 2.  **Printing:** It provides functions to serialize a fully resolved `infix_type`
 *     graph back into a canonical signature string. This is crucial for introspection,
 *     debugging, and verifying the library's understanding of a type.
 *
 * The public functions `infix_type_from_signature` and `infix_signature_parse`
 * are high-level orchestrators. They manage the entire **"Parse -> Copy -> Resolve -> Layout"**
 * pipeline, providing the user with a fully validated, self-contained, and ready-to-use
 * type object that is safe to use for the lifetime of its returned arena.
 */
#include "common/infix_internals.h"
#include <ctype.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/** @internal A thread-local pointer to the full signature string being parsed, used by `error.c` for rich error
 * reporting. */
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
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.
 */
INFIX_INTERNAL void _infix_set_parser_error(parser_state * state, infix_error_code_t code) {
    _infix_set_error(INFIX_CATEGORY_PARSER, code, (size_t)(state->p - state->start));
}
INFIX_INTERNAL void skip_whitespace(parser_state * state);

/**
 * @internal
 * @brief Advances the parser's cursor past any whitespace or C-style line comments.
 * @param[in,out] state The parser state to modify.
 */
INFIX_INTERNAL void skip_whitespace(parser_state * state) {
    while (true) {
        while (isspace((unsigned char)*state->p))
            state->p++;
        if (*state->p == '#')  // C-style line comments
            while (*state->p != '\n' && *state->p != '\0')
                state->p++;
        else
            break;
    }
}
/**
 * @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;
    errno = 0;  // Reset errno before call

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

                skip_whitespace(state);
                if (*state->p == ',') {
                    state->p++;
                    skip_whitespace(state);
                    if (*state->p == ')') {  // Trailing comma error.
                        _infix_set_parser_error(state, INFIX_CODE_UNEXPECTED_TOKEN);
                        return INFIX_ERROR_INVALID_ARGUMENT;
                    }
                }
                else if (*state->p != ')') {
                    _infix_set_parser_error(state, INFIX_CODE_UNEXPECTED_TOKEN);
                    return INFIX_ERROR_INVALID_ARGUMENT;
                }
                else
                    break;
            }
        }
    }
    skip_whitespace(state);
    if (*state->p != ')') {
        _infix_set_parser_error(state, INFIX_CODE_UNTERMINATED_AGGREGATE);
        return INFIX_ERROR_INVALID_ARGUMENT;
    }
    state->p++;
    // Parse Return Type
    skip_whitespace(state);
    if (state->p[0] != '-' || state->p[1] != '>') {
        _infix_set_parser_error(state, INFIX_CODE_MISSING_RETURN_TYPE);
        return INFIX_ERROR_INVALID_ARGUMENT;
    }
    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;
}
// High-Level API Implementation
/**
 * @internal
 * @brief The internal entry point for the signature parser (the "Parse" stage).
 *
 * This function takes a signature string and produces a raw, unresolved type
 * graph in a new, temporary arena. It is the core parsing logic, separated from the
 * higher-level functions that manage the full data pipeline. It is careful not to
 * modify the global error context string (`g_infix_last_signature_context`), which
 * is the responsibility of its public API callers.
 *
 * @param[out] out_type On success, receives the parsed type graph.
 * @param[out] out_arena On success, receives the temporary arena holding the graph. The caller is responsible for
 * destroying it.
 * @param[in] signature The signature string to parse.
 * @return `INFIX_SUCCESS` on success.
 */
c23_nodiscard infix_status _infix_parse_type_internal(infix_type ** out_type,
                                                      infix_arena_t ** out_arena,
                                                      const char * signature) {
    if (!out_type || !out_arena) {
        _infix_set_error(INFIX_CATEGORY_GENERAL, INFIX_CODE_NULL_POINTER, 0);
        return INFIX_ERROR_INVALID_ARGUMENT;
    }
    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') {
            _infix_set_parser_error(&state, INFIX_CODE_UNEXPECTED_TOKEN);
            type = nullptr;
        }
    }
    if (!type) {
        // If parsing failed at any point, clean up the temporary arena.
        infix_arena_destroy(*out_arena);
        *out_arena = nullptr;
        *out_type = nullptr;
        return INFIX_ERROR_INVALID_ARGUMENT;
    }
    *out_type = type;
    return INFIX_SUCCESS;
}
/**
 * @brief Parses a signature string representing a single data type.
 *
 * This function orchestrates the full **"Parse -> Estimate -> Copy -> Resolve -> Layout"** pipeline
 * for a single type, resulting in a fully resolved and laid-out `infix_type` object graph.
 *
 * @param[out] out_type On success, receives a pointer to the parsed type object.
 * @param[out] out_arena On success, receives a pointer to the arena holding the type. The caller is responsible for
 * freeing this with `infix_arena_destroy`.
 * @param[in] signature The signature string of the data type (e.g., `"{id:int, name:*char}"`).
 * @param[in] registry An optional type registry for resolving named types. Can be `nullptr`.
 * @return `INFIX_SUCCESS` on success.
 */
c23_nodiscard infix_status infix_type_from_signature(infix_type ** out_type,
                                                     infix_arena_t ** out_arena,
                                                     const char * signature,
                                                     infix_registry_t * registry) {
    _infix_clear_error();
    g_infix_last_signature_context = signature;  // Set context for rich error reporting.
    // "Parse" stage: Create a raw, unresolved type graph in 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, signature);
    if (status != INFIX_SUCCESS)
        return status;
    // Create the final arena that will be returned to the caller.
    *out_arena = infix_arena_create(4096);
    if (!*out_arena) {
        infix_arena_destroy(parser_arena);
        _infix_set_error(INFIX_CATEGORY_ALLOCATION, INFIX_CODE_OUT_OF_MEMORY, 0);
        return INFIX_ERROR_ALLOCATION_FAILED;
    }
    // "Copy" stage: Deep copy the raw graph into the final arena.
    infix_type * final_type = _copy_type_graph_to_arena(*out_arena, raw_type);
    infix_arena_destroy(parser_arena);  // The temporary graph is no longer needed.



( run in 0.550 second using v1.01-cache-2.11-cpan-ceb78f64989 )