Skip to main content

c_src/libwrouter/src/symbol.c

#include "symbol.h"
#include "arena.h"
#include "wrouter.h"
#include <stddef.h>
#include <errno.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

static int symbol_table_next_slot(symbol_table_t *tbl, size_t len, size_t *slot)
{
    if (tbl->count + len > tbl->capacity) {
        size_t new_cap = tbl->capacity ? tbl->capacity * 2 : 1024;

        while (new_cap < tbl->count + len)
            new_cap *= 2;

        const char **new_base = realloc(tbl->base, new_cap * sizeof(*tbl->base));
        if (!new_base)
            return -ENOMEM;

        tbl->base = new_base;
        tbl->capacity = new_cap;
    }

    *slot = tbl->count;
    tbl->count += len;
    return 0;
}

const char *symbol_append(symbol_table_t *tbl, const char *str, size_t len, wrouter_error_t *err)
{
    if (tbl->count >= UINT16_MAX) {
        *err = WROUTER_ERR_OUT_OF_RANGE;
        return NULL;
    }

    // Check for duplicates.
    for (size_t i = 0; i < tbl->count; i++)
        if (strncmp(tbl->base[i], str, len) == 0 && tbl->base[i][len] == '\0')
            return tbl->base[i];

    // Get a slot in the table for the string pointer.
    size_t slot;
    if (symbol_table_next_slot(tbl, 1, &slot) != 0) {
        *err = WROUTER_ERR_NO_MEMORY;
        return NULL;
    }

    // Allocate space in the arena for the string.
    char *a = arena_alloc(&tbl->arena, len + 1);
    if (a == NULL) {
        *err = WROUTER_ERR_NO_MEMORY;
        return NULL;
    }

    // Copy the string into the arena, ensuring it is null-terminated.
    memcpy(a, str, len);
    a[len] = '\0';

    // Append the string pointer.
    tbl->base[slot] = a;

    return a;
}

void symbol_table_free(symbol_table_t *tbl)
{
    if (tbl == NULL)
        return;

    free(tbl->base);
    tbl->base = NULL;
    tbl->count = 0;
    tbl->capacity = 0;

    arena_free(&tbl->arena);
}

int symbol_ncompare(const void *a, const void *b)
{
    const struct symbol_key *nkey = (const struct symbol_key *)a;
    const char *sym = *(const char **)b;
    const char *key = nkey->ptr;

    for (size_t i = 0;; i++, key++, sym++) {
        if (!(i < nkey->length)) {
            if (!*sym)
                return 0;
            return -(unsigned char)*sym;
        }

        if (*key != *sym)
            goto miss;
    }

    return 0;

miss:
    return (unsigned char)*key - (unsigned char)*sym;
}

int symbol_compare(const void *a, const void *b)
{
    const char *key = *(const char **)a;
    const char *sym = *(const char **)b;

    for (; *key && *sym; key++, sym++)
        if (*key != *sym)
            goto miss;

    if (!*key && !*sym)
        return 0;

miss:
    return (unsigned char)*key - (unsigned char)*sym;
}

static int strpcmp(const void *p1, const void *p2)
{
    return strcmp(*(const char **)p1, *(const char **)p2);
}

/**
 * Compiles an alphabetically sorted symbol list from a symbol strings table.
 *
 * References its own memory region of character strings.
 *
 * @param tbl Symbol strings table.
 * @param sym Symbol list.
 */
wrouter_error_t symbol_compile(const symbol_table_t *tbl, symbols_t *sym)
{
    sym->count = tbl->count;

    // Check for an empty symbol table, which is valid, and return immediately
    // without any changes. This assumes that the symbol list is initialised
    // to zeros.
    if (!sym->count)
        return WROUTER_OK;

    // Allocate space for the string pointers.
    sym->base = malloc(sizeof(char *) * sym->count);
    if (sym->base == NULL)
        goto no_memory;

    // Allocate space for the strings in a contiguous memory region.
    sym->region = malloc(arena_used(&tbl->arena));
    if (sym->region == NULL) {
        goto no_memory;
    }

    // Copy and sort string pointers by string contents.
    memcpy(sym->base, tbl->base, sizeof(char *) * sym->count);
    qsort(sym->base, sym->count, sizeof(char *), strpcmp);

    // Copy strings from the arena and update the string pointers.
    for (size_t cursor = 0, i = 0; i < sym->count; i++) {
        size_t n = strlen(sym->base[i]) + 1;

        // Copy string.
        memcpy(sym->region + cursor, sym->base[i], n);

        // Update pointer to point to the copied string!
        sym->base[i] = sym->region + cursor;

        cursor += n;
    }

    return WROUTER_OK;

no_memory:
    symbols_free(sym);
    return WROUTER_ERR_NO_MEMORY;
}

size_t symbol_nresolve(const symbols_t *symbols, const char *key, size_t n)
{
    const struct symbol_key k = {
        .ptr = key,
        .length = n,
    };

    size_t nmemb = symbols->count;
    const char **res, **base = symbols->base;

    // Binary search of the sorted symbol array.
    res = bsearch(&k, base, nmemb, sizeof(char *), symbol_ncompare);

    // Zero indicates that a symbol was not found. Therefore, increment the
    // index by one if there was a match; return zero otherwise.
    return res ? res - base + 1 : 0;
}

size_t symbol_resolve(const symbols_t *symbols, const char *key)
{
    const char *k = key;
    size_t nmemb = symbols->count;
    const char **res, **base = symbols->base;

    // Binary search of the sorted symbol array.
    res = bsearch(&k, base, nmemb, sizeof(char *), symbol_compare);

    // Zero indicates that a symbol was not found. Therefore, increment the
    // index by one if there was a match; return zero otherwise.
    return res ? res - base + 1 : 0;
}

const char *symbol_lookup(const symbols_t *symbols, uint16_t symbol)
{
    if (symbol == 0)
        return NULL;

    return symbols->base[symbol - 1];
}

void symbols_free(symbols_t *symbols)
{
    if (symbols == NULL)
        return;

    free(symbols->region);
    free(symbols->base);
}