DVX_GUI/src/apps/kpunch/dvxbasic/compiler/scope.h
2026-10-05 20:01:04 -05:00

308 lines
14 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.
// 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 <stdbool.h>
#include <stdint.h>
// 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