// 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 #include #include // 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; }