Files
five/hbrt/class.go
Charles KWON OhJun 207fa9f7dd fix: Phase 1 Step 0 cleanup + CRITICAL #3, MEDIUM #36-37, LOW #50
Files modified (5):
  hbrt/macro.go — Replace hand-rolled parseFloat/parseInt64 with strconv (#50)
                   Remove stale TODO, redundant TrimSpace
  hbrt/macroeval.go — Use strconv for literal parsing (was using removed functions)
  hbrt/class.go — CRITICAL #3: Change RWMutex to Mutex on classList
                   Prevents slice reallocation race on concurrent GetClass
  hbrt/goroutine.go — #36: Channel double-close protection (sync.Once)
                       #37: Send on closed channel recovery (defer/recover)
                       Add IsClosed(), safe Receive (handles closed channel)
  hbrt/gobridge.go — Already clean (confirmed)
  hbrt/hbfunc.go — Already clean (confirmed)

Issues resolved: #3 (CRITICAL), #36, #37 (MEDIUM), #50 (LOW)
Total fixed: 16/53

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 10:51:20 +09:00

346 lines
8.0 KiB
Go

// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
// All rights reserved.
// CLASS system for the Five runtime.
// Harbour: classes.c — object = array with uiClass, methods in class registry.
//
// Key concepts:
// - Class = definition (name, DATA fields, METHODs, parent)
// - Object = HbArray with Class > 0 (fields stored in Items[])
// - Send = method dispatch: obj:method(args) → lookup class → call func
// - :: = Self access (current object in method context)
// - INHERIT FROM = parent class embedding
// - Operator overloading: HB_OO_OP_PLUS etc.
//
// Reference:
// /mnt/d/harbour-core/include/hbapicls.h
// /mnt/d/harbour-core/src/vm/classes.c
package hbrt
import (
"fmt"
"strings"
"sync"
)
// ClassDef defines a class (DATA fields + METHODs).
// Harbour: internal class structure in classes.c
type ClassDef struct {
ID uint16
Name string
Parent *ClassDef // INHERIT FROM
Fields []ClassField // DATA declarations (ordered)
Methods map[string]MethodFunc // method name → function
Operators [MaxOperator + 1]MethodFunc // operator overloading
fieldMap map[string]int // field name → index
}
// ClassField describes a DATA member.
type ClassField struct {
Name string
Init Value // default INIT value
AsType string // optional type hint
}
// MethodFunc is the signature for class methods.
// The method receives the thread; Self is available via thread context.
type MethodFunc func(t *Thread)
// Operator IDs matching Harbour's HB_OO_OP_*
const (
OpPlus = 0
OpMinus = 1
OpMult = 2
OpDivide = 3
OpMod = 4
OpPower = 5
OpInc = 6
OpDec = 7
OpEqual = 8
OpExactEqual = 9
OpNotEqual = 10
OpLess = 11
OpLessEqual = 12
OpGreater = 13
OpGreaterEqual = 14
OpAssign = 15
OpInString = 16
OpInclude = 17
OpNot = 18
OpAnd = 19
OpOr = 20
OpArrayIndex = 21
MaxOperator = 21
)
// --- Class Registry ---
var (
classRegMu sync.Mutex // full mutex (not RW) — prevents slice reallocation race on classList
classReg = map[string]*ClassDef{}
classList []*ClassDef // index = classID - 1
)
// RegisterClass registers a class definition.
// Returns the assigned class ID (1-based).
func RegisterClass(cls *ClassDef) uint16 {
classRegMu.Lock()
defer classRegMu.Unlock()
cls.ID = uint16(len(classList) + 1)
classList = append(classList, cls)
classReg[strings.ToUpper(cls.Name)] = cls
// Build field index map
cls.fieldMap = make(map[string]int, len(cls.Fields))
for i, f := range cls.Fields {
cls.fieldMap[strings.ToUpper(f.Name)] = i
}
return cls.ID
}
// FindClass looks up a class by name.
func FindClass(name string) *ClassDef {
classRegMu.Lock()
defer classRegMu.Unlock()
return classReg[strings.ToUpper(name)]
}
// GetClass looks up a class by ID.
func GetClass(id uint16) *ClassDef {
classRegMu.Lock()
defer classRegMu.Unlock()
if int(id) > 0 && int(id) <= len(classList) {
return classList[id-1]
}
return nil
}
// --- Class builder (fluent API for generated code) ---
// NewClassDef creates a new class definition builder.
func NewClassDef(name string) *ClassDef {
return &ClassDef{
Name: name,
Methods: make(map[string]MethodFunc),
}
}
// InheritFrom sets the parent class.
func (c *ClassDef) InheritFrom(parentName string) *ClassDef {
parent := FindClass(parentName)
if parent != nil {
c.Parent = parent
// Copy parent fields first
c.Fields = append(append([]ClassField{}, parent.Fields...), c.Fields...)
// Copy parent methods (child can override)
for name, fn := range parent.Methods {
if _, exists := c.Methods[name]; !exists {
c.Methods[name] = fn
}
}
// Copy parent operators
for i, fn := range parent.Operators {
if c.Operators[i] == nil {
c.Operators[i] = fn
}
}
}
return c
}
// AddData adds a DATA field to the class.
func (c *ClassDef) AddData(name string, init Value) *ClassDef {
c.Fields = append(c.Fields, ClassField{Name: name, Init: init})
return c
}
// AddMethod adds a METHOD to the class.
func (c *ClassDef) AddMethod(name string, fn MethodFunc) *ClassDef {
c.Methods[strings.ToUpper(name)] = fn
return c
}
// AddOperator sets an operator overload.
func (c *ClassDef) AddOperator(op int, fn MethodFunc) *ClassDef {
if op >= 0 && op <= MaxOperator {
c.Operators[op] = fn
}
return c
}
// Register registers this class and returns the class ID.
func (c *ClassDef) Register() uint16 {
return RegisterClass(c)
}
// FieldIndex returns the 0-based field index by name, or -1.
func (c *ClassDef) FieldIndex(name string) int {
if c.fieldMap == nil {
return -1
}
if idx, ok := c.fieldMap[strings.ToUpper(name)]; ok {
return idx
}
// Check parent
if c.Parent != nil {
return c.Parent.FieldIndex(name)
}
return -1
}
// --- Object creation ---
// NewObject creates a new object instance of a class.
// Harbour: object = array with uiClass set.
func NewObject(classID uint16) Value {
cls := GetClass(classID)
if cls == nil {
return MakeNil()
}
obj := MakeObject(classID, len(cls.Fields))
arr := obj.AsArray()
// Initialize fields with default values
for i, f := range cls.Fields {
arr.Items[i] = f.Init
}
return obj
}
// --- Method dispatch ---
// Send dispatches a method call on an object.
// Harbour: hb_objGetMethod + call
// Stack: [object] [arg1] ... [argN] → call method → [result]
func (t *Thread) Send(methodName string, nArgs int) {
// Collect args
args := make([]Value, nArgs)
for i := nArgs - 1; i >= 0; i-- {
args[i] = t.pop()
}
objVal := t.pop() // object
if !objVal.IsObject() {
// Not an object — try as property access on non-object
panic(t.runtimeError(fmt.Sprintf("not an object for method %s", methodName)))
}
arr := objVal.AsArray()
cls := GetClass(arr.Class)
if cls == nil {
panic(t.runtimeError(fmt.Sprintf("unknown class ID %d", arr.Class)))
}
upperMethod := strings.ToUpper(methodName)
// Check for data field access (getter)
if nArgs == 0 {
if idx := cls.FieldIndex(methodName); idx >= 0 {
t.push(arr.Items[idx])
return
}
}
// Check for data field setter: _FIELDNAME convention
if nArgs == 1 && strings.HasPrefix(upperMethod, "_") {
fieldName := upperMethod[1:]
if idx := cls.FieldIndex(fieldName); idx >= 0 {
arr.Items[idx] = args[0]
t.push(args[0])
return
}
}
// Look up method
fn, ok := cls.Methods[upperMethod]
if !ok {
panic(t.runtimeError(fmt.Sprintf("unknown method %s in class %s", methodName, cls.Name)))
}
// Set up Self context
oldSelf := t.self
t.self = objVal
// Push args for Frame
for _, arg := range args {
t.push(arg)
}
t.pendingParams = nArgs
fn(t)
// Restore Self
t.self = oldSelf
// Push return value
t.push(t.retVal)
}
// SendAssign dispatches a setter: obj:prop := value
// Generated for ::fieldName := value
func (t *Thread) SendAssign(fieldName string) {
val := t.pop()
objVal := t.pop()
if !objVal.IsObject() {
panic(t.runtimeError("not an object for assignment"))
}
arr := objVal.AsArray()
cls := GetClass(arr.Class)
if cls == nil {
return
}
if idx := cls.FieldIndex(fieldName); idx >= 0 {
arr.Items[idx] = val
}
}
// Send0 dispatches a no-arg method (getter).
func (t *Thread) Send0(methodName string) {
t.Send(methodName, 0)
}
// PushSelfField pushes a field from the current Self object.
// Used by :: access in methods.
func (t *Thread) PushSelfField(fieldName string) {
if t.self.IsNil() {
t.push(MakeNil())
return
}
arr := t.self.AsArray()
cls := GetClass(arr.Class)
if cls != nil {
if idx := cls.FieldIndex(fieldName); idx >= 0 {
t.push(arr.Items[idx])
return
}
}
t.push(MakeNil())
}
// SetSelfField sets a field on the current Self object.
func (t *Thread) SetSelfField(fieldName string) {
val := t.pop()
if t.self.IsNil() {
return
}
arr := t.self.AsArray()
cls := GetClass(arr.Class)
if cls != nil {
if idx := cls.FieldIndex(fieldName); idx >= 0 {
arr.Items[idx] = val
}
}
}
// GetSelf returns the current Self value.
func (t *Thread) GetSelf() Value {
return t.self
}