Harbour's ::super: idiom routes a method call through the parent of
the class that defines the currently-executing method — Self stays
the child instance, only the vtable entry point shifts. Five
previously parsed ::super as a data-field access (PushSelfField("SUPER"))
which returned nil and panicked on the subsequent Send.
Runtime: Thread.SendSuper(fromClassName, methodName, nArgs).
Binding to the *defining* class (not Self's runtime class) is
load-bearing for 3+ level hierarchies: without it,
Grand:New → ::super:New → Child:New → ::super:New
would resolve to Grand.Parent=Child again and infinite-loop.
Gengo: Generator.curMethodClass tracks the class name across each
method body emission. emitSendExpr detects the nested SendExpr
shape `::super:X(...)` and emits SendSuper with curMethodClass as
the first argument.
Tested (/tmp/test_super, /tmp/test_super2):
Parent → Child: ::super:Greet() returns composed result
Base → Child → Grand: ::super:New chain passes args correctly
Also fixes three gengo unit tests whose expected output was stale
from prior perf commits (b829ed4 const prop, 1f63c7f symbol hoist,
7e4079f string-concat reassoc) — assertions now match the current
optimized codegen.
FiveSql2 43/43, Harbour compat 56/56, Go test ALL PASS.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
397 lines
9.7 KiB
Go
397 lines
9.7 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 a class object — try built-in Value methods (String:Upper, Array:Sort, etc.)
|
|
if result, ok := SendBuiltin(t, objVal, methodName, args); ok {
|
|
t.push(result)
|
|
return
|
|
}
|
|
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)
|
|
}
|
|
|
|
// SendSuper dispatches a method call on Self, but starting the method
|
|
// lookup from the parent of the class that defined the currently-
|
|
// executing method. Implements `::super:Method(args)`.
|
|
//
|
|
// fromClassName is the class whose method body contains the ::super
|
|
// call — gengo emits it at compile time from the `METHOD ... CLASS X`
|
|
// declaration. Using Self's runtime class here would infinite-loop on
|
|
// 3-level hierarchies: Grand:New calls ::super:New → runs Child:New →
|
|
// Child:New calls ::super:New → would look up Grand.Parent = Child
|
|
// again, not Child.Parent = Base. Binding to the defining class is
|
|
// the same technique Harbour uses (method slot carries its origin
|
|
// class in the vtable).
|
|
//
|
|
// Stack: [arg1] ... [argN] → [result].
|
|
func (t *Thread) SendSuper(fromClassName, methodName string, nArgs int) {
|
|
args := make([]Value, nArgs)
|
|
for i := nArgs - 1; i >= 0; i-- {
|
|
args[i] = t.pop()
|
|
}
|
|
|
|
if !t.self.IsObject() {
|
|
panic(t.runtimeError("::super: outside method context"))
|
|
}
|
|
from := FindClass(fromClassName)
|
|
if from == nil {
|
|
panic(t.runtimeError(fmt.Sprintf("::super: unknown defining class %s", fromClassName)))
|
|
}
|
|
if from.Parent == nil {
|
|
panic(t.runtimeError(fmt.Sprintf("class %s has no parent for ::super", from.Name)))
|
|
}
|
|
|
|
parent := from.Parent
|
|
upper := strings.ToUpper(methodName)
|
|
fn, ok := parent.Methods[upper]
|
|
if !ok {
|
|
panic(t.runtimeError(fmt.Sprintf("unknown method %s in parent class %s", methodName, parent.Name)))
|
|
}
|
|
|
|
// Self unchanged — push args and invoke parent's slot.
|
|
for _, a := range args {
|
|
t.push(a)
|
|
}
|
|
t.pendingParams = nArgs
|
|
fn(t)
|
|
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
|
|
}
|