// 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.h -- DVX BASIC symbol table // // Tracks variables, constants, subroutines, functions, and labels // during compilation. Supports nested scopes (global + one local // scope per SUB/FUNCTION). // // Embeddable: no DVX dependencies, pure C. #ifndef DVXBASIC_SYMTAB_H #define DVXBASIC_SYMTAB_H #include "../compiler/opcodes.h" #include #include // ============================================================ // Symbol kinds // ============================================================ typedef enum { SYM_VARIABLE, SYM_CONST, SYM_SUB, SYM_FUNCTION, SYM_LABEL, SYM_TYPE_DEF // user-defined TYPE } BasSymKindE; // BasScopeE moved to opcodes.h (shared with runtime). // ============================================================ // Symbol entry // ============================================================ #define BAS_MAX_SYMBOL_NAME 64 #define BAS_MAX_PARAMS 16 #define BAS_MAX_CONST_STR 256 // max CONST string-literal length // UDT field definition typedef struct { char name[BAS_MAX_SYMBOL_NAME]; uint8_t dataType; // BAS_TYPE_* int32_t udtTypeId; // if dataType == BAS_TYPE_UDT, index of the TYPE_DEF symbol } BasFieldDefT; typedef struct { // Fields are ordered by alignment (4-byte, then 2-byte, then 1-byte) to // minimize struct padding on the 32-bit target. uint32_t nameHash; // FNV-1a over uppercase(name); fast-reject during lookup BasSymKindE kind; BasScopeE scope; int32_t index; // slot index (local or global) int32_t codeAddr; // PC address for SUB/FUNCTION/LABEL int32_t localCount; // number of local variables (for SUB/FUNCTION, set on leave) int32_t udtTypeId; // for variables of BAS_TYPE_UDT: index of TYPE_DEF symbol int32_t fixedLen; // for STRING * n: fixed length (0 = variable-length) // For SUB/FUNCTION: parameter info int32_t paramCount; int32_t requiredParams; // count of non-optional params // Forward-reference backpatch list (code addresses to patch when defined) int32_t *patchAddrs; // stb_ds dynamic array int32_t patchCount; // For CONST: the constant value union { int32_t constInt; double constDbl; }; // For TYPE_DEF: field definitions BasFieldDefT *fields; // stb_ds dynamic array int32_t fieldCount; uint16_t externLibIdx; // constant pool index for library name (if isExtern) uint16_t externFuncIdx; // constant pool index for function name (if isExtern) uint8_t dataType; // BAS_TYPE_* for variables/functions bool isDefined; // false = forward-declared bool isArray; bool isShared; bool isExtern; // true = external library function (DECLARE LIBRARY) bool formScopeEnded; // true = form scope ended, invisible to lookups char name[BAS_MAX_SYMBOL_NAME]; char formName[BAS_MAX_SYMBOL_NAME]; // form name for SCOPE_FORM vars uint8_t paramTypes[BAS_MAX_PARAMS]; bool paramByVal[BAS_MAX_PARAMS]; bool paramOptional[BAS_MAX_PARAMS]; // true = OPTIONAL parameter char constStr[BAS_MAX_CONST_STR]; } BasSymbolT; // ============================================================ // Symbol table // ============================================================ typedef struct { // Array of POINTERS to heap-allocated symbols (stb_ds array of pointers). // Pointers-of-pointers rather than array-of-structs because callers // routinely hold a BasSymbolT * across parsing operations that may // trigger basSymTabAdd (which grows this array). An array of structs // would be reallocated on growth, invalidating any held pointer. // Indirection via a stable heap pointer per symbol avoids that. BasSymbolT **symbols; int32_t count; int32_t nextGlobalIdx; // next global variable slot int32_t nextLocalIdx; // next local variable slot (reset per SUB/FUNCTION) bool inLocalScope; // true when inside SUB/FUNCTION bool inFormScope; // true inside BEGINFORM...ENDFORM char formScopeName[BAS_MAX_SYMBOL_NAME]; // current form name int32_t nextFormVarIdx; // next form-level variable slot int32_t formScopeSymStart; // symbol count at BEGINFORM (for marking ended) } BasSymTabT; // ============================================================ // API // ============================================================ void basSymTabInit(BasSymTabT *tab); // Free all symbols and the symbol array (called at parser teardown). void basSymTabFree(BasSymTabT *tab); // Add a symbol. Returns the symbol pointer, or NULL if the name already // exists in the current scope or memory allocation fails. BasSymbolT *basSymTabAdd(BasSymTabT *tab, const char *name, BasSymKindE kind, uint8_t dataType); // Look up a symbol by name. Searches local scope first, then global. // Case-insensitive. BasSymbolT *basSymTabFind(BasSymTabT *tab, const char *name); // Look up a symbol in the global scope only. BasSymbolT *basSymTabFindGlobal(BasSymTabT *tab, const char *name); // Enter local scope (called at SUB/FUNCTION start). void basSymTabEnterLocal(BasSymTabT *tab); // Leave local scope (called at END SUB/FUNCTION). Removes local symbols. void basSymTabLeaveLocal(BasSymTabT *tab); // Allocate the next variable slot (global, local, or form depending on scope). int32_t basSymTabAllocSlot(BasSymTabT *tab); // Allocate the next global variable slot unconditionally, ignoring the // current local/form scope flags. Used when a symbol's final scope is // SCOPE_GLOBAL but the table is currently inside a SUB/FUNCTION or form. int32_t basSymTabAllocGlobalSlot(BasSymTabT *tab); // Enter form scope (called at BEGINFORM). Form-level DIMs create SCOPE_FORM variables. void basSymTabEnterFormScope(BasSymTabT *tab, const char *formName); // Leave form scope (called at ENDFORM). Marks form-scope symbols as ended. // Returns the number of form variables allocated. int32_t basSymTabLeaveFormScope(BasSymTabT *tab); #endif // DVXBASIC_SYMTAB_H