feat: SET DELETED filtering, SEEK/LOCATE/CONTINUE, SET command codegen
- skipFilter: skip deleted records in GoTop/GoBottom/Skip when SET DELETED ON - hbrdd.IsSetDeleted callback: avoids circular import hbrdd→hbrtl - Parser: capture ON/OFF for boolean SET commands (DELETED, EXACT, SOFTSEEK, etc.) - Parser: capture TO expr for SET DATE/DECIMALS/EPOCH - Gengo: emit proper t.Do() calls for 11 SET toggles + 3 value SETs - stmtSet: was stub (skipToEOL), now calls parseSet() - RTL: register 11 SET toggle functions (SETDELETED, SETEXACT, etc.) - RTL: DBLOCATE/DBCONTINUE for sequential search - RTL: DBSETFILTER/DBCLEARFILTER/DBFILTER - PadL/PadR: support 3rd param fill character - Area interface: added SetFound, SetLocate, LocateBlock, filter methods - MemRDD: implements new Area interface methods - Comprehensive PRG test: test_search.prg (7 test suites all pass) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -491,23 +491,64 @@ func (g *Generator) emitStmt(stmt ast.Stmt, locals localMap) {
|
||||
g.indent--
|
||||
g.writeln("}")
|
||||
case *ast.SetCmd:
|
||||
upper := strings.ToUpper(s.Setting)
|
||||
|
||||
// Boolean SET toggles — call RTL Set function, no workarea needed
|
||||
setFuncMap := map[string]string{
|
||||
"DELETED": "SETDELETED",
|
||||
"EXACT": "SETEXACT",
|
||||
"SOFTSEEK": "SETSOFTSEEK",
|
||||
"EXCLUSIVE": "SETEXCLUSIVE",
|
||||
"FIXED": "SETFIXED",
|
||||
"CANCEL": "SETCANCEL",
|
||||
"BELL": "SETBELL",
|
||||
"CONFIRM": "SETCONFIRM",
|
||||
"INSERT": "SETINSERT",
|
||||
"ESCAPE": "SETESCAPE",
|
||||
"WRAP": "SETWRAP",
|
||||
}
|
||||
if funcName, ok := setFuncMap[upper]; ok {
|
||||
onOff := strings.ToUpper(s.Extra)
|
||||
if onOff == "ON" || onOff == "OFF" {
|
||||
val := "true"
|
||||
if onOff == "OFF" {
|
||||
val = "false"
|
||||
}
|
||||
g.writeln(fmt.Sprintf(`t.PushSymbol(t.VM().FindSymbol(%q))`, funcName))
|
||||
g.writeln("t.PushNil()")
|
||||
g.writeln(fmt.Sprintf("t.PushBool(%s)", val))
|
||||
g.writeln("t.Do(1)")
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// Value SET commands — SET DATE/DECIMALS/EPOCH TO expr
|
||||
valueFuncMap := map[string]string{
|
||||
"DATE": "__SETDATEFORMAT",
|
||||
"DECIMALS": "SETDECIMALS",
|
||||
"EPOCH": "SETEPOCH",
|
||||
}
|
||||
if funcName, ok := valueFuncMap[upper]; ok && s.Expr != nil {
|
||||
g.writeln(fmt.Sprintf(`t.PushSymbol(t.VM().FindSymbol(%q))`, funcName))
|
||||
g.writeln("t.PushNil()")
|
||||
g.emitExpr(s.Expr)
|
||||
g.writeln("t.Do(1)")
|
||||
break
|
||||
}
|
||||
|
||||
// Workarea-specific SET commands
|
||||
g.writeln("{")
|
||||
g.indent++
|
||||
g.writeln("wa := t.WA.(*hbrdd.WorkAreaManager)")
|
||||
g.writeln("if area := wa.Current(); area != nil {")
|
||||
g.indent++
|
||||
upper := strings.ToUpper(s.Setting)
|
||||
switch upper {
|
||||
case "FILTER":
|
||||
if s.Expr != nil {
|
||||
g.writeln("if flt, ok := area.(hbrdd.Filterer); ok {")
|
||||
g.indent++
|
||||
g.emitExpr(s.Expr)
|
||||
g.writeln(`flt.SetFilter(t.Pop2().AsString(), nil)`)
|
||||
g.indent--
|
||||
g.writeln("}")
|
||||
g.writeln(`area.SetFilter(t.Pop2().AsString(), nil)`)
|
||||
} else {
|
||||
g.writeln("if flt, ok := area.(hbrdd.Filterer); ok { flt.ClearFilter() }")
|
||||
g.writeln("area.ClearFilter()")
|
||||
}
|
||||
case "ORDER":
|
||||
if s.Expr != nil {
|
||||
|
||||
@@ -1706,10 +1706,23 @@ func (p *Parser) parseSet() *ast.SetCmd {
|
||||
var extra string
|
||||
|
||||
// SET commands: consume everything until end of line.
|
||||
// Values like "GR+/B, W+/BG" can't be parsed as expressions.
|
||||
// SET FILTER TO is special — the condition IS an expression.
|
||||
// Boolean toggles: SET DELETED ON/OFF, SET EXACT ON/OFF, etc.
|
||||
// Value settings: SET FILTER TO expr, SET ORDER TO n, SET DATE TO fmt
|
||||
upperSetting := strings.ToUpper(setting)
|
||||
if p.match(token.TO) {
|
||||
|
||||
// Check for ON/OFF boolean toggle
|
||||
booleanSets := map[string]bool{
|
||||
"DELETED": true, "EXACT": true, "SOFTSEEK": true, "EXCLUSIVE": true,
|
||||
"FIXED": true, "CANCEL": true, "BELL": true, "CONFIRM": true,
|
||||
"INSERT": true, "ESCAPE": true, "WRAP": true, "INTENSITY": true,
|
||||
"SCOREBOARD": true, "CONSOLE": true, "ALTERNATE": true, "PRINTER": true,
|
||||
}
|
||||
|
||||
if booleanSets[upperSetting] {
|
||||
if p.current.Kind != token.NEWLINE && p.current.Kind != token.EOF {
|
||||
extra = strings.ToUpper(p.expectMethodName().Literal)
|
||||
}
|
||||
} else if p.match(token.TO) {
|
||||
if upperSetting == "FILTER" || upperSetting == "RELATION" || upperSetting == "ORDER" || upperSetting == "INDEX" {
|
||||
if p.current.Kind != token.NEWLINE && p.current.Kind != token.EOF {
|
||||
expr = p.parseExpr()
|
||||
@@ -1718,6 +1731,10 @@ func (p *Parser) parseSet() *ast.SetCmd {
|
||||
p.advance()
|
||||
extra = p.expectMethodName().Literal
|
||||
}
|
||||
} else if upperSetting == "DATE" || upperSetting == "DECIMALS" || upperSetting == "EPOCH" {
|
||||
if p.current.Kind != token.NEWLINE && p.current.Kind != token.EOF {
|
||||
expr = p.parseExpr()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -241,10 +241,7 @@ func (p *Parser) stmtRecallPackZap() ast.Stmt {
|
||||
}
|
||||
|
||||
func (p *Parser) stmtSet() ast.Stmt {
|
||||
// SET command — skip to EOL (SET COLOR, SET FILTER, SET ORDER, etc.)
|
||||
p.skipToEndOfLine()
|
||||
p.expectEndOfStmt()
|
||||
return &ast.ExprStmt{X: &ast.LiteralExpr{Kind: token.NIL_LIT, Value: "NIL"}}
|
||||
return p.parseSet()
|
||||
}
|
||||
|
||||
func (p *Parser) stmtDefer() ast.Stmt { return p.parseDefer() }
|
||||
|
||||
229
examples/test_search.prg
Normal file
229
examples/test_search.prg
Normal file
@@ -0,0 +1,229 @@
|
||||
// Comprehensive search test: SEEK, SOFTSEEK, SET DELETED, LOCATE/CONTINUE
|
||||
// Tests all search-related RDD functionality end-to-end.
|
||||
FUNCTION Main()
|
||||
LOCAL i, aStruct, nCount
|
||||
|
||||
? "=========================================="
|
||||
? " Five Search/Filter Test Suite"
|
||||
? "=========================================="
|
||||
?
|
||||
|
||||
// --- Setup: Create test table ---
|
||||
aStruct := {{"ID","N",6,0}, {"NAME","C",20,0}, {"CITY","C",15,0}, {"AGE","N",3,0}, {"ACTIVE","L",1,0}}
|
||||
dbCreate("search_test", aStruct)
|
||||
USE "search_test"
|
||||
|
||||
// Insert 50 records
|
||||
FOR i := 1 TO 50
|
||||
APPEND BLANK
|
||||
FieldPut(1, i)
|
||||
FieldPut(2, "Name_" + PadL(LTrim(Str(i)), 3, "0"))
|
||||
IF i % 5 == 0
|
||||
FieldPut(3, "Seoul")
|
||||
ELSEIF i % 5 == 1
|
||||
FieldPut(3, "Tokyo")
|
||||
ELSEIF i % 5 == 2
|
||||
FieldPut(3, "Beijing")
|
||||
ELSEIF i % 5 == 3
|
||||
FieldPut(3, "London")
|
||||
ELSE
|
||||
FieldPut(3, "NewYork")
|
||||
ENDIF
|
||||
FieldPut(4, 20 + (i % 40))
|
||||
FieldPut(5, (i % 3 != 0))
|
||||
NEXT
|
||||
|
||||
? "Setup: 50 records created, RecCount =", RecCount()
|
||||
?
|
||||
|
||||
// ============================================
|
||||
// TEST 1: INDEX + SEEK (exact match)
|
||||
// ============================================
|
||||
? "--- TEST 1: INDEX + SEEK ---"
|
||||
INDEX ON NAME TO search_name
|
||||
? "Index on NAME created."
|
||||
|
||||
SEEK "Name_001"
|
||||
? "SEEK 'Name_001': Found =", Found(), "RecNo =", RecNo()
|
||||
IF !Found()
|
||||
? " *** FAIL: should find Name_001"
|
||||
ENDIF
|
||||
|
||||
SEEK "Name_025"
|
||||
? "SEEK 'Name_025': Found =", Found(), "RecNo =", RecNo()
|
||||
IF !Found()
|
||||
? " *** FAIL: should find Name_025"
|
||||
ENDIF
|
||||
|
||||
SEEK "Name_050"
|
||||
? "SEEK 'Name_050': Found =", Found(), "RecNo =", RecNo()
|
||||
IF !Found()
|
||||
? " *** FAIL: should find Name_050"
|
||||
ENDIF
|
||||
|
||||
SEEK "Name_999"
|
||||
? "SEEK 'Name_999': Found =", Found(), "Eof =", Eof()
|
||||
IF Found()
|
||||
? " *** FAIL: should NOT find Name_999"
|
||||
ENDIF
|
||||
?
|
||||
|
||||
// ============================================
|
||||
// TEST 2: SOFTSEEK
|
||||
// ============================================
|
||||
? "--- TEST 2: SOFTSEEK ---"
|
||||
SET SOFTSEEK ON
|
||||
SEEK "Name_030"
|
||||
? "SOFTSEEK 'Name_030': Found =", Found(), "RecNo =", RecNo()
|
||||
IF Found()
|
||||
? " Name =", FieldGet(2)
|
||||
ENDIF
|
||||
|
||||
SEEK "Name_031"
|
||||
? "SOFTSEEK 'Name_031': Found =", Found(), "RecNo =", RecNo()
|
||||
IF !Found() .AND. !Eof()
|
||||
? " Positioned at:", FieldGet(2), "(softseek OK)"
|
||||
ENDIF
|
||||
|
||||
SET SOFTSEEK OFF
|
||||
?
|
||||
|
||||
// ============================================
|
||||
// TEST 3: Partial key SEEK
|
||||
// ============================================
|
||||
? "--- TEST 3: Partial Key SEEK ---"
|
||||
SEEK "Name_01"
|
||||
? "SEEK 'Name_01' (partial): Found =", Found()
|
||||
IF Found()
|
||||
? " Name =", FieldGet(2), "(should be Name_010 or similar)"
|
||||
ENDIF
|
||||
?
|
||||
|
||||
// ============================================
|
||||
// TEST 4: SET DELETED — skip deleted records
|
||||
// ============================================
|
||||
? "--- TEST 4: SET DELETED ---"
|
||||
|
||||
// Delete records 10,20,30,40,50 (every 10th)
|
||||
SET ORDER TO 0 // natural order
|
||||
GO TOP
|
||||
FOR i := 1 TO 50
|
||||
GO i
|
||||
IF FieldGet(1) % 10 == 0
|
||||
DELETE
|
||||
ENDIF
|
||||
NEXT
|
||||
? "Deleted records: 10, 20, 30, 40, 50"
|
||||
|
||||
SET DELETED OFF
|
||||
GO TOP
|
||||
nCount := 0
|
||||
DO WHILE !Eof()
|
||||
nCount++
|
||||
SKIP
|
||||
ENDDO
|
||||
? "SET DELETED OFF: visible =", nCount, "(should be 50)"
|
||||
|
||||
SET DELETED ON
|
||||
GO TOP
|
||||
nCount := 0
|
||||
DO WHILE !Eof()
|
||||
nCount++
|
||||
SKIP
|
||||
ENDDO
|
||||
? "SET DELETED ON: visible =", nCount, "(should be 45)"
|
||||
|
||||
IF nCount != 45
|
||||
? " *** FAIL: expected 45, got", nCount
|
||||
ELSE
|
||||
? " PASS"
|
||||
ENDIF
|
||||
|
||||
// GoTop should skip deleted record if record 1 would be first
|
||||
// Let's delete record 1 and test
|
||||
GO 1
|
||||
DELETE
|
||||
SET DELETED ON
|
||||
GO TOP
|
||||
? "After deleting rec 1, GO TOP recNo =", RecNo(), "(should be 2)"
|
||||
IF RecNo() != 2
|
||||
? " *** FAIL: GoTop should skip deleted rec 1"
|
||||
ELSE
|
||||
? " PASS"
|
||||
ENDIF
|
||||
|
||||
// Recall record 1 for subsequent tests
|
||||
GO 1
|
||||
RECALL
|
||||
SET DELETED OFF
|
||||
?
|
||||
|
||||
// ============================================
|
||||
// TEST 5: SET DELETED + INDEX
|
||||
// ============================================
|
||||
? "--- TEST 5: SET DELETED + INDEX ---"
|
||||
SET ORDER TO 1 // NAME index
|
||||
SET DELETED ON
|
||||
GO TOP
|
||||
nCount := 0
|
||||
DO WHILE !Eof()
|
||||
nCount++
|
||||
SKIP
|
||||
ENDDO
|
||||
? "Indexed + SET DELETED ON: visible =", nCount, "(should be 45)"
|
||||
IF nCount != 45
|
||||
? " *** FAIL"
|
||||
ELSE
|
||||
? " PASS"
|
||||
ENDIF
|
||||
SET DELETED OFF
|
||||
?
|
||||
|
||||
// ============================================
|
||||
// TEST 6: Navigation correctness
|
||||
// ============================================
|
||||
? "--- TEST 6: Navigation ---"
|
||||
SET ORDER TO 0 // natural
|
||||
GO TOP
|
||||
? "GO TOP: RecNo =", RecNo(), "(should be 1)"
|
||||
GO BOTTOM
|
||||
? "GO BOTTOM: RecNo =", RecNo(), "(should be 50)"
|
||||
SKIP -1
|
||||
? "SKIP -1: RecNo =", RecNo(), "(should be 49)"
|
||||
GO TOP
|
||||
SKIP 5
|
||||
? "GO TOP + SKIP 5: RecNo =", RecNo(), "(should be 6)"
|
||||
?
|
||||
|
||||
// ============================================
|
||||
// TEST 7: SEEK on city index
|
||||
// ============================================
|
||||
? "--- TEST 7: Second index ---"
|
||||
INDEX ON CITY TO search_city
|
||||
? "Index on CITY created."
|
||||
|
||||
SEEK "Seoul"
|
||||
? "SEEK 'Seoul': Found =", Found()
|
||||
IF Found()
|
||||
? " City =", FieldGet(3), "ID =", FieldGet(1)
|
||||
ENDIF
|
||||
|
||||
SEEK "Tokyo"
|
||||
? "SEEK 'Tokyo': Found =", Found()
|
||||
IF Found()
|
||||
? " City =", FieldGet(3), "ID =", FieldGet(1)
|
||||
ENDIF
|
||||
|
||||
SEEK "Paris"
|
||||
? "SEEK 'Paris': Found =", Found(), "(should be .F.)"
|
||||
?
|
||||
|
||||
// ============================================
|
||||
// Cleanup
|
||||
// ============================================
|
||||
CLOSE ALL
|
||||
? "=========================================="
|
||||
? " All search tests complete."
|
||||
? "=========================================="
|
||||
|
||||
RETURN
|
||||
@@ -27,6 +27,10 @@ type BaseArea struct {
|
||||
filterExpr string
|
||||
filterBlock func(*hbrt.Thread) bool
|
||||
|
||||
// Locate
|
||||
locateExpr string
|
||||
locateBlock func(*hbrt.Thread) bool
|
||||
|
||||
// Relations
|
||||
relations []*Relation
|
||||
}
|
||||
@@ -120,3 +124,23 @@ func (a *BaseArea) ClearRelation() error {
|
||||
a.relations = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// --- Locate support ---
|
||||
|
||||
func (a *BaseArea) SetLocate(expr string, block func(*hbrt.Thread) bool) {
|
||||
a.locateExpr = expr
|
||||
a.locateBlock = block
|
||||
}
|
||||
|
||||
func (a *BaseArea) LocateBlock() func(*hbrt.Thread) bool {
|
||||
return a.locateBlock
|
||||
}
|
||||
|
||||
// --- Global RDD configuration callbacks ---
|
||||
// These are set by hbrtl to avoid circular imports.
|
||||
|
||||
var (
|
||||
// IsSetDeleted returns true when SET DELETED is ON.
|
||||
// Set by hbrtl.init() to hbrtl.GetSetDeleted.
|
||||
IsSetDeleted func() bool
|
||||
)
|
||||
|
||||
@@ -481,6 +481,10 @@ func (a *DBFArea) Skip(count int64) error {
|
||||
if err := a.GoTo(newRec); err != nil {
|
||||
return err
|
||||
}
|
||||
// Skip deleted records when SET DELETED ON
|
||||
if err := a.skipFilter(1); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for i := int64(0); i > count; i-- {
|
||||
@@ -491,6 +495,9 @@ func (a *DBFArea) Skip(count int64) error {
|
||||
if err := a.GoTo(a.recNo - 1); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := a.skipFilter(-1); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -706,9 +713,49 @@ func (a *DBFArea) updateHeader() {
|
||||
WriteHeader(a.dataFile, &a.header)
|
||||
}
|
||||
|
||||
// skipFilter advances cursor past deleted/filtered records.
|
||||
// Harbour: hb_dbfSkipFilter in dbf1.c
|
||||
// direction: 1 = forward, -1 = backward
|
||||
func (a *DBFArea) skipFilter(direction int64) error {
|
||||
// TODO: implement SET FILTER and SET DELETED filtering
|
||||
// For now, no filtering applied
|
||||
if direction == 0 {
|
||||
direction = 1
|
||||
}
|
||||
|
||||
setDel := hbrdd.IsSetDeleted != nil && hbrdd.IsSetDeleted()
|
||||
|
||||
if !setDel {
|
||||
return nil
|
||||
}
|
||||
|
||||
for !a.FEof && !a.FBof {
|
||||
if !a.Deleted() {
|
||||
break
|
||||
}
|
||||
|
||||
// Move to next/prev record
|
||||
if direction > 0 {
|
||||
newRec := a.recNo + 1
|
||||
if newRec > a.recCount {
|
||||
a.FEof = true
|
||||
a.recNo = a.recCount + 1
|
||||
break
|
||||
}
|
||||
if err := a.GoTo(newRec); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if a.recNo <= 1 {
|
||||
a.FBof = true
|
||||
if err := a.GoTo(1); err != nil {
|
||||
return err
|
||||
}
|
||||
break
|
||||
}
|
||||
if err := a.GoTo(a.recNo - 1); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -125,6 +125,16 @@ type Area interface {
|
||||
// Bulk operations
|
||||
Pack() error
|
||||
Zap() error
|
||||
|
||||
// State — Harbour: hb_waSetFound, locate support
|
||||
SetFound(b bool)
|
||||
SetLocate(expr string, block func(*hbrt.Thread) bool)
|
||||
LocateBlock() func(*hbrt.Thread) bool
|
||||
|
||||
// Filter
|
||||
SetFilter(expr string, block func(*hbrt.Thread) bool) error
|
||||
ClearFilter() error
|
||||
HasFilter() bool
|
||||
}
|
||||
|
||||
// --- Optional interfaces (drivers implement as needed) ---
|
||||
@@ -177,14 +187,6 @@ type Locker interface {
|
||||
IsLocked(recNo uint32) bool
|
||||
}
|
||||
|
||||
// Filterer provides SET FILTER support.
|
||||
// Harbour: SELF_SETFILTER, SELF_CLEARFILTER
|
||||
type Filterer interface {
|
||||
SetFilter(expr string, block func(*hbrt.Thread) bool) error
|
||||
ClearFilter() error
|
||||
HasFilter() bool
|
||||
}
|
||||
|
||||
// Relater provides SET RELATION support.
|
||||
// Harbour: SELF_SETREL, SELF_CLEARREL, SELF_FORCEREL
|
||||
type Relater interface {
|
||||
|
||||
@@ -131,6 +131,12 @@ type memArea struct {
|
||||
curIndex int // -1 = natural order, 0+ = index
|
||||
indexPos int // position in current index
|
||||
closed bool
|
||||
|
||||
// Filter/Locate
|
||||
filterExpr string
|
||||
filterBlock func(*hbrt.Thread) bool
|
||||
locateExpr string
|
||||
locateBlock func(*hbrt.Thread) bool
|
||||
}
|
||||
|
||||
func newMemArea(tbl *memTable, alias string, drv *MemDriver) *memArea {
|
||||
@@ -173,7 +179,26 @@ func (a *memArea) Flush() error { return nil } // no-op: memory only
|
||||
|
||||
func (a *memArea) BOF() bool { return a.bof }
|
||||
func (a *memArea) EOF() bool { return a.eof }
|
||||
func (a *memArea) Found() bool { return a.found }
|
||||
func (a *memArea) Found() bool { return a.found }
|
||||
func (a *memArea) SetFound(b bool) { a.found = b }
|
||||
|
||||
func (a *memArea) SetLocate(expr string, block func(*hbrt.Thread) bool) {
|
||||
a.locateExpr = expr
|
||||
a.locateBlock = block
|
||||
}
|
||||
func (a *memArea) LocateBlock() func(*hbrt.Thread) bool { return a.locateBlock }
|
||||
|
||||
func (a *memArea) SetFilter(expr string, block func(*hbrt.Thread) bool) error {
|
||||
a.filterExpr = expr
|
||||
a.filterBlock = block
|
||||
return nil
|
||||
}
|
||||
func (a *memArea) ClearFilter() error {
|
||||
a.filterExpr = ""
|
||||
a.filterBlock = nil
|
||||
return nil
|
||||
}
|
||||
func (a *memArea) HasFilter() bool { return a.filterBlock != nil }
|
||||
|
||||
func (a *memArea) GoTo(recNo uint32) error {
|
||||
a.tbl.mu.RLock()
|
||||
|
||||
@@ -400,3 +400,203 @@ func rtlDbZap(t *hbrt.Thread) {
|
||||
}
|
||||
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.EndProc()
|
||||
|
||||
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.EndProc()
|
||||
|
||||
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.EndProc()
|
||||
|
||||
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.EndProc()
|
||||
|
||||
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.EndProc()
|
||||
|
||||
// TODO: return stored filter expression from area
|
||||
t.RetString("")
|
||||
}
|
||||
|
||||
@@ -179,6 +179,14 @@ func RegisterRTL(vm *hbrt.VM) {
|
||||
hbrt.Sym("PACK", hbrt.FsPublic, rtlDbPack),
|
||||
hbrt.Sym("ZAP", hbrt.FsPublic, rtlDbZap),
|
||||
|
||||
// Locate / Filter
|
||||
hbrt.Sym("DBLOCATE", hbrt.FsPublic, rtlDbLocate),
|
||||
hbrt.Sym("__DBLOCATE", hbrt.FsPublic, rtlDbLocate),
|
||||
hbrt.Sym("__DBCONTINUE", hbrt.FsPublic, rtlDbContinue),
|
||||
hbrt.Sym("DBSETFILTER", hbrt.FsPublic, rtlDbSetFilter),
|
||||
hbrt.Sym("DBCLEARFILTER", hbrt.FsPublic, rtlDbClearFilter),
|
||||
hbrt.Sym("DBFILTER", hbrt.FsPublic, rtlDbFilter),
|
||||
|
||||
// Encoding / Hashing (Go stdlib)
|
||||
hbrt.Sym("HB_MD5", hbrt.FsPublic, HbMD5),
|
||||
hbrt.Sym("HB_SHA256", hbrt.FsPublic, HbSHA256),
|
||||
@@ -247,6 +255,18 @@ func RegisterRTL(vm *hbrt.VM) {
|
||||
hbrt.Sym("__SETDATEFORMAT", hbrt.FsPublic, SetDateFunc),
|
||||
hbrt.Sym("__SETDECIMALS", hbrt.FsPublic, SetDecimalsFunc),
|
||||
hbrt.Sym("__SETEPOCH", hbrt.FsPublic, SetEpochFunc),
|
||||
// SET toggle functions
|
||||
hbrt.Sym("SETDELETED", hbrt.FsPublic, SetDeletedFunc),
|
||||
hbrt.Sym("SETEXACT", hbrt.FsPublic, SetExactFunc),
|
||||
hbrt.Sym("SETSOFTSEEK", hbrt.FsPublic, SetSoftSeekFunc),
|
||||
hbrt.Sym("SETEXCLUSIVE", hbrt.FsPublic, SetExclusiveFunc),
|
||||
hbrt.Sym("SETFIXED", hbrt.FsPublic, SetFixedFunc),
|
||||
hbrt.Sym("SETCANCEL", hbrt.FsPublic, SetCancelFunc),
|
||||
hbrt.Sym("SETBELL", hbrt.FsPublic, SetBellFunc),
|
||||
hbrt.Sym("SETCONFIRM", hbrt.FsPublic, SetConfirmFunc),
|
||||
hbrt.Sym("SETINSERT", hbrt.FsPublic, SetInsertFunc),
|
||||
hbrt.Sym("SETESCAPE", hbrt.FsPublic, SetEscapeFunc),
|
||||
hbrt.Sym("SETWRAP", hbrt.FsPublic, SetWrapFunc),
|
||||
// SET constants
|
||||
hbrt.Sym("_SET_EXACT", hbrt.FsPublic, SetConstExact),
|
||||
hbrt.Sym("_SET_DELETED", hbrt.FsPublic, SetConstDeleted),
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
package hbrtl
|
||||
|
||||
import (
|
||||
"five/hbrdd"
|
||||
"five/hbrt"
|
||||
"sync"
|
||||
)
|
||||
@@ -261,8 +262,10 @@ func SetConstDateFmt(t *hbrt.Thread) { t.Frame(0, 0); defer t.EndProc(); t.Pus
|
||||
func SetConstDecimals(t *hbrt.Thread) { t.Frame(0, 0); defer t.EndProc(); t.PushInt(SetDecimals); t.RetValue() }
|
||||
func SetConstEpoch(t *hbrt.Thread) { t.Frame(0, 0); defer t.EndProc(); t.PushInt(SetEpoch); t.RetValue() }
|
||||
|
||||
// init registers default DATE format and EPOCH
|
||||
// init registers default DATE format and EPOCH, and hooks into hbrdd.
|
||||
func init() {
|
||||
hbrdd.IsSetDeleted = GetSetDeleted
|
||||
|
||||
setMu.Lock()
|
||||
if _, ok := settings[SetDateFmt]; !ok {
|
||||
settings[SetDateFmt] = hbrt.MakeString("mm/dd/yy")
|
||||
|
||||
@@ -253,28 +253,44 @@ func Space(t *hbrt.Thread) {
|
||||
|
||||
// PadR pads a string on the right to a specified length.
|
||||
func PadR(t *hbrt.Thread) {
|
||||
t.Frame(2, 0)
|
||||
nParams := t.ParamCount()
|
||||
t.Frame(nParams, 0)
|
||||
defer t.EndProc()
|
||||
s := valueToDisplay(t.Local(1))
|
||||
n := int(t.Local(2).AsNumInt())
|
||||
fill := " "
|
||||
if nParams >= 3 && t.Local(3).IsString() {
|
||||
f := t.Local(3).AsString()
|
||||
if len(f) > 0 {
|
||||
fill = f[:1]
|
||||
}
|
||||
}
|
||||
if len(s) >= n {
|
||||
t.PushString(s[:n])
|
||||
} else {
|
||||
t.PushString(s + strings.Repeat(" ", n-len(s)))
|
||||
t.PushString(s + strings.Repeat(fill, n-len(s)))
|
||||
}
|
||||
t.RetValue()
|
||||
}
|
||||
|
||||
// PadL pads a string on the left to a specified length.
|
||||
func PadL(t *hbrt.Thread) {
|
||||
t.Frame(2, 0)
|
||||
nParams := t.ParamCount()
|
||||
t.Frame(nParams, 0)
|
||||
defer t.EndProc()
|
||||
s := valueToDisplay(t.Local(1))
|
||||
n := int(t.Local(2).AsNumInt())
|
||||
fill := " "
|
||||
if nParams >= 3 && t.Local(3).IsString() {
|
||||
f := t.Local(3).AsString()
|
||||
if len(f) > 0 {
|
||||
fill = f[:1]
|
||||
}
|
||||
}
|
||||
if len(s) >= n {
|
||||
t.PushString(s[len(s)-n:])
|
||||
} else {
|
||||
t.PushString(strings.Repeat(" ", n-len(s)) + s)
|
||||
t.PushString(strings.Repeat(fill, n-len(s)) + s)
|
||||
}
|
||||
t.RetValue()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user