feat: FiveSql2 43/43, @byref, mutable closure, RTL 479, DateTime fix

Major changes since last commit:
- FiveSql2 SQL:1999 engine (10,458 LOC) — 43/43 ALL PASS
- 21 compiler/runtime bugs fixed (short-circuit AND/OR, FOR LOOP, etc.)
- @byref pass-by-reference via RefCell pattern
- Mutable closure capture (EnsureLocalRef + RefCell sharing)
- RTL: 400 → 479 functions (+79: file, string, datetime, hash, UTF-8)
- DateTime/Timestamp fully working (hb_DateTime, hb_Hour/Min/Sec, display)
- Reserved word guard (39 keywords blocked from function calls)
- AEval arg order fix (element before index)
- Closure capture redecl fix (unique _cap_ names per block)
- Hash/string indexing in ArrayPush/ArrayPop
- Harbour compat test suite: 51/51
- 4 docs: Porting Report, Implementation Plan, Optimization Plan, Commercialization

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-11 11:35:37 +09:00
parent d451b836a6
commit 486e466592
129 changed files with 35248 additions and 241 deletions

View File

@@ -299,61 +299,72 @@ func (t *Thread) AddInt(n int64) {
panic(t.argError("+int", a))
}
// LocalAdd adds the top of stack to a local variable, pops the value.
// LocalAdd adds the top of stack to a local variable, pops the value. Byref-aware.
// Harbour: hb_xvmLocalAdd
func (t *Thread) LocalAdd(n int) {
val := t.pop()
idx := t.localIndex(n)
loc := t.locals[idx]
isRef := loc.Type() == tByref
if isRef {
loc = (*HbRefCell)(loc.ptr).V
}
var result Value
if loc.IsNumInt() && val.IsNumInt() {
r := loc.AsNumInt() + val.AsNumInt()
if (val.AsNumInt() >= 0 && r >= loc.AsNumInt()) || (val.AsNumInt() < 0 && r < loc.AsNumInt()) {
t.locals[idx] = MakeNumInt(r)
result = MakeNumInt(r)
} else {
t.locals[idx] = MakeDoubleAuto(float64(loc.AsNumInt()) + float64(val.AsNumInt()))
result = MakeDoubleAuto(float64(loc.AsNumInt()) + float64(val.AsNumInt()))
}
return
}
if loc.IsNumeric() && val.IsNumeric() {
} else if loc.IsNumeric() && val.IsNumeric() {
dec := maxDec(loc.Decimal(), val.Decimal())
t.locals[idx] = MakeDouble(loc.AsNumDouble()+val.AsNumDouble(), 255, dec)
return
result = MakeDouble(loc.AsNumDouble()+val.AsNumDouble(), 255, dec)
} else if loc.IsString() && val.IsString() {
result = MakeString(loc.AsString() + val.AsString())
} else {
panic(t.argError("+=", loc, val))
}
if loc.IsString() && val.IsString() {
t.locals[idx] = MakeString(loc.AsString() + val.AsString())
return
if isRef {
(*HbRefCell)(t.locals[idx].ptr).V = result
} else {
t.locals[idx] = result
}
panic(t.argError("+=", loc, val))
}
// LocalAddInt adds an integer constant directly to a local variable.
// LocalAddInt adds an integer constant directly to a local variable. Byref-aware.
// Harbour: hb_xvmLocalAddInt (fused PUSHINT + PLUS + POPLOCAL)
func (t *Thread) LocalAddInt(n int, val int64) {
idx := t.localIndex(n)
idx := t.curFrame.localBase + n - 1 // inline index
loc := t.locals[idx]
isRef := loc.Type() == tByref
if isRef {
loc = (*HbRefCell)(loc.ptr).V
}
var result Value
if loc.IsNumInt() {
r := loc.AsNumInt() + val
if (val >= 0 && r >= loc.AsNumInt()) || (val < 0 && r < loc.AsNumInt()) {
t.locals[idx] = MakeNumInt(r)
result = MakeNumInt(r)
} else {
t.locals[idx] = MakeDoubleAuto(float64(loc.AsNumInt()) + float64(val))
result = MakeDoubleAuto(float64(loc.AsNumInt()) + float64(val))
}
return
} else if loc.IsDouble() {
result = MakeDouble(loc.AsDouble()+float64(val), loc.Length(), loc.Decimal())
} else if loc.IsDate() {
result = MakeDate(loc.AsJulian() + val)
} else {
panic(t.argError("+int", loc))
}
if loc.IsDouble() {
t.locals[idx] = MakeDouble(loc.AsDouble()+float64(val), loc.Length(), loc.Decimal())
return
if isRef {
(*HbRefCell)(t.locals[idx].ptr).V = result
} else {
t.locals[idx] = result
}
if loc.IsDate() {
t.locals[idx] = MakeDate(loc.AsJulian() + val)
return
}
panic(t.argError("+int", loc))
}
// --- Helpers ---

View File

@@ -35,13 +35,37 @@ func (t *Thread) HashGen(n int) {
})
}
// ArrayPush pops index and array, pushes array[index].
// ArrayPush pops index and array/hash, pushes array[index] or hash[key].
// Harbour: HB_P_ARRAYPUSH
func (t *Thread) ArrayPush() {
idx := t.pop()
arr := t.pop()
// Hash: h[key] → value
if arr.IsHash() {
hh := arr.AsHash()
for i, k := range hh.Keys {
if valueEqual(k, idx) {
t.push(hh.Values[i])
return
}
}
t.push(MakeNil())
return
}
if !arr.IsArray() {
// String indexing: cString[n] → single char
if arr.IsString() {
s := arr.AsString()
n := int(idx.AsNumInt())
if n >= 1 && n <= len(s) {
t.push(MakeString(string(s[n-1])))
return
}
t.push(MakeString(""))
return
}
panic(t.argError("[]", arr, idx))
}
ha := arr.AsArray()
@@ -54,13 +78,27 @@ func (t *Thread) ArrayPush() {
t.push(ha.Items[n-1])
}
// ArrayPop pops value, index, array and sets array[index] = value.
// ArrayPop pops value, index, array/hash and sets array[index]=value or hash[key]=value.
// Harbour: HB_P_ARRAYPOP
func (t *Thread) ArrayPop() {
val := t.pop()
idx := t.pop()
arr := t.pop()
// Hash: h[key] := value
if arr.IsHash() {
hh := arr.AsHash()
for i, k := range hh.Keys {
if valueEqual(k, idx) {
hh.Values[i] = val
return
}
}
hh.Keys = append(hh.Keys, idx)
hh.Values = append(hh.Values, val)
return
}
if !arr.IsArray() {
panic(t.argError("[]=", arr, idx))
}

View File

@@ -168,17 +168,22 @@ func (t *Thread) PopLogical() bool {
if v.Type() == tLogical {
return v.scalar != 0
}
// Harbour: non-logical values in condition → type error
// NIL → false (Harbour treats NIL as .F. in conditions)
if v.Type() == tNil {
return false
}
panic(t.argError("logical", v))
}
// --- Fused opcodes: combined stack operations for hot loops ---
// Eliminates 3-4 function calls per FOR iteration.
// LocalLessEqualInt: t.Local(idx) <= val (no stack ops)
// LocalLessEqualInt: t.Local(idx) <= val (no stack ops). Byref-aware.
func (t *Thread) LocalLessEqualInt(localIdx, val int) bool {
idx := t.curFrame.localBase + localIdx - 1
v := t.locals[idx]
v := t.locals[t.curFrame.localBase+localIdx-1]
if v.Type() == tByref {
v = (*HbRefCell)(v.ptr).V
}
if v.Type() == tInt {
return int64(v.scalar) <= int64(val)
}
@@ -188,10 +193,12 @@ func (t *Thread) LocalLessEqualInt(localIdx, val int) bool {
return false
}
// LocalGreaterEqualInt: t.Local(idx) >= val (for descending FOR)
// LocalGreaterEqualInt: t.Local(idx) >= val (for descending FOR). Byref-aware.
func (t *Thread) LocalGreaterEqualInt(localIdx, val int) bool {
idx := t.curFrame.localBase + localIdx - 1
v := t.locals[idx]
v := t.locals[t.curFrame.localBase+localIdx-1]
if v.Type() == tByref {
v = (*HbRefCell)(v.ptr).V
}
if v.Type() == tInt {
return int64(v.scalar) >= int64(val)
}

View File

@@ -86,7 +86,8 @@ type Thread struct {
Memvars *MemvarTable
// WorkArea manager (goroutine-local, no locks needed)
WA interface{} // *hbrdd.WorkAreaManager — set by caller to avoid import cycle
WA interface{} // *hbrdd.WorkAreaManager — set by caller to avoid import cycle
waStack []uint16 // saved workarea numbers for (expr)->(expr) context switching
// VM reference (shared, read-mostly)
vm *VM
@@ -248,14 +249,15 @@ func (t *Thread) Frame(params, locals int) {
// EndProc is called via defer at the end of every function.
// Handles recover for BEGIN SEQUENCE and restores frame.
// HbError panics are re-panicked so the generated SEQUENCE handler can catch them.
func (t *Thread) EndProc() {
if r := recover(); r != nil {
if hbErr, ok := r.(*HbError); ok {
t.handleSequenceError(hbErr)
} else {
fmt.Fprintf(os.Stderr, "Five runtime error: %v\n", r)
panic(r)
t.endFrame()
if _, ok := r.(*HbError); ok {
panic(r) // re-panic: let BEGIN SEQUENCE's generated recover catch it
}
fmt.Fprintf(os.Stderr, "Five runtime error: %v\n", r)
panic(r)
}
t.endFrame()
}
@@ -295,38 +297,130 @@ func (t *Thread) EndProcNoRecover() {
func (t *Thread) PushLocal(n int) {
idx := t.localIndex(n)
t.push(t.locals[idx])
v := t.locals[idx]
if v.Type() == tByref {
t.push((*HbRefCell)(v.ptr).V)
} else {
t.push(v)
}
}
func (t *Thread) PopLocal(n int) {
idx := t.localIndex(n)
t.locals[idx] = t.pop()
val := t.pop()
if e := t.locals[idx]; e.Type() == tByref {
(*HbRefCell)(e.ptr).V = val
} else {
t.locals[idx] = val
}
}
func (t *Thread) Local(n int) Value {
return t.locals[t.localIndex(n)]
v := t.locals[t.localIndex(n)]
if v.Type() == tByref {
return (*HbRefCell)(v.ptr).V
}
return v
}
func (t *Thread) SetLocal(n int, v Value) {
t.locals[t.localIndex(n)] = v
idx := t.localIndex(n)
if e := t.locals[idx]; e.Type() == tByref {
(*HbRefCell)(e.ptr).V = v
} else {
t.locals[idx] = v
}
}
// PushLocalRef pushes a reference to a local variable (for @param).
// Harbour: hb_vmPushLocalByRef
// Simplified: pushes the value (true BYREF needs refcell pattern).
// TODO: implement proper ByRef with shared mutation.
// Fast variants — no bounds checking (gengo guarantees valid indices).
// Byref-aware: transparently dereference/write-through RefCell.
func (t *Thread) PushLocalFast(n int) {
v := t.locals[t.curFrame.localBase+n-1]
if v.Type() == tByref {
t.push((*HbRefCell)(v.ptr).V)
} else {
t.push(v)
}
}
func (t *Thread) PopLocalFast(n int) {
idx := t.curFrame.localBase + n - 1
val := t.pop()
if e := t.locals[idx]; e.Type() == tByref {
(*HbRefCell)(e.ptr).V = val
} else {
t.locals[idx] = val
}
}
func (t *Thread) LocalFast(n int) Value {
v := t.locals[t.curFrame.localBase+n-1]
if v.Type() == tByref {
return (*HbRefCell)(v.ptr).V
}
return v
}
func (t *Thread) SetLocalFast(n int, v Value) {
idx := t.curFrame.localBase + n - 1
if e := t.locals[idx]; e.Type() == tByref {
(*HbRefCell)(e.ptr).V = v
} else {
t.locals[idx] = v
}
}
// PushLocalRef creates a shared RefCell and pushes it for @param.
// Both caller's local and callee's param point to the same cell.
func (t *Thread) PushLocalRef(n int) {
t.push(t.Local(n)) // simplified: pass by value for now
idx := t.localIndex(n)
v := t.locals[idx]
if v.Type() == tByref {
t.push(v) // already a RefCell — share it
return
}
cell := &HbRefCell{V: v}
ref := MakeByref(cell)
t.locals[idx] = ref // caller's local becomes RefCell
t.push(ref) // callee gets same RefCell
}
func (t *Thread) LocalAsString(n int) string {
return t.Local(n).AsString()
}
// LocalSetInt is an optimization: set local directly without stack.
// Harbour: hb_xvmLocalSetInt(n, val)
// EnsureLocalRef converts a local to a RefCell if it isn't one already.
// Used by closure capture to enable shared mutable access.
func (t *Thread) EnsureLocalRef(n int) {
idx := t.curFrame.localBase + n - 1
v := t.locals[idx]
if v.Type() != tByref {
cell := &HbRefCell{V: v}
t.locals[idx] = MakeByref(cell)
}
}
// LocalRaw returns the raw Value at local slot (including RefCell wrapper).
// Used by closure capture to grab the RefCell itself, not the dereferenced value.
func (t *Thread) LocalRaw(n int) Value {
return t.locals[t.curFrame.localBase+n-1]
}
// SetLocalRaw sets a local slot to the raw Value (including RefCell wrapper).
// Used by closure to inject shared RefCell into block locals.
func (t *Thread) SetLocalRaw(n int, v Value) {
t.locals[t.curFrame.localBase+n-1] = v
}
// LocalSetInt is an optimization: set local directly without stack. Byref-aware.
func (t *Thread) LocalSetInt(n int, val int) {
t.locals[t.localIndex(n)] = MakeInt(val)
idx := t.localIndex(n)
if e := t.locals[idx]; e.Type() == tByref {
(*HbRefCell)(e.ptr).V = MakeInt(val)
} else {
t.locals[idx] = MakeInt(val)
}
}
func (t *Thread) localIndex(n int) int {
@@ -537,3 +631,32 @@ func (t *Thread) PopStatic(module string, n int) {
}
statics[n-1] = t.pop()
}
// --- Workarea context switching for (alias)->(expr) ---
func (t *Thread) WASaveAndSelect(areaNum int) {
type waSel interface{ SelectByNum(uint16); Current() uint16 }
if wam, ok := t.WA.(waSel); ok {
t.waStack = append(t.waStack, wam.Current())
wam.SelectByNum(uint16(areaNum))
}
}
func (t *Thread) WASaveAndSelectAlias(alias string) {
type waSel interface{ SelectByAlias(string); Current() uint16 }
if wam, ok := t.WA.(waSel); ok {
t.waStack = append(t.waStack, wam.Current())
wam.SelectByAlias(alias)
}
}
func (t *Thread) WARestore() {
if n := len(t.waStack); n > 0 {
saved := t.waStack[n-1]
t.waStack = t.waStack[:n-1]
type waSel interface{ SelectByNum(uint16) }
if wam, ok := t.WA.(waSel); ok {
wam.SelectByNum(saved)
}
}
}

View File

@@ -454,10 +454,23 @@ func (v Value) String() string {
case tSymbol:
return "Symbol"
case tByref:
return "Byref"
if cell := (*HbRefCell)(v.ptr); cell != nil {
return fmt.Sprintf("Byref→%s", cell.V.String())
}
return "Byref(nil)"
case tPointer:
return fmt.Sprintf("Pointer(%x)", v.scalar)
default:
return fmt.Sprintf("Unknown(type=%d)", v.Type())
}
}
// --- Byref (pass-by-reference) support ---
// HbRefCell is a shared mutable cell for @variable pass-by-reference.
type HbRefCell struct{ V Value }
// MakeByref wraps a RefCell into a tByref Value.
func MakeByref(cell *HbRefCell) Value {
return Value{info: uint64(tByref) << typeShift, ptr: unsafe.Pointer(cell)}
}