sources/symtab.c (186 lines of code) (raw):
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "cql.h"
#include "symtab.h"
#include "bytebuf.h"
#include "charbuf.h"
static void symtab_rehash(symtab *syms);
static void set_payload(symtab *syms) {
syms->payload = (symtab_entry *)calloc(syms->capacity, sizeof(symtab_entry));
}
static uint32_t hash_case_insens(const char *sym) {
const unsigned char *bytes = (const unsigned char *)sym;
uint64_t hash = 0;
while (*bytes) {
unsigned char byte = *bytes | 0x20;
hash = ((hash << 5) | (hash >> 27)) ^ byte;
bytes++;
}
return (uint32_t)(hash ^ (hash >>32));
}
static int32_t cmp_case_insens(const char *s1, const char *s2) {
return (int32_t)Strcasecmp(s1, s2);
}
static int32_t cmp_case_sens(const char *s1, const char *s2) {
return (int32_t)strcmp(s1, s2);
}
static uint32_t hash_case_sens(const char *sym) {
const unsigned char *bytes = (const unsigned char *)sym;
uint64_t hash = 0;
while (*bytes) {
unsigned char byte = *bytes;
hash = ((hash << 5) | (hash >> 27)) ^ byte;
bytes++;
}
return (uint32_t)(hash ^ (hash >>32));
}
cql_noexport symtab *symtab_new() {
symtab *syms = _new(symtab);
syms->count = 0;
syms->capacity = SYMTAB_INIT_SIZE;
syms->hash = hash_case_insens;
syms->cmp = cmp_case_insens;
syms->teardown = NULL;
set_payload(syms);
return syms;
}
cql_noexport symtab *symtab_new_case_sens() {
symtab *syms = symtab_new();
syms->hash = hash_case_sens;
syms->cmp = cmp_case_sens;
return syms;
}
cql_noexport void symtab_delete(symtab *syms) {
if (syms->teardown) {
for (int32_t i = 0; i < syms->capacity; i++) {
void *val = syms->payload[i].val;
if (val) {
syms->teardown(val);
}
}
}
free(syms->payload);
free(syms);
}
cql_noexport bool_t symtab_add(symtab *syms, const char *sym_new, void *val_new) {
uint32_t hash = syms->hash(sym_new);
uint32_t offset = hash % syms->capacity;
symtab_entry *payload = syms->payload;
for (;;) {
const char *sym = payload[offset].sym;
if (!sym) {
payload[offset].sym = sym_new;
payload[offset].val = val_new;
syms->count++;
if (syms->count > syms->capacity * SYMTAB_LOAD_FACTOR) {
symtab_rehash(syms);
}
return true;
}
if (!syms->cmp(sym, sym_new)) {
return false;
}
offset++;
if (offset >= syms->capacity) {
offset = 0;
}
}
}
cql_noexport symtab_entry *symtab_find(symtab *syms, const char *sym_needed) {
if (!syms) {
return NULL;
}
uint32_t hash = syms->hash(sym_needed);
uint32_t offset = hash % syms->capacity;
symtab_entry *payload = syms->payload;
for (;;) {
const char *sym = syms->payload[offset].sym;
if (!sym) {
return NULL;
}
if (!syms->cmp(sym, sym_needed)) {
return &payload[offset];
}
offset++;
if (offset >= syms->capacity) {
offset = 0;
}
}
}
static void symtab_rehash(symtab *syms) {
uint32_t old_capacity = syms->capacity;
symtab_entry *old_payload = syms->payload;
syms->count = 0;
syms->capacity *= 2;
set_payload(syms);
for (uint32_t i = 0; i < old_capacity; i++) {
const char *sym = old_payload[i].sym;
if (!sym) {
continue;
}
symtab_add(syms, old_payload[i].sym, old_payload[i].val);
}
free(old_payload);
}
// patternlint-disable-next-line prefer-sized-ints-in-msys
cql_noexport int default_symtab_comparator(symtab_entry *entry1, symtab_entry *entry2) {
return strcmp(entry1->sym, entry2->sym);
}
// patternlint-disable-next-line prefer-sized-ints-in-msys
cql_noexport symtab_entry *symtab_copy_sorted_payload(symtab *syms, int (*comparator)(symtab_entry *entry1, symtab_entry *entry2)) {
uint32_t count = syms->count;
size_t size = sizeof(symtab_entry);
symtab_entry *sorted = calloc(count, size);
int32_t found = 0;
for (int32_t i = 0; i < syms->capacity; i++) {
// skip the null syms in our copy
if (syms->payload[i].sym) {
sorted[found++] = syms->payload[i];
}
}
// now sort the nonnull values
// patternlint-disable-next-line prefer-sized-ints-in-msys
qsort(sorted, count, size, (int (*)(const void *, const void *))comparator);
return sorted;
}
// adding special case code for two common cases
// * a symbol table with payload of symbol tables
// * a symbol table with payload of bytebuffers
static void symtab_teardown(void *val) {
symtab_delete(val);
}
cql_noexport symtab *_Nonnull symtab_ensure_symtab(symtab *syms, const char *name) {
syms->teardown = symtab_teardown;
symtab_entry *entry = symtab_find(syms, name);
symtab *value = entry ? (symtab *)entry->val : NULL;
if (entry == NULL) {
value = symtab_new();
symtab_add(syms, name, value);
}
return value;
}
static void bytebuf_teardown(void *val) {
bytebuf_close((bytebuf*)val);
free(val);
}
cql_noexport bytebuf *_Nonnull symtab_ensure_bytebuf(symtab *syms, const char *sym_new) {
syms->teardown = bytebuf_teardown;
symtab_entry *entry = symtab_find(syms, sym_new);
bytebuf *buf = entry ? (bytebuf *)entry->val : NULL;
if (entry == NULL) {
buf = _new(bytebuf);
bytebuf_open(buf);
symtab_add(syms, sym_new, buf);
}
return buf;
}
cql_noexport void symtab_append_bytes(symtab *syms, const char *sym_new, const void *bytes, size_t count) {
bytebuf *buf = symtab_ensure_bytebuf(syms, sym_new);
bytebuf_append(buf, bytes, (uint32_t)count);
}
static void charbuf_teardown(void *val) {
bclose((charbuf*)val);
free(val);
}
cql_noexport charbuf *_Nonnull symtab_ensure_charbuf(symtab *syms, const char *sym_new) {
syms->teardown = charbuf_teardown;
symtab_entry *entry = symtab_find(syms, sym_new);
charbuf *output = entry ? (charbuf *)entry->val : NULL;
if (!output) {
// None found, create one
output = _new(charbuf);
bopen(output);
// This buffer doesn't participate in the normal stack of charbufs
// it will be freed when the symbol table is torn down
charbuf_open_count--;
symtab_add(syms, sym_new, output);
}
return output;
}