315 lines
9.3 KiB
C
315 lines
9.3 KiB
C
// The MIT License (MIT)
|
|
//
|
|
// Copyright (C) 2026 Scott Duensing
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to
|
|
// deal in the Software without restriction, including without limitation the
|
|
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
// sell copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
// IN THE SOFTWARE.
|
|
|
|
// symtab.c -- DVX BASIC symbol table implementation
|
|
|
|
#include "symtab.h"
|
|
#include "thirdparty/stb_ds_wrap.h"
|
|
|
|
#include <ctype.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
// Distance from a lowercase ASCII letter to its uppercase counterpart
|
|
// ('a' - 'A'). Used by basAsciiUpper to fold case without touching
|
|
// bytes outside a-z.
|
|
#define BAS_ASCII_CASE_OFFSET 32
|
|
|
|
|
|
// Function prototypes (alphabetical)
|
|
BasSymbolT *basSymTabAdd(BasSymTabT *tab, const char *name, BasSymKindE kind, uint8_t dataType);
|
|
int32_t basSymTabAllocGlobalSlot(BasSymTabT *tab);
|
|
int32_t basSymTabAllocSlot(BasSymTabT *tab);
|
|
void basSymTabEnterFormScope(BasSymTabT *tab, const char *formName);
|
|
void basSymTabEnterLocal(BasSymTabT *tab);
|
|
BasSymbolT *basSymTabFind(BasSymTabT *tab, const char *name);
|
|
BasSymbolT *basSymTabFindGlobal(BasSymTabT *tab, const char *name);
|
|
void basSymTabFree(BasSymTabT *tab);
|
|
void basSymTabInit(BasSymTabT *tab);
|
|
int32_t basSymTabLeaveFormScope(BasSymTabT *tab);
|
|
void basSymTabLeaveLocal(BasSymTabT *tab);
|
|
static char basAsciiUpper(char c);
|
|
static void basSymbolFree(BasSymbolT *sym);
|
|
static bool namesEqual(const char *a, const char *b);
|
|
static uint32_t nameHashCI(const char *name);
|
|
|
|
BasSymbolT *basSymTabAdd(BasSymTabT *tab, const char *name, BasSymKindE kind, uint8_t dataType) {
|
|
// Determine scope: local > form > global.
|
|
// Only variables get SCOPE_FORM; SUBs/FUNCTIONs/CONSTs remain global.
|
|
BasScopeE scope;
|
|
|
|
if (tab->inLocalScope) {
|
|
scope = SCOPE_LOCAL;
|
|
} else if (tab->inFormScope && kind == SYM_VARIABLE) {
|
|
scope = SCOPE_FORM;
|
|
} else {
|
|
scope = SCOPE_GLOBAL;
|
|
}
|
|
|
|
uint32_t h = nameHashCI(name);
|
|
|
|
// Check for duplicate in current scope (skip ended form symbols)
|
|
for (int32_t i = 0; i < tab->count; i++) {
|
|
if (tab->symbols[i]->formScopeEnded) {
|
|
continue;
|
|
}
|
|
|
|
if (tab->symbols[i]->nameHash == h && tab->symbols[i]->scope == scope && namesEqual(tab->symbols[i]->name, name)) {
|
|
return NULL; // duplicate
|
|
}
|
|
}
|
|
|
|
BasSymbolT *sym = (BasSymbolT *)calloc(1, sizeof(BasSymbolT));
|
|
|
|
if (!sym) {
|
|
return NULL;
|
|
}
|
|
|
|
strncpy(sym->name, name, BAS_MAX_SYMBOL_NAME - 1);
|
|
sym->name[BAS_MAX_SYMBOL_NAME - 1] = '\0';
|
|
sym->nameHash = h;
|
|
sym->kind = kind;
|
|
sym->scope = scope;
|
|
sym->dataType = dataType;
|
|
sym->isDefined = true;
|
|
|
|
// Record owning form for both SCOPE_FORM vars AND SUBs/FUNCTIONs
|
|
// declared inside BEGINFORM...ENDFORM. SUBs stay at SCOPE_GLOBAL
|
|
// (callable from anywhere) but carry the owning form so the VM can
|
|
// bind form-scope vars correctly when the SUB is dispatched as an
|
|
// event handler for a different form's control.
|
|
if (tab->inFormScope && tab->formScopeName[0] &&
|
|
(scope == SCOPE_FORM || kind == SYM_SUB || kind == SYM_FUNCTION)) {
|
|
strncpy(sym->formName, tab->formScopeName, BAS_MAX_SYMBOL_NAME - 1);
|
|
sym->formName[BAS_MAX_SYMBOL_NAME - 1] = '\0';
|
|
}
|
|
|
|
arrput(tab->symbols, sym);
|
|
tab->count = (int32_t)arrlen(tab->symbols);
|
|
return sym;
|
|
}
|
|
|
|
|
|
int32_t basSymTabAllocGlobalSlot(BasSymTabT *tab) {
|
|
return tab->nextGlobalIdx++;
|
|
}
|
|
|
|
|
|
int32_t basSymTabAllocSlot(BasSymTabT *tab) {
|
|
if (tab->inLocalScope) {
|
|
return tab->nextLocalIdx++;
|
|
}
|
|
|
|
if (tab->inFormScope) {
|
|
return tab->nextFormVarIdx++;
|
|
}
|
|
|
|
return tab->nextGlobalIdx++;
|
|
}
|
|
|
|
|
|
void basSymTabEnterFormScope(BasSymTabT *tab, const char *formName) {
|
|
tab->inFormScope = true;
|
|
strncpy(tab->formScopeName, formName, BAS_MAX_SYMBOL_NAME - 1);
|
|
tab->formScopeName[BAS_MAX_SYMBOL_NAME - 1] = '\0';
|
|
tab->nextFormVarIdx = 0;
|
|
tab->formScopeSymStart = tab->count;
|
|
}
|
|
|
|
|
|
void basSymTabEnterLocal(BasSymTabT *tab) {
|
|
tab->inLocalScope = true;
|
|
tab->nextLocalIdx = 0;
|
|
}
|
|
|
|
|
|
BasSymbolT *basSymTabFind(BasSymTabT *tab, const char *name) {
|
|
uint32_t h = nameHashCI(name);
|
|
|
|
// Search local scope first
|
|
if (tab->inLocalScope) {
|
|
for (int32_t i = tab->count - 1; i >= 0; i--) {
|
|
BasSymbolT *s = tab->symbols[i];
|
|
if (s->nameHash == h && s->scope == SCOPE_LOCAL && namesEqual(s->name, name)) {
|
|
return s;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Search form scope and global scope
|
|
for (int32_t i = tab->count - 1; i >= 0; i--) {
|
|
BasSymbolT *s = tab->symbols[i];
|
|
if (s->formScopeEnded) {
|
|
continue;
|
|
}
|
|
|
|
if (s->nameHash == h && (s->scope == SCOPE_FORM || s->scope == SCOPE_GLOBAL) &&
|
|
namesEqual(s->name, name)) {
|
|
return s;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
BasSymbolT *basSymTabFindGlobal(BasSymTabT *tab, const char *name) {
|
|
uint32_t h = nameHashCI(name);
|
|
|
|
for (int32_t i = 0; i < tab->count; i++) {
|
|
BasSymbolT *s = tab->symbols[i];
|
|
if (s->formScopeEnded) {
|
|
continue;
|
|
}
|
|
|
|
if (s->nameHash == h && s->scope == SCOPE_GLOBAL && namesEqual(s->name, name)) {
|
|
return s;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
void basSymTabFree(BasSymTabT *tab) {
|
|
// Free every remaining symbol's dynamic arrays and the struct, then
|
|
// release the symbol-pointer array itself.
|
|
for (int32_t i = 0; i < tab->count; i++) {
|
|
basSymbolFree(tab->symbols[i]);
|
|
}
|
|
|
|
arrfree(tab->symbols);
|
|
tab->symbols = NULL;
|
|
tab->count = 0;
|
|
}
|
|
|
|
|
|
void basSymTabInit(BasSymTabT *tab) {
|
|
memset(tab, 0, sizeof(*tab));
|
|
}
|
|
|
|
|
|
int32_t basSymTabLeaveFormScope(BasSymTabT *tab) {
|
|
int32_t varCount = tab->nextFormVarIdx;
|
|
|
|
// Mark all form-scope symbols added since BEGINFORM as ended
|
|
for (int32_t i = tab->formScopeSymStart; i < tab->count; i++) {
|
|
if (tab->symbols[i]->scope == SCOPE_FORM) {
|
|
tab->symbols[i]->formScopeEnded = true;
|
|
}
|
|
}
|
|
|
|
tab->inFormScope = false;
|
|
tab->formScopeName[0] = '\0';
|
|
tab->nextFormVarIdx = 0;
|
|
tab->formScopeSymStart = 0;
|
|
|
|
return varCount;
|
|
}
|
|
|
|
|
|
void basSymTabLeaveLocal(BasSymTabT *tab) {
|
|
// Remove all local symbols, freeing their dynamic arrays and the
|
|
// symbol struct itself.
|
|
int32_t newCount = 0;
|
|
|
|
for (int32_t i = 0; i < tab->count; i++) {
|
|
if (tab->symbols[i]->scope == SCOPE_LOCAL) {
|
|
basSymbolFree(tab->symbols[i]);
|
|
} else {
|
|
if (i != newCount) {
|
|
tab->symbols[newCount] = tab->symbols[i];
|
|
}
|
|
|
|
newCount++;
|
|
}
|
|
}
|
|
|
|
arrsetlen(tab->symbols, newCount);
|
|
tab->count = newCount;
|
|
tab->inLocalScope = false;
|
|
tab->nextLocalIdx = 0;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// Fold a single ASCII byte to upper case. Char in, char out (not int
|
|
// via toupper) so bytes >= 0x80 pass through unchanged and the uint8_t
|
|
// then uint32_t cast in nameHashCI stays well defined.
|
|
// ============================================================
|
|
static char basAsciiUpper(char c) {
|
|
if (c >= 'a' && c <= 'z') {
|
|
c -= BAS_ASCII_CASE_OFFSET;
|
|
}
|
|
|
|
return c;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// Free a single symbol: its dynamic arrays and the struct itself.
|
|
// ============================================================
|
|
static void basSymbolFree(BasSymbolT *sym) {
|
|
arrfree(sym->patchAddrs);
|
|
arrfree(sym->fields);
|
|
free(sym);
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// Case-insensitive FNV-1a hash used to accelerate symbol-table lookups.
|
|
// Caller computes hash of search-name once; each entry stores its own
|
|
// precomputed hash, so per-entry comparison is a fast uint32 compare
|
|
// that nearly always rejects non-matches without calling namesEqual.
|
|
// ============================================================
|
|
static uint32_t nameHashCI(const char *name) {
|
|
uint32_t h = 0x811C9DC5u;
|
|
|
|
while (*name) {
|
|
char c = basAsciiUpper(*name);
|
|
h ^= (uint32_t)(uint8_t)c;
|
|
h *= 0x01000193u;
|
|
name++;
|
|
}
|
|
|
|
return h;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// Case-insensitive name comparison
|
|
// ============================================================
|
|
static bool namesEqual(const char *a, const char *b) {
|
|
while (*a && *b) {
|
|
char ca = basAsciiUpper(*a);
|
|
char cb = basAsciiUpper(*b);
|
|
|
|
if (ca != cb) {
|
|
return false;
|
|
}
|
|
|
|
a++;
|
|
b++;
|
|
}
|
|
|
|
return *a == *b;
|
|
}
|