/** * @file symtab.c * @brief Implements the fixed-capacity open-addressed symbol table. */ #include #include #include #include /* * 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 (libakstdlib's UPGRADING.md) -- 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; size_t keylen = 0; int index = 0; int cmp = 0; int i = 0; PASS(errctx, aksl_strlen(key, &keylen)); PASS(errctx, aksl_strhash_djb2((char *)key, keylen, &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); } PASS(errctx, aksl_strcmp(obj->slots[probeidx].key, key, &cmp)); if ( cmp == 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); PASS(errctx, aksl_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); size_t keylen = 0; 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"); PASS(errctx, aksl_strlen(key, &keylen)); FAIL_ZERO_RETURN(errctx, (keylen < 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 ) { /* aksl_strcpy always terminates, so the explicit terminator this replaced is no longer needed. The length check above has already refused anything that would not fit, so it cannot fail here. */ PASS(errctx, aksl_strcpy(obj->slots[slot].key, sizeof(obj->slots[slot].key), key)); 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); size_t keylen = 0; 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"); PASS(errctx, aksl_strlen(key, &keylen)); FAIL_ZERO_RETURN(errctx, (keylen < 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; PASS(errctx, aksl_memset(obj, 0, sizeof(*obj))); obj->capacity = capacity; SUCCEED_RETURN(errctx); }