scratch/proglangs/nsforth/dict.c
2024-12-26 14:02:40 -03:00

170 lines
4.6 KiB
C

#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include "dict.h"
#include <stdio.h>
#define FREE_DICT_NAMES(env) \
while ((env)->capacity > 0) { \
char *word_name = (env)->entries[--(env)->capacity].word_name; \
if (word_name != NULL && word_name != DELETED_ENTRY_MARK) \
free(word_name); \
}
const size_t DEFAULT_HASHMAP_SIZE = 255;
const size_t HASHMAP_REHASH_MARK = 70;
struct dict_ent *dict_last;
static size_t dict_table_fnv1a(const char *key, const size_t key_len)
{
const size_t fnv_offset = 0xcbf29ce484222325;
const size_t fnv_prime = 0x100000001b3;
size_t hash = fnv_offset;
for (size_t ki = 0; ki < key_len; ++ki) {
hash ^= (uint8_t)key[ki];
hash *= fnv_prime;
}
return hash;
}
static void dict_destroy_enviroment(struct dict_table *env)
{
FREE_DICT_NAMES(env);
free(env->entries);
free(env);
}
struct dict_table *dict_new_env(struct dict_table *prev_env)
{
struct dict_table *new_env = xcalloc(1, sizeof(*new_env));
new_env->entries = xcalloc(DEFAULT_HASHMAP_SIZE, sizeof(struct dict_ent));
new_env->prev = prev_env;
new_env->capacity = DEFAULT_HASHMAP_SIZE;
if (prev_env != NULL)
new_env->level = prev_env->level + 1;
return new_env;
}
void dict_destroy_env(struct dict_table *env)
{
if (env->prev == NULL) {
dict_destroy_enviroment(env);
return;
}
while (env != NULL) {
struct dict_table *tmp = env;
env = env->prev;
dict_destroy_enviroment(tmp);
}
}
struct dict_ent *_dict_insert(
struct dict_table *env, const char *word_name, uint8_t flags, dict_fn nat_fn,
struct comp_word *fn_def
);
// Resizes the bucket size if needed (shrink or expand), also removes deleted entries
void dict_table_rehash(struct dict_table *env)
{
const size_t new_capacity = env->capacity * 2;
struct dict_table new_dict_table = {0};
new_dict_table.entries = xcalloc(new_capacity, sizeof(struct dict_ent));
new_dict_table.capacity = new_capacity;
// copy elements to the new dict_table, removing deleted entries in the process
for (size_t i = 0; i < env->capacity; ++i) {
struct dict_ent *oldent = &env->entries[i];
//if (oldent->word_name != NULL && oldent->word_name != DELETED_ENTRY_MARK) {
if (IS_ENTRY(oldent)) {
dict_insert(&new_dict_table, oldent->word_name, oldent->flags, oldent->nat_fn, oldent->fn_def);
}
}
FREE_DICT_NAMES(env);
free(env->entries); // free old buckets
env->capacity = new_capacity;
env->entries = new_dict_table.entries;
}
struct dict_ent *_dict_insert(
struct dict_table *env, const char *word_name, uint8_t flags, dict_fn nat_fn,
struct comp_word *fn_def
)
{
if ((env->used * 100) / env->capacity >= HASHMAP_REHASH_MARK)
dict_table_rehash(env);
size_t hash_index = dict_table_fnv1a(word_name, strlen(word_name)) % env->capacity;
while (env->entries[hash_index].word_name != NULL) {
++hash_index;
if (hash_index >= env->capacity)
hash_index = 0;
}
if (env->entries[hash_index].word_name != DELETED_ENTRY_MARK)
++env->used;
struct dict_ent *dslot = &env->entries[hash_index];
dslot->word_name = (char *)word_name;
dslot->flags = flags;
dslot->nat_fn = nat_fn;
dslot->fn_def = fn_def;
dict_last = dslot;
return dslot;
}
struct dict_ent *dict_insert(
struct dict_table *env, const char *word_name, uint8_t flags, dict_fn nat_fn,
struct comp_word *fn_def
)
{
return _dict_insert(env, strdup(word_name), flags, nat_fn, fn_def);
}
// like dict_lookup it searches a symbol, but limited to the current environment (scope)
struct dict_ent *dict_lookup(struct dict_table *env, const char *word_name)
{
if (env->entries == NULL)
return NULL;
size_t hash_index = dict_table_fnv1a(word_name, strlen(word_name)) % env->capacity;
while (env->entries[hash_index].word_name != NULL) {
struct dict_ent *ent = &env->entries[hash_index];
if (ent->word_name != DELETED_ENTRY_MARK && !strcmp(ent->word_name, word_name))
return ent;
++hash_index;
if (hash_index >= env->capacity)
hash_index = 0;
}
// entry not found
return NULL;
}
// searches for a symbol in the current table and in all linked environments (scopes)
//struct dict_ent *dict_lookup(struct dict_table *env, char *word_name)
//{
// if (env->entries == NULL)
// return NULL;
// size_t hash_index = dict_table_fnv1a(word_name, strlen(word_name)) % env->capacity;
//
// for (struct dict_table *s = env; s != NULL; s = s->prev) {
// while (s->entries[hash_index].word_name != NULL) {
// if (s->entries[hash_index].word_name != DELETED_ENTRY_MARK && !strcmp(s->entries[hash_index].word_name, word_name))
// return &s->entries[hash_index];
//
// ++hash_index;
// if (hash_index >= s->capacity)
// hash_index = 0;
// }
// }
//
// // entry not found
// return NULL;
//}