Three xBase analytical commands that were silent no-ops in the
parser now execute as Harbour-style PP rewrites:
COUNT [TO <v>] [FOR <for>] [WHILE <while>] ... -> dbEval()
SUM <x> TO <v> [FOR <for>] [WHILE <while>] ... -> dbEval()
AVERAGE <x> TO <v> [FOR ...] -> __dbAverage()
COUNT and SUM expand to a `<v> := 0 ; dbEval( {|| ... } )` pair
matching harbour-core/include/std.ch verbatim. AVERAGE delegates to
a new RTL function rtlDbAverage (sum + count + divide; returns 0 on
empty match) — the chained-private-variable trick Harbour uses to
keep AVERAGE inline doesn't translate cleanly through Five's PP.
Wiring up these rules surfaced four PP issues that had to be fixed
for the rewrite to even reach the parser:
* Result template did not implement <{name}> blockify. So a rule
body like `{|| x := x + <x> }, <{for}>` left the literal text
`<{for}>` in the output. Added blockify substitution: captured
-> `{|| <captured> }`, missing -> NIL.
* findMarkerEnd did not recognise `{`/`}` so unreferenced
blockify markers were not cleaned up either. Added `{`/`}` to
its prefix/suffix sets.
* Optional-clause matching had no view of the outer pattern, so a
regular marker at the end of `[TO <v>]` would swallow the rest
of the line — `COUNT TO n FOR x>5` captured `<v>` as
"n FOR x>5". matchSegment now takes outerTail and stops at its
first literal.
* `#command` directives could not span multiple physical lines.
A trailing `;` is harbour-core's line-continuation marker for
std.ch and now joins the next line into the directive before
parsing.
Parser cleanup: COUNT, SUM, AVERAGE removed from the IDENT-statement
no-op switch in parseIdentStmt + parseExprStmt. The remaining xBase
verbs (COPY, SORT, TOTAL, JOIN, LIST, DISPLAY, LABEL, REPORT, ...)
stay in the parser until their RTL backends arrive.
Gates green:
go test ./... : PASS
FiveSql2 SQL:1999 : 43/43
Harbour compat : 56/56
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
827 lines
15 KiB
Go
827 lines
15 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"
|
|
"five/hbrdd/dbf"
|
|
)
|
|
|
|
// 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()
|
|
}
|
|
_, err := wam.Open(cDriver, cName, cAlias, shared, readOnly)
|
|
if err != nil {
|
|
panic(&hbrt.HbError{
|
|
Description: err.Error(),
|
|
Operation: "DBUSEAREA",
|
|
SubSystem: "BASE",
|
|
})
|
|
}
|
|
t.RetNil()
|
|
}
|
|
|
|
// DBCLOSEALL() → NIL — closes all open work areas
|
|
func rtlDbCloseAll(t *hbrt.Thread) {
|
|
t.Frame(0, 0)
|
|
defer t.EndProcFast()
|
|
wam := getWA(t)
|
|
if wam != nil {
|
|
wam.CloseAll()
|
|
}
|
|
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()
|
|
}
|
|
|
|
// DBRLOCK([nRecNo]) → lSuccess
|
|
// Acquires an advisory byte-range lock on a single record via fcntl(F_SETLK).
|
|
// Non-blocking: returns .F. if another process already holds the lock.
|
|
// Harbour: SELF_LOCK(a, &lockInfo) with DBLM_EXCLUSIVE.
|
|
func rtlDbRLock(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
|
|
}
|
|
da, ok := area.(*dbf.DBFArea)
|
|
if !ok {
|
|
// Non-DBF drivers: assume success (in-memory, etc.)
|
|
t.RetBool(true)
|
|
return
|
|
}
|
|
var recNo uint32
|
|
if nParams >= 1 && !t.Local(1).IsNil() {
|
|
recNo = uint32(t.Local(1).AsNumInt())
|
|
}
|
|
locked, err := da.LockRecord(recNo)
|
|
if err != nil {
|
|
t.RetBool(false)
|
|
return
|
|
}
|
|
t.RetBool(locked)
|
|
}
|
|
|
|
// DBRUNLOCK([nRecNo]) → NIL
|
|
// Releases a specific record lock, or all record locks held by this
|
|
// workarea if called without arguments. Harbour: SELF_UNLOCK.
|
|
func rtlDbRUnlock(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
|
|
}
|
|
da, ok := area.(*dbf.DBFArea)
|
|
if !ok {
|
|
t.RetNil()
|
|
return
|
|
}
|
|
var recNo uint32
|
|
if nParams >= 1 && !t.Local(1).IsNil() {
|
|
recNo = uint32(t.Local(1).AsNumInt())
|
|
}
|
|
_ = da.UnlockRecord(recNo)
|
|
t.RetNil()
|
|
}
|
|
|
|
// FLOCK() → lSuccess
|
|
// Acquires an exclusive file-wide lock via fcntl. Non-blocking: returns .F.
|
|
// if another process holds the file lock. Harbour: SELF_LOCK with DBLM_FILE.
|
|
func rtlFLock(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
|
|
}
|
|
da, ok := area.(*dbf.DBFArea)
|
|
if !ok {
|
|
t.RetBool(true) // in-memory etc.
|
|
return
|
|
}
|
|
locked, err := da.LockFile()
|
|
if err != nil {
|
|
t.RetBool(false)
|
|
return
|
|
}
|
|
t.RetBool(locked)
|
|
}
|
|
|
|
// DBUNLOCK() → NIL
|
|
// Releases all locks (file + record) held by the current workarea.
|
|
// Harbour: SELF_UNLOCK with no record number.
|
|
func rtlDbUnlock(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 {
|
|
t.RetNil()
|
|
return
|
|
}
|
|
if da, ok := area.(*dbf.DBFArea); ok {
|
|
_ = da.UnlockRecord(0)
|
|
_ = da.UnlockFile()
|
|
}
|
|
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)
|
|
}
|
|
|
|
// rtlDbAverage implements __dbAverage(bExpr, bFor, bWhile, nNext, nRec,
|
|
// lRest) — sum the expression over visible records and return the
|
|
// arithmetic mean. Returns 0 when the loop visits no records (mirrors
|
|
// Harbour's idiom of avoiding a divide-by-zero in the expansion).
|
|
//
|
|
// Used by `AVERAGE <x> TO <v>` in std.ch.
|
|
func rtlDbAverage(t *hbrt.Thread) {
|
|
nParams := t.ParamCount()
|
|
t.Frame(nParams, 0)
|
|
defer t.EndProcFast()
|
|
|
|
wam := getWA(t)
|
|
if wam == nil {
|
|
t.RetDouble(0, 10, 2)
|
|
return
|
|
}
|
|
area := wam.Current()
|
|
if area == nil {
|
|
t.RetDouble(0, 10, 2)
|
|
return
|
|
}
|
|
|
|
bExpr := t.Local(1)
|
|
if !bExpr.IsBlock() {
|
|
t.RetDouble(0, 10, 2)
|
|
return
|
|
}
|
|
var bFor, bWhile hbrt.Value
|
|
if nParams >= 2 {
|
|
bFor = t.Local(2)
|
|
}
|
|
if nParams >= 3 {
|
|
bWhile = t.Local(3)
|
|
}
|
|
nCount := -1
|
|
if nParams >= 4 && !t.Local(4).IsNil() {
|
|
nCount = t.Local(4).AsInt()
|
|
}
|
|
if nParams >= 5 && !t.Local(5).IsNil() {
|
|
area.GoTo(uint32(t.Local(5).AsInt()))
|
|
}
|
|
lRest := false
|
|
if nParams >= 6 && !t.Local(6).IsNil() {
|
|
lRest = t.Local(6).AsBool()
|
|
}
|
|
if !lRest && (nParams < 5 || t.Local(5).IsNil()) {
|
|
area.GoTop()
|
|
}
|
|
|
|
sum := 0.0
|
|
n := 0
|
|
scanned := 0
|
|
for !area.EOF() {
|
|
if nCount >= 0 && scanned >= nCount {
|
|
break
|
|
}
|
|
// WHILE
|
|
if bWhile.IsBlock() {
|
|
t.PendingParams2(0)
|
|
bWhile.AsBlock().Fn(t)
|
|
if !t.GetRetValue().AsBool() {
|
|
break
|
|
}
|
|
}
|
|
// FOR
|
|
eval := true
|
|
if bFor.IsBlock() {
|
|
t.PendingParams2(0)
|
|
bFor.AsBlock().Fn(t)
|
|
eval = t.GetRetValue().AsBool()
|
|
}
|
|
if eval {
|
|
t.PendingParams2(0)
|
|
bExpr.AsBlock().Fn(t)
|
|
sum += t.GetRetValue().AsNumDouble()
|
|
n++
|
|
}
|
|
area.Skip(1)
|
|
scanned++
|
|
}
|
|
if n == 0 {
|
|
t.RetDouble(0, 10, 2)
|
|
return
|
|
}
|
|
t.RetDouble(sum/float64(n), 10, 2)
|
|
}
|
|
|
|
// --- 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("")
|
|
}
|