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:
@@ -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 ---
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
159
hbrt/thread.go
159
hbrt/thread.go
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user