134 lines
4.0 KiB
C
134 lines
4.0 KiB
C
|
|
/**
|
||
|
|
* @file symtab.c
|
||
|
|
* @brief Implements the fixed-capacity open-addressed symbol table.
|
||
|
|
*/
|
||
|
|
|
||
|
|
#include <string.h>
|
||
|
|
|
||
|
|
#include <akerror.h>
|
||
|
|
#include <akstdlib.h>
|
||
|
|
|
||
|
|
#include <akbasic/error.h>
|
||
|
|
#include <akbasic/symtab.h>
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Probe for `key`. On success *slot points at either the slot holding the key or
|
||
|
|
* the first free slot it could occupy; *found says which. Uses
|
||
|
|
* aksl_strhash_djb2 rather than a private hash. That wrapper sign-extends char,
|
||
|
|
* so a high-bit byte hashes wrong (deps/libakstdlib/TODO.md 1.6) -- harmless
|
||
|
|
* here because BASIC identifiers are 7-bit ASCII, and it would only ever cost
|
||
|
|
* probe efficiency, never correctness, since the key comparison is a strcmp.
|
||
|
|
*/
|
||
|
|
static akerr_ErrorContext *probe(akbasic_SymbolTable *obj, const char *key, int *slot, bool *found)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
uint32_t hashval = 0;
|
||
|
|
int index = 0;
|
||
|
|
int i = 0;
|
||
|
|
|
||
|
|
PASS(errctx, aksl_strhash_djb2((char *)key, strlen(key), &hashval));
|
||
|
|
|
||
|
|
*found = false;
|
||
|
|
index = (int)(hashval % (uint32_t)obj->capacity);
|
||
|
|
for ( i = 0; i < obj->capacity; i++ ) {
|
||
|
|
int probeidx = (index + i) % obj->capacity;
|
||
|
|
if ( !obj->slots[probeidx].used ) {
|
||
|
|
*slot = probeidx;
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
if ( strcmp(obj->slots[probeidx].key, key) == 0 ) {
|
||
|
|
*slot = probeidx;
|
||
|
|
*found = true;
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
FAIL_RETURN(errctx, AKBASIC_ERR_BOUNDS,
|
||
|
|
"Symbol table is full (%d entries), cannot place '%s'",
|
||
|
|
obj->capacity, key);
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext *akbasic_symtab_init(akbasic_SymbolTable *obj, int capacity)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
|
||
|
|
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER,
|
||
|
|
"NULL symbol table in init");
|
||
|
|
FAIL_ZERO_RETURN(errctx, (capacity > 0 && capacity <= AKBASIC_SYMTAB_MAX_SLOTS),
|
||
|
|
AKBASIC_ERR_BOUNDS,
|
||
|
|
"Symbol table capacity %d out of range 1..%d",
|
||
|
|
capacity, AKBASIC_SYMTAB_MAX_SLOTS);
|
||
|
|
|
||
|
|
memset(obj, 0, sizeof(*obj));
|
||
|
|
obj->capacity = capacity;
|
||
|
|
obj->count = 0;
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext *akbasic_symtab_set(akbasic_SymbolTable *obj, const char *key, void *value, int64_t ivalue)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
int slot = 0;
|
||
|
|
bool found = false;
|
||
|
|
|
||
|
|
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER,
|
||
|
|
"NULL symbol table in set");
|
||
|
|
FAIL_ZERO_RETURN(errctx, (key != NULL), AKERR_NULLPOINTER,
|
||
|
|
"NULL key in symbol table set");
|
||
|
|
FAIL_ZERO_RETURN(errctx, (strlen(key) < AKBASIC_SYMTAB_MAX_KEY),
|
||
|
|
AKBASIC_ERR_BOUNDS,
|
||
|
|
"Symbol name '%s' exceeds %d characters",
|
||
|
|
key, AKBASIC_SYMTAB_MAX_KEY - 1);
|
||
|
|
|
||
|
|
PASS(errctx, probe(obj, key, &slot, &found));
|
||
|
|
|
||
|
|
if ( !found ) {
|
||
|
|
strncpy(obj->slots[slot].key, key, AKBASIC_SYMTAB_MAX_KEY - 1);
|
||
|
|
obj->slots[slot].key[AKBASIC_SYMTAB_MAX_KEY - 1] = '\0';
|
||
|
|
obj->slots[slot].used = true;
|
||
|
|
obj->count += 1;
|
||
|
|
}
|
||
|
|
obj->slots[slot].value = value;
|
||
|
|
obj->slots[slot].ivalue = ivalue;
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext *akbasic_symtab_get(akbasic_SymbolTable *obj, const char *key, void **value, int64_t *ivalue)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
int slot = 0;
|
||
|
|
bool found = false;
|
||
|
|
|
||
|
|
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER,
|
||
|
|
"NULL symbol table in get");
|
||
|
|
FAIL_ZERO_RETURN(errctx, (key != NULL), AKERR_NULLPOINTER,
|
||
|
|
"NULL key in symbol table get");
|
||
|
|
FAIL_ZERO_RETURN(errctx, (strlen(key) < AKBASIC_SYMTAB_MAX_KEY),
|
||
|
|
AKERR_KEY,
|
||
|
|
"Symbol '%s' is not present", key);
|
||
|
|
|
||
|
|
PASS(errctx, probe(obj, key, &slot, &found));
|
||
|
|
FAIL_ZERO_RETURN(errctx, found, AKERR_KEY, "Symbol '%s' is not present", key);
|
||
|
|
|
||
|
|
if ( value != NULL ) {
|
||
|
|
*value = obj->slots[slot].value;
|
||
|
|
}
|
||
|
|
if ( ivalue != NULL ) {
|
||
|
|
*ivalue = obj->slots[slot].ivalue;
|
||
|
|
}
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext *akbasic_symtab_clear(akbasic_SymbolTable *obj)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
int capacity = 0;
|
||
|
|
|
||
|
|
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER,
|
||
|
|
"NULL symbol table in clear");
|
||
|
|
|
||
|
|
capacity = obj->capacity;
|
||
|
|
memset(obj, 0, sizeof(*obj));
|
||
|
|
obj->capacity = capacity;
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|