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>
179 lines
4.2 KiB
Go
179 lines
4.2 KiB
Go
package hbrt
|
|
|
|
import (
|
|
"testing"
|
|
)
|
|
|
|
func TestMemvar_PublicCreate(t *testing.T) {
|
|
m := NewMemvarTable()
|
|
m.SetPublic("NAME", MakeString("Charles"))
|
|
|
|
v, ok := m.Get("NAME")
|
|
if !ok { t.Fatal("not found") }
|
|
if v.AsString() != "Charles" { t.Errorf("got %q", v.AsString()) }
|
|
if m.Count() != 1 { t.Errorf("count=%d", m.Count()) }
|
|
}
|
|
|
|
func TestMemvar_PublicCaseInsensitive(t *testing.T) {
|
|
m := NewMemvarTable()
|
|
m.SetPublic("Name", MakeString("Charles"))
|
|
|
|
v, ok := m.Get("name")
|
|
if !ok { t.Fatal("case: not found") }
|
|
if v.AsString() != "Charles" { t.Error("case") }
|
|
|
|
v, ok = m.Get("NAME")
|
|
if !ok { t.Fatal("upper: not found") }
|
|
if v.AsString() != "Charles" { t.Error("upper") }
|
|
}
|
|
|
|
func TestMemvar_PublicUpdate(t *testing.T) {
|
|
m := NewMemvarTable()
|
|
m.SetPublic("X", MakeInt(1))
|
|
m.Set("X", MakeInt(42))
|
|
|
|
v, _ := m.Get("X")
|
|
if v.AsInt() != 42 { t.Errorf("got %d", v.AsInt()) }
|
|
}
|
|
|
|
func TestMemvar_PrivateBasic(t *testing.T) {
|
|
m := NewMemvarTable()
|
|
m.SetPrivate("TEMP", MakeInt(10), 1)
|
|
|
|
v, ok := m.Get("TEMP")
|
|
if !ok { t.Fatal("not found") }
|
|
if v.AsInt() != 10 { t.Errorf("got %d", v.AsInt()) }
|
|
}
|
|
|
|
func TestMemvar_PrivateShadow(t *testing.T) {
|
|
m := NewMemvarTable()
|
|
|
|
// PUBLIC x = 100
|
|
m.SetPublic("X", MakeInt(100))
|
|
|
|
// Enter function scope — PRIVATE x = 200 (shadows PUBLIC)
|
|
m.BeginPrivateScope(1)
|
|
m.SetPrivate("X", MakeInt(200), 1)
|
|
|
|
v, _ := m.Get("X")
|
|
if v.AsInt() != 200 { t.Errorf("shadow: got %d", v.AsInt()) }
|
|
|
|
// Return from function — restore PUBLIC x = 100
|
|
m.EndPrivateScope()
|
|
|
|
v, _ = m.Get("X")
|
|
if v.AsInt() != 100 { t.Errorf("restore: got %d", v.AsInt()) }
|
|
}
|
|
|
|
func TestMemvar_PrivateNestedShadow(t *testing.T) {
|
|
m := NewMemvarTable()
|
|
|
|
m.SetPublic("V", MakeString("pub"))
|
|
|
|
// Level 1: PRIVATE V = "priv1"
|
|
m.BeginPrivateScope(1)
|
|
m.SetPrivate("V", MakeString("priv1"), 1)
|
|
|
|
// Level 2: PRIVATE V = "priv2"
|
|
m.BeginPrivateScope(2)
|
|
m.SetPrivate("V", MakeString("priv2"), 2)
|
|
|
|
v, _ := m.Get("V")
|
|
if v.AsString() != "priv2" { t.Errorf("L2: %q", v.AsString()) }
|
|
|
|
// Return level 2
|
|
m.EndPrivateScope()
|
|
v, _ = m.Get("V")
|
|
if v.AsString() != "priv1" { t.Errorf("L1: %q", v.AsString()) }
|
|
|
|
// Return level 1
|
|
m.EndPrivateScope()
|
|
v, _ = m.Get("V")
|
|
if v.AsString() != "pub" { t.Errorf("pub: %q", v.AsString()) }
|
|
}
|
|
|
|
func TestMemvar_PrivateNewVar(t *testing.T) {
|
|
m := NewMemvarTable()
|
|
|
|
// Enter scope — create new PRIVATE (no prior value)
|
|
m.BeginPrivateScope(1)
|
|
m.SetPrivate("TEMP", MakeInt(42), 1)
|
|
|
|
v, ok := m.Get("TEMP")
|
|
if !ok { t.Fatal("not found") }
|
|
if v.AsInt() != 42 { t.Errorf("got %d", v.AsInt()) }
|
|
|
|
// Return — TEMP should be gone
|
|
m.EndPrivateScope()
|
|
_, ok = m.Get("TEMP")
|
|
if ok { t.Error("TEMP should not exist after scope exit") }
|
|
}
|
|
|
|
func TestMemvar_Release(t *testing.T) {
|
|
m := NewMemvarTable()
|
|
m.SetPublic("X", MakeInt(1))
|
|
m.Release("X")
|
|
if m.Exists("X") { t.Error("should not exist") }
|
|
}
|
|
|
|
func TestMemvar_ReleaseAll(t *testing.T) {
|
|
m := NewMemvarTable()
|
|
m.SetPublic("A", MakeInt(1))
|
|
m.SetPublic("B", MakeInt(2))
|
|
m.SetPrivate("C", MakeInt(3), 1)
|
|
m.ReleaseAll()
|
|
if m.Count() != 0 { t.Errorf("count=%d", m.Count()) }
|
|
}
|
|
|
|
func TestMemvar_Names(t *testing.T) {
|
|
m := NewMemvarTable()
|
|
m.SetPublic("ALPHA", MakeInt(1))
|
|
m.SetPublic("BETA", MakeInt(2))
|
|
names := m.Names()
|
|
if len(names) != 2 { t.Errorf("names=%d", len(names)) }
|
|
}
|
|
|
|
func TestMemvar_ThreadIntegration(t *testing.T) {
|
|
vm := NewVM()
|
|
th := vm.NewThread()
|
|
th.Frame(0, 0)
|
|
|
|
// PUBLIC via Thread
|
|
th.DeclarePublic("GNAME")
|
|
th.PushMemvar("GNAME")
|
|
v := th.pop()
|
|
if !v.IsNil() { t.Error("should be NIL initially") }
|
|
|
|
// Set via memvar
|
|
th.push(MakeString("Charles"))
|
|
th.PopMemvar("GNAME")
|
|
|
|
th.PushMemvar("GNAME")
|
|
v = th.pop()
|
|
if v.AsString() != "Charles" { t.Errorf("got %q", v.AsString()) }
|
|
|
|
// PRIVATE
|
|
th.DeclarePrivate("TEMP")
|
|
th.push(MakeInt(42))
|
|
th.PopMemvar("TEMP")
|
|
|
|
th.PushMemvar("TEMP")
|
|
v = th.pop()
|
|
if v.AsInt() != 42 { t.Errorf("got %d", v.AsInt()) }
|
|
}
|
|
|
|
func TestMemvar_MacroAccess(t *testing.T) {
|
|
vm := NewVM()
|
|
th := vm.NewThread()
|
|
th.Frame(0, 0)
|
|
|
|
// Set PUBLIC via memvar table
|
|
th.Memvars.SetPublic("MYVAR", MakeString("hello from memvar"))
|
|
|
|
// MacroEval should find it
|
|
v := th.MacroEval("MYVAR")
|
|
if v.AsString() != "hello from memvar" {
|
|
t.Errorf("macro lookup: got %q", v.AsString())
|
|
}
|
|
}
|