Complete Harbour-compatible MEMVAR implementation: - PUBLIC: global scope, persist until program end - PRIVATE: function scope + called functions, auto-release on return - Shadowing: PRIVATE can shadow PUBLIC, restored on scope exit - Nested: multi-level PRIVATE scoping with save/restore stack - Thread.PushMemvar/PopMemvar: stack-based memvar access - Thread.DeclarePublic/DeclarePrivate: declaration helpers - MacroEval: &cVar now looks up memvars (was returning string) - Shutdown: Phase 4 clears all memvars on all threads - Case-insensitive: all lookups uppercased Tests: 12 tests including: PUBLIC create/update, case-insensitive, PRIVATE basic, shadow/restore, nested 3-level shadow, new var cleanup, release, releaseAll, names, thread integration, macro access Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
204 lines
5.1 KiB
Go
204 lines
5.1 KiB
Go
// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
|
|
// All rights reserved.
|
|
|
|
// memvar.go — PUBLIC/PRIVATE variable system for Five.
|
|
//
|
|
// Harbour: src/vm/memvars.c — dynamic scoping for PUBLIC/PRIVATE variables.
|
|
//
|
|
// PUBLIC variables:
|
|
// - Visible from anywhere in the program
|
|
// - Persist until program ends or explicitly RELEASEd
|
|
// - Created with: PUBLIC cName, nAge
|
|
//
|
|
// PRIVATE variables:
|
|
// - Visible in declaring function and all functions it calls
|
|
// - Automatically released when declaring function returns
|
|
// - Created with: PRIVATE cName, nAge
|
|
// - Can shadow PUBLIC vars of the same name
|
|
//
|
|
// Access:
|
|
// M->varname — explicit memvar access
|
|
// &cVarName — macro access (runtime lookup)
|
|
// undeclared name — if not LOCAL/STATIC, falls back to memvar
|
|
|
|
package hbrt
|
|
|
|
import (
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
// MemvarScope indicates PUBLIC or PRIVATE.
|
|
type MemvarScope int
|
|
|
|
const (
|
|
MemvarPublic MemvarScope = iota
|
|
MemvarPrivate
|
|
)
|
|
|
|
// memvarEntry stores one memvar with its scope and owning call depth.
|
|
type memvarEntry struct {
|
|
Value Value
|
|
Scope MemvarScope
|
|
CallDepth int // for PRIVATE: call stack depth when declared
|
|
}
|
|
|
|
// MemvarTable manages all PUBLIC/PRIVATE variables for a thread.
|
|
type MemvarTable struct {
|
|
mu sync.RWMutex
|
|
vars map[string]*memvarEntry // uppercase name → entry
|
|
privStack []privFrame // stack of PRIVATE scopes for cleanup
|
|
}
|
|
|
|
// privFrame records which PRIVATE vars were created at a given call depth.
|
|
type privFrame struct {
|
|
callDepth int
|
|
names []string // uppercase names to release on return
|
|
saved map[string]*memvarEntry // previous values (for shadowing)
|
|
}
|
|
|
|
// NewMemvarTable creates an empty memvar table.
|
|
func NewMemvarTable() *MemvarTable {
|
|
return &MemvarTable{
|
|
vars: make(map[string]*memvarEntry),
|
|
}
|
|
}
|
|
|
|
// --- PUBLIC ---
|
|
|
|
// SetPublic creates or updates a PUBLIC memvar.
|
|
func (m *MemvarTable) SetPublic(name string, val Value) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
upper := strings.ToUpper(name)
|
|
m.vars[upper] = &memvarEntry{Value: val, Scope: MemvarPublic}
|
|
}
|
|
|
|
// --- PRIVATE ---
|
|
|
|
// BeginPrivateScope starts a new PRIVATE scope at the given call depth.
|
|
// Called at the start of a function that declares PRIVATE vars.
|
|
func (m *MemvarTable) BeginPrivateScope(callDepth int) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.privStack = append(m.privStack, privFrame{
|
|
callDepth: callDepth,
|
|
saved: make(map[string]*memvarEntry),
|
|
})
|
|
}
|
|
|
|
// SetPrivate creates or shadows a PRIVATE memvar.
|
|
func (m *MemvarTable) SetPrivate(name string, val Value, callDepth int) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
upper := strings.ToUpper(name)
|
|
|
|
// Save previous value for restore on scope exit
|
|
if len(m.privStack) > 0 {
|
|
frame := &m.privStack[len(m.privStack)-1]
|
|
if _, exists := frame.saved[upper]; !exists {
|
|
// Save old value (or nil if didn't exist)
|
|
if old, ok := m.vars[upper]; ok {
|
|
oldCopy := *old
|
|
frame.saved[upper] = &oldCopy
|
|
} else {
|
|
frame.saved[upper] = nil // marker: didn't exist before
|
|
}
|
|
frame.names = append(frame.names, upper)
|
|
}
|
|
}
|
|
|
|
m.vars[upper] = &memvarEntry{Value: val, Scope: MemvarPrivate, CallDepth: callDepth}
|
|
}
|
|
|
|
// EndPrivateScope restores PRIVATE vars from the most recent scope.
|
|
// Called when a function returns.
|
|
func (m *MemvarTable) EndPrivateScope() {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if len(m.privStack) == 0 {
|
|
return
|
|
}
|
|
frame := m.privStack[len(m.privStack)-1]
|
|
m.privStack = m.privStack[:len(m.privStack)-1]
|
|
|
|
// Restore saved values
|
|
for _, name := range frame.names {
|
|
if saved, ok := frame.saved[name]; ok {
|
|
if saved == nil {
|
|
// Didn't exist before — remove
|
|
delete(m.vars, name)
|
|
} else {
|
|
// Restore previous value
|
|
m.vars[name] = saved
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- Access ---
|
|
|
|
// Get retrieves a memvar value by name. Returns (value, true) or (nil, false).
|
|
func (m *MemvarTable) Get(name string) (Value, bool) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
upper := strings.ToUpper(name)
|
|
if e, ok := m.vars[upper]; ok {
|
|
return e.Value, true
|
|
}
|
|
return MakeNil(), false
|
|
}
|
|
|
|
// Set updates an existing memvar value (PUBLIC or PRIVATE).
|
|
func (m *MemvarTable) Set(name string, val Value) bool {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
upper := strings.ToUpper(name)
|
|
if e, ok := m.vars[upper]; ok {
|
|
e.Value = val
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Exists checks if a memvar exists.
|
|
func (m *MemvarTable) Exists(name string) bool {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
_, ok := m.vars[strings.ToUpper(name)]
|
|
return ok
|
|
}
|
|
|
|
// Release removes a memvar by name.
|
|
func (m *MemvarTable) Release(name string) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
delete(m.vars, strings.ToUpper(name))
|
|
}
|
|
|
|
// ReleaseAll removes all memvars.
|
|
func (m *MemvarTable) ReleaseAll() {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.vars = make(map[string]*memvarEntry)
|
|
m.privStack = nil
|
|
}
|
|
|
|
// Names returns all memvar names.
|
|
func (m *MemvarTable) Names() []string {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
names := make([]string, 0, len(m.vars))
|
|
for k := range m.vars {
|
|
names = append(names, k)
|
|
}
|
|
return names
|
|
}
|
|
|
|
// Count returns the number of memvars.
|
|
func (m *MemvarTable) Count() int {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
return len(m.vars)
|
|
}
|