Files
five/hbrtl/database.go
CharlesKWON e961660f61 feat(pp): COPY TO via std.ch + four PP completeness fixes
`COPY TO <file> [FIELDS <list>] [FOR ...] [WHILE ...] [NEXT ...]
[RECORD ...] [REST] [ALL]` reaches the parser as a plain function
call to a new RTL primitive __dbCopy (rtlDbCopy in hbrtl/database.go).

Implementation: project the field list (case-insensitive name match
against the source's structure, full copy when omitted), dbCreate the
target file with that struct, open it under a temp alias, walk the
source under dbEval-style FOR/WHILE/NEXT/RECORD/REST bounds, and
GetValue/Append/PutValue per record into the target. SDF / DELIMITED
variants stay parser no-ops until those backends arrive.

Wiring up COPY surfaced four longstanding gaps in the PP that had to
be fixed for the rule to even reach the runtime:

  * `<(name)>` *pattern* marker was treated as a regular `<name>`
    with the parens baked into the captured key, so the matching
    result substitution `<(name)>` couldn't find it. parseOneMarker
    now strips the parens at parse time so capture key and result
    marker share the bare name. The smart-stringify result behavior
    is unchanged.
  * matchSegment (the optional-clause matcher) bailed on every
    non-Regular marker. `[FIELDS <fields,...>]` therefore failed to
    match at all and the fields list arrived empty in the result
    template. matchSegment now handles MarkerList with paren-balanced
    capture and segment+outer literal stop boundaries.
  * captureExpression only used the first literal in the pattern
    tail as a stop boundary. With std.ch's chain of optional
    clauses (`[TO <(f)>] [FIELDS ...] [FOR ...] [WHILE ...] ...`)
    the file-name marker was happy to gobble a trailing FOR clause
    when FIELDS was absent. It now stops at *any* of the remaining
    pattern literals.
  * `<(name)>` smart-stringify on a list-typed capture wrapped the
    whole comma-joined string in one set of quotes — `{ "a , b" }` —
    instead of `{ "a", "b" }`. New helper quoteListElements splits on
    top-level commas (paren / bracket / brace / string-balanced) and
    quotes each element. applyResult now consults the rule's marker
    table to know which captures came from `<name,...>`.

Parser cleanup: COPY removed from the IDENT-statement no-op switch in
both parseIdentStmt and parseExprStmt.

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>
2026-04-30 15:00:18 +09:00

992 lines
19 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 (
"strings"
"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)
}
// rtlDbCopy implements __dbCopy(cFile, aFields, bFor, bWhile, nNext,
// xRec, lRest) — copy visible records from the current workarea into a
// freshly created DBF. Field projection: an empty/missing aFields
// copies the whole structure; otherwise only fields whose names match
// (case-insensitive) are carried over. Used by `COPY TO <f> [FIELDS]
// [FOR] [WHILE] [NEXT] [RECORD] [REST] [ALL]` in std.ch.
//
// Harbour's __dbCopy also accepts cRDD / nConnection / cCodepage / xDelim
// (params 8..11). Five only supports DBFNTX→DBFNTX for now; SDF/DELIMITED
// copies stay parser no-ops until that backend lands.
func rtlDbCopy(t *hbrt.Thread) {
nParams := t.ParamCount()
t.Frame(nParams, 0)
defer t.EndProcFast()
wam := getWA(t)
if wam == nil {
t.RetBool(false)
return
}
srcArea := wam.Current()
if srcArea == nil {
t.RetBool(false)
return
}
if nParams < 1 || t.Local(1).IsNil() {
t.RetBool(false)
return
}
cFile := t.Local(1).AsString()
if cFile == "" {
t.RetBool(false)
return
}
// Field projection. Harbour passes `{ <(fields)> }` so each entry
// is a string literal already; uppercase for case-insensitive
// matching against the source's field names.
var srcIdx []int
var dstFields []hbrdd.FieldInfo
nSrcFields := srcArea.FieldCount()
useAll := true
if nParams >= 2 && t.Local(2).IsArray() {
arr := t.Local(2).AsArray()
if arr != nil && len(arr.Items) > 0 {
useAll = false
wanted := make(map[string]struct{}, len(arr.Items))
for _, it := range arr.Items {
s := strings.ToUpper(strings.TrimSpace(it.AsString()))
if s != "" {
wanted[s] = struct{}{}
}
}
for i := 0; i < nSrcFields; i++ {
fi := srcArea.GetFieldInfo(i)
if _, ok := wanted[strings.ToUpper(fi.Name)]; ok {
srcIdx = append(srcIdx, i)
dstFields = append(dstFields, fi)
}
}
}
}
if useAll {
srcIdx = make([]int, nSrcFields)
dstFields = make([]hbrdd.FieldInfo, nSrcFields)
for i := 0; i < nSrcFields; i++ {
srcIdx[i] = i
dstFields[i] = srcArea.GetFieldInfo(i)
}
}
if len(dstFields) == 0 {
// Nothing to copy — empty FIELDS list with no matches.
t.RetBool(false)
return
}
// Loop bounds — same shape as dbEval.
var bFor, bWhile hbrt.Value
if nParams >= 3 {
bFor = t.Local(3)
}
if nParams >= 4 {
bWhile = t.Local(4)
}
nCount := -1
if nParams >= 5 && !t.Local(5).IsNil() {
nCount = t.Local(5).AsInt()
}
if nParams >= 6 && !t.Local(6).IsNil() {
srcArea.GoTo(uint32(t.Local(6).AsInt()))
}
lRest := false
if nParams >= 7 && !t.Local(7).IsNil() {
lRest = t.Local(7).AsBool()
}
if !lRest && (nParams < 6 || t.Local(6).IsNil()) {
srcArea.GoTop()
}
// Create + open the destination. Use a temp alias so we don't
// clash with whatever the caller may have open under a name
// matching the file's basename.
drv, err := hbrdd.GetDriver("DBFNTX")
if err != nil {
t.RetBool(false)
return
}
if _, err := drv.Create(hbrdd.CreateParams{Path: cFile, Fields: dstFields}); err != nil {
t.RetBool(false)
return
}
srcSel := wam.CurrentNum()
dstSel, err := wam.Open("DBFNTX", cFile, "__copytmp", false, false)
if err != nil {
t.RetBool(false)
return
}
dstArea := wam.AreaAt(dstSel)
wam.SelectByNum(srcSel)
scanned := 0
for !srcArea.EOF() {
if nCount >= 0 && scanned >= nCount {
break
}
if bWhile.IsBlock() {
t.PendingParams2(0)
bWhile.AsBlock().Fn(t)
if !t.GetRetValue().AsBool() {
break
}
}
emit := true
if bFor.IsBlock() {
t.PendingParams2(0)
bFor.AsBlock().Fn(t)
emit = t.GetRetValue().AsBool()
}
if emit {
vals := make([]hbrt.Value, len(srcIdx))
for i, idx := range srcIdx {
v, _ := srcArea.GetValue(idx)
vals[i] = v
}
wam.SelectByNum(dstSel)
dstArea.Append()
for i, v := range vals {
dstArea.PutValue(i, v)
}
wam.SelectByNum(srcSel)
}
srcArea.Skip(1)
scanned++
}
// Close the destination, leaving the source selected as on entry.
wam.SelectByNum(dstSel)
wam.Close()
wam.SelectByNum(srcSel)
t.RetBool(true)
}
// --- 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("")
}