Files
five/hbrt/class_test.go
Charles KWON OhJun 59568f3301 Five v0.9 — Harbour + Go fusion language
- Compiler: PP → Lexer → Parser → Analyzer → Gengo pipeline
- Parser: 232/236 (98%) Harbour compatibility, registry-based dispatch
- RTL: 351 Harbour-compatible functions
- RDD: DBF/NTX/CDX engines with Rushmore bitmap optimization
- Go Interop: IMPORT + pkg.Func() + obj:Method() with FastPath (15M calls/sec)
- HB_FUNC API: Full Harbour C API compatible Go bridge
- Concurrency: SPAWN/LAUNCH/GOROUTINE, <-, WATCH, PARALLEL FOR, ASYNC/AWAIT
- Extensions: Multi-return, DEFER, Slice, f-string, Nil-safe ?:, CONST
- Macro Compiler: Runtime AST parsing and evaluation
- Debugger: TUI debugger with source display, breakpoints, stepping
- FRB: Native + Pcode dual mode runtime binary
- Tests: 13 packages ALL PASS

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 09:41:50 +09:00

216 lines
4.6 KiB
Go

// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
// All rights reserved.
package hbrt
import "testing"
func TestClassCreateAndInstantiate(t *testing.T) {
cls := NewClassDef("Person").
AddData("CNAME", MakeString("")).
AddData("NAGE", MakeInt(0)).
Register()
obj := NewObject(cls)
if !obj.IsObject() {
t.Fatal("should be object")
}
arr := obj.AsArray()
if arr.Class != cls {
t.Errorf("class = %d, want %d", arr.Class, cls)
}
if len(arr.Items) != 2 {
t.Fatalf("fields = %d, want 2", len(arr.Items))
}
// Default values
if arr.Items[0].AsString() != "" {
t.Errorf("cName default = %q, want empty", arr.Items[0].AsString())
}
if arr.Items[1].AsInt() != 0 {
t.Errorf("nAge default = %d, want 0", arr.Items[1].AsInt())
}
}
func TestClassMethodDispatch(t *testing.T) {
cls := NewClassDef("Person")
cls.AddData("CNAME", MakeString(""))
cls.AddData("NAGE", MakeInt(0))
// METHOD New(cName, nAge)
cls.AddMethod("NEW", func(th *Thread) {
th.Frame(2, 0)
defer th.EndProc()
// ::cName := param1
th.PushValue(th.Local(1))
th.SetSelfField("CNAME")
// ::nAge := param2
th.PushValue(th.Local(2))
th.SetSelfField("NAGE")
// RETURN Self
th.PushSelf()
th.RetValue()
})
// METHOD Greet() → "Hello, I'm " + ::cName
cls.AddMethod("GREET", func(th *Thread) {
th.Frame(0, 0)
defer th.EndProc()
th.PushString("Hello, I'm ")
th.PushSelfField("CNAME")
th.Plus()
th.RetValue()
})
cls.Register()
// Create instance
vm := NewVM()
th := vm.NewThread()
th.Frame(0, 0)
// obj := Person():New("Kim", 30)
clsID := FindClass("Person").ID
obj := NewObject(clsID)
th.push(obj)
th.PushString("Kim")
th.PushInt(30)
th.Send("NEW", 2)
resultObj := th.pop()
// Verify fields were set
arr := resultObj.AsArray()
if arr.Items[0].AsString() != "Kim" {
t.Errorf("cName = %q, want Kim", arr.Items[0].AsString())
}
if arr.Items[1].AsInt() != 30 {
t.Errorf("nAge = %d, want 30", arr.Items[1].AsInt())
}
// Call Greet
th.push(resultObj)
th.Send("GREET", 0)
greeting := th.pop()
if greeting.AsString() != "Hello, I'm Kim" {
t.Errorf("greet = %q, want %q", greeting.AsString(), "Hello, I'm Kim")
}
th.EndProc()
}
func TestClassFieldGetterSetter(t *testing.T) {
cls := NewClassDef("Point")
cls.AddData("X", MakeInt(0))
cls.AddData("Y", MakeInt(0))
cls.Register()
vm := NewVM()
th := vm.NewThread()
th.Frame(0, 0)
obj := NewObject(FindClass("Point").ID)
// Getter: obj:X
th.push(obj)
th.Send("X", 0)
if th.pop().AsInt() != 0 {
t.Error("X default should be 0")
}
// Setter: obj:_X := 10 (convention: _FIELDNAME for setter)
th.push(obj)
th.PushInt(10)
th.Send("_X", 1)
th.pop() // discard setter result
// Verify
th.push(obj)
th.Send("X", 0)
if th.pop().AsInt() != 10 {
t.Error("X should be 10 after setter")
}
th.EndProc()
}
func TestClassInheritance(t *testing.T) {
// Parent: Animal
animal := NewClassDef("Animal")
animal.AddData("CNAME", MakeString(""))
animal.AddMethod("SPEAK", func(th *Thread) {
th.Frame(0, 0)
defer th.EndProc()
th.PushString("...")
th.RetValue()
})
animal.Register()
// Child: Dog INHERIT FROM Animal
dog := NewClassDef("Dog")
dog.InheritFrom("Animal")
// Override SPEAK
dog.AddMethod("SPEAK", func(th *Thread) {
th.Frame(0, 0)
defer th.EndProc()
th.PushString("Woof!")
th.RetValue()
})
// Add new method
dog.AddMethod("FETCH", func(th *Thread) {
th.Frame(0, 0)
defer th.EndProc()
th.PushSelfField("CNAME")
th.PushString(" fetches the ball!")
th.Plus()
th.RetValue()
})
dog.Register()
vm := NewVM()
th := vm.NewThread()
th.Frame(0, 0)
// Create Dog
obj := NewObject(FindClass("Dog").ID)
arr := obj.AsArray()
arr.Items[0] = MakeString("Rex") // set cName
// Dog:Speak → "Woof!" (overridden)
th.push(obj)
th.Send("SPEAK", 0)
if th.pop().AsString() != "Woof!" {
t.Error("Dog:Speak should be 'Woof!'")
}
// Dog:Fetch → "Rex fetches the ball!" (new method using inherited field)
th.push(obj)
th.Send("FETCH", 0)
result := th.pop().AsString()
if result != "Rex fetches the ball!" {
t.Errorf("Dog:Fetch = %q", result)
}
// Dog has inherited cName from Animal
th.push(obj)
th.Send("CNAME", 0)
if th.pop().AsString() != "Rex" {
t.Error("inherited CNAME should be Rex")
}
th.EndProc()
}
func TestClassFindAndRegistry(t *testing.T) {
// These classes were registered in previous tests
if FindClass("Person") == nil {
t.Error("Person class should be registered")
}
if FindClass("PERSON") == nil {
t.Error("case-insensitive lookup should work")
}
if FindClass("NonExistent") != nil {
t.Error("non-existent class should return nil")
}
}