Files
five/hbrt/memvar_test.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

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