// 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. // scope.h -- DVX BASIC compiler symbols and scopes // // A scope is a hash table of symbols plus their creation order. The // compiler keeps one module scope (globals, procedures, constants, TYPEs, // ENUMs, class modules and their members as "Class.name", the predefined // constants), one form scope while a form's code is compiled (its form // variables), one procedure scope while a procedure is compiled (its // parameters and locals), and label tables for the module and for the // procedure. Symbols are allocated from an arena: module symbols live // as long as the compilation, procedure symbols as long as the // procedure's unit. // // Embeddable: no DVX dependencies, pure C. #ifndef DVXBASIC_SCOPE_H #define DVXBASIC_SCOPE_H #include "ast.h" #include "codegen.h" #include "opcodes.h" #include "../runtime/vm.h" #include #include // Most parameters a SUB / FUNCTION declares. #define BAS_MAX_PARAMS 16 // Longest string a CONST may hold. #define BAS_MAX_CONST_STR 256 // Hash buckets per scope (powers of two). #define BAS_MODULE_BUCKETS 256 #define BAS_LOCAL_BUCKETS 64 // objClass of an object declared As Collection (a class module has its // number there; BAS_CLASS_NONE (vm.h): no class, e.g. As Object). #define BAS_CLASS_COLLECTION (-2) // Qualified names of class module symbols: "Class.Member", and the // Property Let / Set procedures "Class.Member.Let" / "Class.Member.Set" // (Property Get takes the plain qualified name). #define BAS_CLASS_SEP BAS_MEMBER_QUALIFIER #define BAS_PROP_LET_SUFFIX ".Let" #define BAS_PROP_SET_SUFFIX ".Set" typedef enum { SK_VAR, SK_CONST, SK_SUB, SK_FUNCTION, SK_LABEL, SK_TYPE, SK_ENUM, SK_CLASS, SK_CONTROL // a control of the form being compiled (its BEGINFORM list) } BasSymKindE; // BasSymT.flags #define SF_DEFINED 0x00000001u // procedure body / label / ENUM / class seen (else forward or declared only) #define SF_ARRAY 0x00000002u #define SF_DYNAMIC 0x00000004u // array declared without bounds: ERASE frees it #define SF_SHARED 0x00000008u // module variable visible inside procedures #define SF_TYPED 0x00000010u // declared with a type (AS, suffix or DEFxxx): stores convert to it #define SF_IMPLICIT 0x00000020u // created by first use (a STRING local needs its "" at entry) #define SF_ALIAS 0x00000040u // procedure name for a module slot (STATIC, SHARED): aliasScope / index #define SF_EXTERN 0x00000080u // DECLARE LIBRARY function #define SF_HASSIG 0x00000100u // signature known (prescan, DECLARE or definition) #define SF_PRIVATE 0x00000200u // class member declared Private #define SF_PROPERTY 0x00000400u // Property Get / Let / Set #define SF_PARAMARRAY 0x00000800u // the last parameter is a ParamArray #define SF_PARAMARRAYTYPED 0x00001000u // ... with a declared element type #define SF_ENUMMEMBER 0x00002000u // CONST that is the bare name of an ENUM member #define SF_AMBIGUOUS 0x00004000u // ENUM member name of two ENUMs: only usable qualified #define SF_HIDDEN 0x00008000u // compiler variable (FOR EACH): no debug entry #define SF_PARAM 0x00010000u // a procedure parameter #define SF_DEFFN 0x00020000u // a DEF FN function #define SF_PREDECLARED 0x00040000u // class member variable the declaration pass made: its DIM is not compiled yet #define SF_STATIC 0x00080000u // hidden member variable holding a class procedure's STATIC variable (one per object) #define SF_FRIEND 0x00100000u // class procedure declared Friend: bound by the compiler, not found by late binding #define SF_BYREF 0x00200000u // a parameter passed by reference: it may name a Variant variable, so its type is not known statically // BasProcInfoT.paramFlags #define PARAM_BYVAL 0x01 #define PARAM_OPTIONAL 0x02 #define PARAM_DEFAULT 0x04 // Optional with "= default": an omitted argument is Missing, the procedure stores the default #define PARAM_TYPED 0x08 // a scalar declared with a type (AS, suffix or DEFxxx): arguments are converted to it // A folded constant: a CONST's value, an ENUM member, an Optional // default. Integer results are LONG. typedef struct { double dblVal; // every numeric type and DATE int64_t curVal; // CURRENCY: the exact amount times BAS_CURRENCY_SCALE int32_t intVal; // INTEGER / LONG / BOOLEAN / BYTE int32_t strLen; // STRING (may hold NULs) const char *str; // STRING text (arena) uint8_t dataType; // BAS_TYPE_* (BAS_TYPE_OBJECT: Nothing) } BasConstValT; // BasConstValT.dataType of an Optional default not evaluated yet (the // declaration pass takes only literals; the definition evaluates every // one). No folded constant is a Variant. #define BAS_DEFAULT_PENDING BAS_TYPE_VARIANT // A SUB / FUNCTION's signature and code. typedef struct { int32_t codeAddr; int32_t *patches; // stb_ds: OP_CALL operands waiting for codeAddr bool hasCode; // codeAddr is known (the body was generated) int32_t localCount; int32_t refLine; // first call made before the definition (Undefined Sub errors) int16_t paramCount; int16_t requiredParams; // up to the last non-Optional parameter uint16_t externLibIdx; // DECLARE LIBRARY: constant pool indices uint16_t externFuncIdx; uint8_t paramTypes[BAS_MAX_PARAMS]; uint8_t paramFlags[BAS_MAX_PARAMS]; // PARAM_* BasConstValT *defaults; // arena: per parameter (only PARAM_DEFAULT ones used, BAS_DEFAULT_PENDING until evaluated); NULL when none int16_t *paramClasses; // arena: per parameter, an object parameter's class (BAS_CLASS_COLLECTION; BAS_CLASS_NONE: any object); NULL when no parameter is declared As a class or Collection const char **paramNames; // module arena: per parameter, its name (arguments given by name: Name:=value) BasSlotInitT *localInits; // module arena: the locals that start as their type's zero (the result, typed scalars), set when it is entered int32_t localInitCount; } BasProcInfoT; // A label's code position and the jumps waiting for it. typedef struct { int32_t codeAddr; int32_t dataIndex; // the DATA items before it (RESTORE label), once hasCode int32_t *dataPatches; // stb_ds: RESTORE operands waiting for dataIndex int32_t *patches; // stb_ds: jump operands waiting for codeAddr int32_t *patchDepths; // stb_ds, parallel to patches: SELECT depth at a GOTO whose pops wait for the label (NOP placeholders); -1 for other jumps int32_t selectDepth; // SELECT CASE blocks open at the label int32_t refLine; // first reference (Label not defined errors) bool hasCode; // codeAddr is known } BasLabelInfoT; // What a STATIC array or TYPE is allocated as, once (a procedure's // variable: at its first call; a class procedure's: with each object): // an array of dims dimensions with constant bounds, or for dims 0 a TYPE // instance. A STATIC array declared without bounds is REDIM'd by the // procedure and has none (u.alloc NULL). typedef struct { int32_t dims; int32_t lower[BAS_ARRAY_MAX_DIMS]; int32_t upper[BAS_ARRAY_MAX_DIMS]; } BasStaticAllocT; typedef struct BasSymS { struct BasSymS *hashNext; // hash chain, newest first struct BasSymS *orderNext; // creation order in its scope const char *name; // as declared ("Class.name" for class members) const char *formName; // form owning a form variable or procedure (NULL: none) uint32_t hash; uint32_t flags; // SF_* int32_t index; // variable slot; TYPE id; class number int32_t udtTypeId; // BAS_TYPE_UDT variables, FUNCTION results int32_t fixedLen; // STRING * n int32_t objClass; // declared As a class: its number, BAS_CLASS_COLLECTION, else BAS_CLASS_NONE int32_t ownerClass; // class module a procedure or member variable belongs to (BAS_CLASS_NONE) int32_t line; // declaration line union { BasProcInfoT *proc; // SK_SUB / SK_FUNCTION BasConstValT *cval; // SK_CONST BasLabelInfoT *label; // SK_LABEL BasStaticAllocT *alloc; // SK_VAR: a STATIC array with bounds or TYPE (the slot and its alias) } u; uint8_t kind; // BasSymKindE uint8_t scope; // BasScopeE of the storage (SK_VAR); SCOPE_GLOBAL otherwise uint8_t dataType; // BAS_TYPE_* uint8_t aliasScope; // SF_ALIAS: BasScopeE of the slot it names } BasSymT; typedef struct { BasSymT **buckets; // hash chains BasSymT *first; // creation order BasSymT *last; uint32_t mask; // bucket count - 1 int32_t count; } BasScopeTabT; // A field of a TYPE. typedef struct { const char *name; int32_t udtTypeId; // BAS_TYPE_UDT field: its TYPE int32_t fixedLen; // STRING * n (0: variable length) uint8_t dataType; } BasFieldT; typedef struct { BasSymT *sym; BasFieldT *fields; // stb_ds int32_t fieldCount; bool defined; // its TYPE ... END TYPE was compiled } BasTypeDefT; // A public member of a class module. typedef struct { const char *name; // as programs write it (obj.name), or "Iface.name" BasSymT *sym; // procedure or member variable uint8_t kind; // BasMemberKindE } BasClassMemberDefT; // An Event a class module declares. typedef struct { const char *name; int32_t paramCount; uint8_t paramTypes[BAS_MAX_PARAMS]; uint8_t paramFlags[BAS_MAX_PARAMS]; // PARAM_BYVAL, PARAM_TYPED } BasEventDefT; typedef struct { const char *name; const char *defaultMember; // Attribute Name.VB_UserMemId = 0 (NULL: none) BasClassMemberDefT *members; // stb_ds, public members in declaration order BasEventDefT *events; // stb_ds int32_t *implements; // stb_ds, class numbers BasSymT *terminateSym; // Class_Terminate (NULL: none) int32_t varCount; int32_t declaredVars; // member variables the declaration pass gave slots (the first slots) int32_t initCodeAddr; int32_t implementsLine; bool defined; bool isInterface; // some class Implements it } BasClassDefT; // A class member variable the declaration pass read (registered once // every procedure header is known, so a name taken by a procedure stays // the procedure's). typedef struct { const char *name; // module arena int32_t classIdx; int32_t udtTypeId; int32_t objClass; int32_t fixedLen; uint32_t flags; // SF_ARRAY, SF_DYNAMIC, SF_TYPED, SF_PRIVATE uint8_t dataType; } BasMemberVarDeclT; // An early-bound member operand waiting for the member's index in its // class's public member table (known once every class is compiled). typedef struct { int32_t pos; // code position of the uint16 member operand int32_t classIdx; const BasSymT *sym; // the member: a variable or procedure of the class } BasMemberPatchT; typedef struct { int32_t eventIdx; BasSymT *proc; } BasEventHandlerDefT; // A variable declared WithEvents. typedef struct { BasSymT *var; const char *formName; // NULL: not a form variable BasEventHandlerDefT *handlers; // stb_ds, found at the end of the compilation int32_t classIdx; int32_t ownerClass; // BAS_CLASS_NONE unless a class member variable int32_t line; } BasWithEventsDefT; // ============================================================ // API // ============================================================ // A symbol named name (copied into arena) of kind added to tab, or NULL // when memory runs out. Duplicates are the caller's business. BasSymT *basScopeAdd(BasScopeTabT *tab, BasArenaT *arena, const char *name, uint8_t kind, uint8_t dataType); // Case-insensitive lookup: the newest symbol named name, or NULL. BasSymT *basScopeFind(const BasScopeTabT *tab, const char *name); // The newest symbol named name of kind (labels share no names with the rest), or NULL. BasSymT *basScopeFindKind(const BasScopeTabT *tab, const char *name, uint8_t kind); // Empty table with buckets (a power of two) bucket chains; false when // memory runs out. bool basScopeInit(BasScopeTabT *tab, BasArenaT *arena, uint32_t buckets); // Case-insensitive FNV-1a hash of name. uint32_t basScopeHash(const char *name); // Case-insensitive name comparison. bool basScopeNamesEqual(const char *a, const char *b); #endif // DVXBASIC_SCOPE_H