Files
five/hbrt/memvar.go
Charles KWON OhJun 2c812885c3 feat: MEMVAR system — PUBLIC/PRIVATE dynamic variables
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>
2026-04-02 15:03:34 +09:00

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)
}