Problem: every RTL function calls defer t.EndProc() which does recover(). 50K SEEK loop = 250K recover() calls = ~12ms wasted. Solution: EndProcFast() skips recover (only needs endFrame restore). Applied to ALL RTL functions in strings.go, rdd.go, missing.go, database.go. EndProc() with recover kept for generated PRG code (needs BEGIN SEQUENCE). Analysis (50K sequential SEEK breakdown): Go NTX Seek direct: 7ms (faster than Harbour 27ms!) PRG VM overhead: 38ms (Frame + RTL calls + key generation) Key generation: 25ms (Str+LTrim+PadL+PadR = 5 RTL Frame/EndProc per iter) With EndProcFast: RTL overhead reduced ~30%. CDX SCOPE: 2ms (Harbour 4ms — 2x FASTER!) 82/82 stress PASS. 14 packages ALL PASS. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
603 lines
10 KiB
Go
603 lines
10 KiB
Go
// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
|
|
// All rights reserved.
|
|
|
|
// Database callable functions: FIELDPUT, ALIAS, DBEVAL, DBUSEAREA, DBCLOSEAREA,
|
|
// DBGOTO, DBSKIP, DBAPPEND, DBDELETE, DBRECALL, DBCOMMIT, DBSEEK,
|
|
// DBGOTOP, DBGOBOTTOM, DBRLOCKLIST, DBSETFILTER, DBCLEARFILTER
|
|
|
|
package hbrtl
|
|
|
|
import (
|
|
"five/hbrt"
|
|
"five/hbrdd"
|
|
)
|
|
|
|
// FIELDPUT(nField, xValue) → xValue
|
|
func rtlFieldPut(t *hbrt.Thread) {
|
|
t.Frame(2, 0)
|
|
defer t.EndProcFast()
|
|
wam := getWA(t)
|
|
if wam == nil {
|
|
t.RetNil()
|
|
return
|
|
}
|
|
area := wam.Current()
|
|
if area == nil {
|
|
t.RetNil()
|
|
return
|
|
}
|
|
nField := t.Local(1).AsInt()
|
|
val := t.Local(2)
|
|
area.PutValue(nField-1, val) // 1-based to 0-based
|
|
t.RetVal(val)
|
|
}
|
|
|
|
// ALIAS([nWorkArea]) → cAlias
|
|
func rtlAlias(t *hbrt.Thread) {
|
|
nParams := t.ParamCount()
|
|
t.Frame(nParams, 0)
|
|
defer t.EndProcFast()
|
|
wam := getWA(t)
|
|
if wam == nil {
|
|
t.RetString("")
|
|
return
|
|
}
|
|
area := wam.Current()
|
|
if area != nil {
|
|
t.RetString(area.Alias())
|
|
} else {
|
|
t.RetString("")
|
|
}
|
|
}
|
|
|
|
// DBEVAL(bBlock [, bFor [, bWhile [, nCount [, nRecord [, lRest]]]]]) → NIL
|
|
func rtlDbEval(t *hbrt.Thread) {
|
|
nParams := t.ParamCount()
|
|
t.Frame(nParams, 0)
|
|
defer t.EndProcFast()
|
|
|
|
wam := getWA(t)
|
|
if wam == nil {
|
|
t.RetNil()
|
|
return
|
|
}
|
|
area := wam.Current()
|
|
if area == nil {
|
|
t.RetNil()
|
|
return
|
|
}
|
|
|
|
block := t.Local(1)
|
|
if !block.IsBlock() {
|
|
t.RetNil()
|
|
return
|
|
}
|
|
|
|
var bFor, bWhile hbrt.Value
|
|
nCount := -1
|
|
lRest := false
|
|
|
|
if nParams >= 2 {
|
|
bFor = t.Local(2)
|
|
}
|
|
if nParams >= 3 {
|
|
bWhile = t.Local(3)
|
|
}
|
|
if nParams >= 4 && !t.Local(4).IsNil() {
|
|
nCount = t.Local(4).AsInt()
|
|
}
|
|
if nParams >= 5 && !t.Local(5).IsNil() {
|
|
nRec := t.Local(5).AsInt()
|
|
area.GoTo(uint32(nRec))
|
|
}
|
|
if nParams >= 6 && !t.Local(6).IsNil() {
|
|
lRest = t.Local(6).AsBool()
|
|
}
|
|
|
|
// If not lRest and no record specified, go top
|
|
if !lRest && (nParams < 5 || t.Local(5).IsNil()) {
|
|
area.GoTop()
|
|
}
|
|
|
|
count := 0
|
|
for !area.EOF() {
|
|
if nCount >= 0 && count >= nCount {
|
|
break
|
|
}
|
|
|
|
// While condition
|
|
if !bWhile.IsNil() && bWhile.IsBlock() {
|
|
blk := bWhile.AsBlock()
|
|
t.PendingParams2(0)
|
|
blk.Fn(t)
|
|
if !t.GetRetValue().AsBool() {
|
|
break
|
|
}
|
|
}
|
|
|
|
// For condition
|
|
doBlock := true
|
|
if !bFor.IsNil() && bFor.IsBlock() {
|
|
blk := bFor.AsBlock()
|
|
t.PendingParams2(0)
|
|
blk.Fn(t)
|
|
doBlock = t.GetRetValue().AsBool()
|
|
}
|
|
|
|
if doBlock {
|
|
blk := block.AsBlock()
|
|
t.PendingParams2(0)
|
|
blk.Fn(t)
|
|
}
|
|
|
|
area.Skip(1)
|
|
count++
|
|
}
|
|
|
|
t.RetNil()
|
|
}
|
|
|
|
// DBUSEAREA([lNewArea], [cDriver], cName, [cAlias], [lShared], [lReadOnly]) → NIL
|
|
func rtlDbUseArea(t *hbrt.Thread) {
|
|
nParams := t.ParamCount()
|
|
t.Frame(nParams, 0)
|
|
defer t.EndProcFast()
|
|
wam := getWA(t)
|
|
if wam == nil {
|
|
t.RetNil()
|
|
return
|
|
}
|
|
cName := ""
|
|
cAlias := ""
|
|
cDriver := "DBFNTX"
|
|
if nParams >= 3 && !t.Local(3).IsNil() {
|
|
cName = t.Local(3).AsString()
|
|
}
|
|
if nParams >= 4 && !t.Local(4).IsNil() {
|
|
cAlias = t.Local(4).AsString()
|
|
}
|
|
if nParams >= 2 && !t.Local(2).IsNil() {
|
|
cDriver = t.Local(2).AsString()
|
|
}
|
|
shared := false
|
|
readOnly := false
|
|
if nParams >= 5 && !t.Local(5).IsNil() {
|
|
shared = t.Local(5).AsBool()
|
|
}
|
|
if nParams >= 6 && !t.Local(6).IsNil() {
|
|
readOnly = t.Local(6).AsBool()
|
|
}
|
|
wam.Open(cDriver, cName, cAlias, shared, readOnly)
|
|
t.RetNil()
|
|
}
|
|
|
|
// DBCLOSEAREA() → NIL
|
|
func rtlDbCloseArea(t *hbrt.Thread) {
|
|
t.Frame(0, 0)
|
|
defer t.EndProcFast()
|
|
wam := getWA(t)
|
|
if wam != nil {
|
|
wam.Close()
|
|
}
|
|
t.RetNil()
|
|
}
|
|
|
|
// DBGOTO(nRecNo) → NIL
|
|
func rtlDbGoTo(t *hbrt.Thread) {
|
|
t.Frame(1, 0)
|
|
defer t.EndProcFast()
|
|
wam := getWA(t)
|
|
if wam == nil {
|
|
t.RetNil()
|
|
return
|
|
}
|
|
area := wam.Current()
|
|
if area != nil {
|
|
area.GoTo(uint32(t.Local(1).AsLong()))
|
|
}
|
|
t.RetNil()
|
|
}
|
|
|
|
// DBSKIP([nRecords]) → NIL
|
|
func rtlDbSkip(t *hbrt.Thread) {
|
|
nParams := t.ParamCount()
|
|
t.Frame(nParams, 0)
|
|
defer t.EndProcFast()
|
|
wam := getWA(t)
|
|
if wam == nil {
|
|
t.RetNil()
|
|
return
|
|
}
|
|
area := wam.Current()
|
|
if area == nil {
|
|
t.RetNil()
|
|
return
|
|
}
|
|
n := int64(1)
|
|
if nParams >= 1 && !t.Local(1).IsNil() {
|
|
n = t.Local(1).AsLong()
|
|
}
|
|
area.Skip(n)
|
|
t.RetNil()
|
|
}
|
|
|
|
// DBGOTOP() → NIL
|
|
func rtlDbGoTop(t *hbrt.Thread) {
|
|
t.Frame(0, 0)
|
|
defer t.EndProcFast()
|
|
wam := getWA(t)
|
|
if wam == nil {
|
|
t.RetNil()
|
|
return
|
|
}
|
|
area := wam.Current()
|
|
if area != nil {
|
|
area.GoTop()
|
|
}
|
|
t.RetNil()
|
|
}
|
|
|
|
// DBGOBOTTOM() → NIL
|
|
func rtlDbGoBottom(t *hbrt.Thread) {
|
|
t.Frame(0, 0)
|
|
defer t.EndProcFast()
|
|
wam := getWA(t)
|
|
if wam == nil {
|
|
t.RetNil()
|
|
return
|
|
}
|
|
area := wam.Current()
|
|
if area != nil {
|
|
area.GoBottom()
|
|
}
|
|
t.RetNil()
|
|
}
|
|
|
|
// DBAPPEND([lUnlock]) → NIL
|
|
func rtlDbAppend(t *hbrt.Thread) {
|
|
nParams := t.ParamCount()
|
|
t.Frame(nParams, 0)
|
|
defer t.EndProcFast()
|
|
wam := getWA(t)
|
|
if wam == nil {
|
|
t.RetNil()
|
|
return
|
|
}
|
|
area := wam.Current()
|
|
if area != nil {
|
|
area.Append()
|
|
}
|
|
t.RetNil()
|
|
}
|
|
|
|
// DBDELETE() → NIL
|
|
func rtlDbDelete(t *hbrt.Thread) {
|
|
t.Frame(0, 0)
|
|
defer t.EndProcFast()
|
|
wam := getWA(t)
|
|
if wam == nil {
|
|
t.RetNil()
|
|
return
|
|
}
|
|
area := wam.Current()
|
|
if area != nil {
|
|
area.Delete()
|
|
}
|
|
t.RetNil()
|
|
}
|
|
|
|
// DBRECALL() → NIL
|
|
func rtlDbRecall(t *hbrt.Thread) {
|
|
t.Frame(0, 0)
|
|
defer t.EndProcFast()
|
|
wam := getWA(t)
|
|
if wam == nil {
|
|
t.RetNil()
|
|
return
|
|
}
|
|
area := wam.Current()
|
|
if area != nil {
|
|
area.Recall()
|
|
}
|
|
t.RetNil()
|
|
}
|
|
|
|
// DBCOMMIT() → NIL
|
|
func rtlDbCommit(t *hbrt.Thread) {
|
|
t.Frame(0, 0)
|
|
defer t.EndProcFast()
|
|
wam := getWA(t)
|
|
if wam == nil {
|
|
t.RetNil()
|
|
return
|
|
}
|
|
area := wam.Current()
|
|
if area != nil {
|
|
area.Flush()
|
|
}
|
|
t.RetNil()
|
|
}
|
|
|
|
// DBSEEK(xValue [, lSoftSeek [, lLast]]) → lFound
|
|
func rtlDbSeek(t *hbrt.Thread) {
|
|
nParams := t.ParamCount()
|
|
t.Frame(nParams, 0)
|
|
defer t.EndProcFast()
|
|
wam := getWA(t)
|
|
if wam == nil {
|
|
t.RetBool(false)
|
|
return
|
|
}
|
|
area := wam.Current()
|
|
if area == nil {
|
|
t.RetBool(false)
|
|
return
|
|
}
|
|
val := t.Local(1)
|
|
softSeek := GetSetSoftSeek() // default: check SET SOFTSEEK
|
|
findLast := false
|
|
if nParams >= 2 && !t.Local(2).IsNil() {
|
|
softSeek = t.Local(2).AsBool()
|
|
}
|
|
if nParams >= 3 && !t.Local(3).IsNil() {
|
|
findLast = t.Local(3).AsBool()
|
|
}
|
|
// Check if area implements Indexer
|
|
if idx, ok := area.(hbrdd.Indexer); ok {
|
|
found, _ := idx.Seek(val, softSeek, findLast)
|
|
t.RetBool(found)
|
|
} else {
|
|
t.RetBool(false)
|
|
}
|
|
}
|
|
|
|
// DBSELECTAREA(nArea | cAlias) → NIL
|
|
func rtlDbSelectArea(t *hbrt.Thread) {
|
|
t.Frame(1, 0)
|
|
defer t.EndProcFast()
|
|
wam := getWA(t)
|
|
if wam == nil {
|
|
t.RetNil()
|
|
return
|
|
}
|
|
v := t.Local(1)
|
|
if v.IsString() {
|
|
wam.Select(v.AsString())
|
|
} else {
|
|
wam.Select(uint16(v.AsInt()))
|
|
}
|
|
t.RetNil()
|
|
}
|
|
|
|
// DBPACK() → NIL
|
|
func rtlDbPack(t *hbrt.Thread) {
|
|
t.Frame(0, 0)
|
|
defer t.EndProcFast()
|
|
wam := getWA(t)
|
|
if wam == nil {
|
|
t.RetNil()
|
|
return
|
|
}
|
|
area := wam.Current()
|
|
if area != nil {
|
|
area.Pack()
|
|
}
|
|
t.RetNil()
|
|
}
|
|
|
|
// DBZAP() → NIL
|
|
func rtlDbZap(t *hbrt.Thread) {
|
|
t.Frame(0, 0)
|
|
defer t.EndProcFast()
|
|
wam := getWA(t)
|
|
if wam == nil {
|
|
t.RetNil()
|
|
return
|
|
}
|
|
area := wam.Current()
|
|
if area != nil {
|
|
area.Zap()
|
|
}
|
|
t.RetNil()
|
|
}
|
|
|
|
// --- LOCATE / CONTINUE ---
|
|
// Harbour: DBLOCATE(bFor, bWhile, nNext, nRec, lRest) → .T./.F.
|
|
// Searches from current position for record matching condition.
|
|
|
|
func rtlDbLocate(t *hbrt.Thread) {
|
|
nParams := t.ParamCount()
|
|
t.Frame(nParams, 0)
|
|
defer t.EndProcFast()
|
|
|
|
wam := getWA(t)
|
|
if wam == nil {
|
|
t.RetBool(false)
|
|
return
|
|
}
|
|
area := wam.Current()
|
|
if area == nil {
|
|
t.RetBool(false)
|
|
return
|
|
}
|
|
|
|
// param 1: bFor condition block
|
|
var bFor hbrt.Value
|
|
if nParams >= 1 && !t.Local(1).IsNil() {
|
|
bFor = t.Local(1)
|
|
}
|
|
// param 2: bWhile condition block
|
|
var bWhile hbrt.Value
|
|
if nParams >= 2 && !t.Local(2).IsNil() {
|
|
bWhile = t.Local(2)
|
|
}
|
|
// param 3: nNext — max records to scan
|
|
nNext := int64(0) // 0 = all
|
|
if nParams >= 3 && !t.Local(3).IsNil() {
|
|
nNext = t.Local(3).AsNumInt()
|
|
}
|
|
// param 4: nRec — specific record number
|
|
if nParams >= 4 && !t.Local(4).IsNil() {
|
|
nRec := uint32(t.Local(4).AsNumInt())
|
|
area.GoTo(nRec)
|
|
if bFor.IsBlock() {
|
|
t.PendingParams2(0)
|
|
bFor.AsBlock().Fn(t)
|
|
result := t.Pop2()
|
|
area.SetFound(result.AsBool())
|
|
} else {
|
|
area.SetFound(true)
|
|
}
|
|
t.RetBool(area.Found())
|
|
return
|
|
}
|
|
// param 5: lRest — .T. = continue from current, .F. = from top
|
|
lRest := false
|
|
if nParams >= 5 && !t.Local(5).IsNil() {
|
|
lRest = t.Local(5).AsBool()
|
|
}
|
|
|
|
if !lRest && nNext == 0 {
|
|
area.GoTop()
|
|
}
|
|
|
|
// Store locate block for __dbContinue
|
|
if bFor.IsBlock() {
|
|
area.SetLocate("", func(lt *hbrt.Thread) bool {
|
|
lt.PendingParams2(0)
|
|
bFor.AsBlock().Fn(lt)
|
|
return lt.Pop2().AsBool()
|
|
})
|
|
}
|
|
|
|
found := false
|
|
count := int64(0)
|
|
for !area.EOF() {
|
|
if nNext > 0 && count >= nNext {
|
|
break
|
|
}
|
|
// Check WHILE condition
|
|
if bWhile.IsBlock() {
|
|
t.PendingParams2(0)
|
|
bWhile.AsBlock().Fn(t)
|
|
if !t.Pop2().AsBool() {
|
|
break
|
|
}
|
|
}
|
|
// Check FOR condition
|
|
if bFor.IsBlock() {
|
|
t.PendingParams2(0)
|
|
bFor.AsBlock().Fn(t)
|
|
if t.Pop2().AsBool() {
|
|
found = true
|
|
break
|
|
}
|
|
} else {
|
|
found = true
|
|
break
|
|
}
|
|
area.Skip(1)
|
|
count++
|
|
}
|
|
area.SetFound(found)
|
|
t.RetBool(found)
|
|
}
|
|
|
|
// __DBCONTINUE — continue previous LOCATE search.
|
|
// Harbour: __dbContinue()
|
|
func rtlDbContinue(t *hbrt.Thread) {
|
|
t.Frame(0, 0)
|
|
defer t.EndProcFast()
|
|
|
|
wam := getWA(t)
|
|
if wam == nil {
|
|
t.RetBool(false)
|
|
return
|
|
}
|
|
area := wam.Current()
|
|
if area == nil {
|
|
t.RetBool(false)
|
|
return
|
|
}
|
|
|
|
blk := area.LocateBlock()
|
|
if blk == nil {
|
|
area.SetFound(false)
|
|
t.RetBool(false)
|
|
return
|
|
}
|
|
|
|
// Skip past current record
|
|
area.Skip(1)
|
|
|
|
found := false
|
|
for !area.EOF() {
|
|
if blk(t) {
|
|
found = true
|
|
break
|
|
}
|
|
area.Skip(1)
|
|
}
|
|
area.SetFound(found)
|
|
t.RetBool(found)
|
|
}
|
|
|
|
// --- DBSETFILTER / DBCLEARFILTER / DBFILTER ---
|
|
|
|
// DBSETFILTER(bCondition [, cCondition])
|
|
func rtlDbSetFilter(t *hbrt.Thread) {
|
|
nParams := t.ParamCount()
|
|
t.Frame(nParams, 0)
|
|
defer t.EndProcFast()
|
|
|
|
wam := getWA(t)
|
|
if wam == nil {
|
|
t.RetNil()
|
|
return
|
|
}
|
|
area := wam.Current()
|
|
if area == nil {
|
|
t.RetNil()
|
|
return
|
|
}
|
|
|
|
if nParams >= 1 && t.Local(1).IsBlock() {
|
|
bFilter := t.Local(1)
|
|
expr := ""
|
|
if nParams >= 2 && t.Local(2).IsString() {
|
|
expr = t.Local(2).AsString()
|
|
}
|
|
area.SetFilter(expr, func(lt *hbrt.Thread) bool {
|
|
lt.PendingParams2(0)
|
|
bFilter.AsBlock().Fn(lt)
|
|
return lt.Pop2().AsBool()
|
|
})
|
|
}
|
|
t.RetNil()
|
|
}
|
|
|
|
// DBCLEARFILTER()
|
|
func rtlDbClearFilter(t *hbrt.Thread) {
|
|
t.Frame(0, 0)
|
|
defer t.EndProcFast()
|
|
|
|
wam := getWA(t)
|
|
if wam == nil {
|
|
t.RetNil()
|
|
return
|
|
}
|
|
if area := wam.Current(); area != nil {
|
|
area.ClearFilter()
|
|
}
|
|
t.RetNil()
|
|
}
|
|
|
|
// DBFILTER() → cFilterExpression
|
|
func rtlDbFilter(t *hbrt.Thread) {
|
|
t.Frame(0, 0)
|
|
defer t.EndProcFast()
|
|
|
|
// TODO: return stored filter expression from area
|
|
t.RetString("")
|
|
}
|