From 6e78d12cc2379aab07d1f7423ba9d8b9281f4219 Mon Sep 17 00:00:00 2001 From: Charles KWON OhJun Date: Mon, 6 Apr 2026 04:41:19 +0900 Subject: [PATCH] =?UTF-8?q?fix:=203=20RDD=20compat=20bugs=20=E2=80=94=20FI?= =?UTF-8?q?ELD->,=20AsNumInt=20Double,=20PACK/ZAP=20with=20index?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug 1: FIELD->NAME in INDEX ON expression - evalKeyExprInner: strip FIELD->/alias-> prefix before field lookup - exprToString: handle AliasExpr (FIELD->NAME → "FIELD->NAME") Bug 2: AsNumInt() on Double returned IEEE 754 raw bits - Value.AsNumInt(): check tDouble and convert via Float64frombits - Fixed array index crash when index is result of % modulo Bug 3: PACK/ZAP crash with open indexes - OrderListRebuild: fully implemented (was TODO stub) Saves index info, closes all, sets idxState=nil, recreates - OrderCreate: set current=-1 during key evaluation (natural GoTo) - PACK/ZAP: save/restore idxState, rebuild after operation - Register __DBPACK, __DBZAP, DBRECALL symbol aliases Harbour vs Five: 45/47 match (96%), 2 diffs are duplicate-key sort order Co-Authored-By: Claude Opus 4.6 (1M context) --- compiler/gengo/gengo.go | 5 ++++ examples/rdd_compat.prg | 6 ++-- hbrdd/dbf/dbf.go | 31 +++++++++++++++++++- hbrdd/dbf/indexer.go | 63 +++++++++++++++++++++++++++++++++++++++-- hbrt/value.go | 7 ++++- hbrtl/register.go | 3 ++ 6 files changed, 108 insertions(+), 7 deletions(-) diff --git a/compiler/gengo/gengo.go b/compiler/gengo/gengo.go index 4cf9c08..6c9e532 100644 --- a/compiler/gengo/gengo.go +++ b/compiler/gengo/gengo.go @@ -1253,6 +1253,11 @@ func exprToString(expr ast.Expr) string { } return ident.Name + "(" + args + ")" } + case *ast.AliasExpr: + // FIELD->NAME, M->VAR, ALIAS->FIELD + alias := exprToString(e.Alias) + field := exprToString(e.Field) + return alias + "->" + field } return "" } diff --git a/examples/rdd_compat.prg b/examples/rdd_compat.prg index 617ccc7..1c07ea1 100644 --- a/examples/rdd_compat.prg +++ b/examples/rdd_compat.prg @@ -17,7 +17,7 @@ PROCEDURE Main() APPEND BLANK REPLACE ID WITH i REPLACE NAME WITH PadR("Name_" + LTrim(Str(i)), 20) - nIdx := Int(((i-1) % 5)) + 1 + nIdx := ((i-1) % 5) + 1 REPLACE CITY WITH PadR(aCities[nIdx], 15) REPLACE SALARY WITH 30000 + i * 1000.50 REPLACE ACTIVE WITH (i % 3 != 0) @@ -122,7 +122,7 @@ PROCEDURE Main() RECALL SET DELETED OFF - INDEX ON NAME TO compat_idx1 + INDEX ON FIELD->NAME TO compat_idx1 Out("T26", "OK") GO TOP @@ -164,7 +164,7 @@ PROCEDURE Main() GO TOP Out("T39", RTrim(FieldGet(2))) - INDEX ON CITY TO compat_idx2 + INDEX ON FIELD->CITY TO compat_idx2 SEEK PadR("Seoul", 15) Out("T40", IIF(Found(), ".T.", ".F.") + " " + LTrim(Str(FieldGet(1)))) diff --git a/hbrdd/dbf/dbf.go b/hbrdd/dbf/dbf.go index 6303c61..b2d0793 100644 --- a/hbrdd/dbf/dbf.go +++ b/hbrdd/dbf/dbf.go @@ -631,6 +631,13 @@ func (a *DBFArea) Pack() error { a.flushRecord() } + // Temporarily disable index to avoid indexed navigation during PACK + var savedIdx *indexState + if a.idxState != nil { + savedIdx = a.idxState + a.idxState = nil + } + outRec := uint32(0) buf := make([]byte, a.header.RecordLen) @@ -661,7 +668,7 @@ func (a *DBFArea) Pack() error { // Update header a.updateHeader() - // Reposition + // Reposition (natural order, no index yet) if a.recCount > 0 { a.GoTo(1) } else { @@ -669,6 +676,14 @@ func (a *DBFArea) Pack() error { a.recNo = 1 } + // Rebuild indexes (record numbers changed after PACK) + if savedIdx != nil { + a.idxState = savedIdx + if err := a.OrderListRebuild(); err != nil { + return err + } + } + return nil } @@ -678,6 +693,13 @@ func (a *DBFArea) Zap() error { return fmt.Errorf("ZAP requires exclusive access") } + // Save index state + var savedIdx *indexState + if a.idxState != nil { + savedIdx = a.idxState + a.idxState = nil + } + a.recCount = 0 a.header.RecCount = 0 @@ -688,6 +710,13 @@ func (a *DBFArea) Zap() error { a.updateHeader() a.FEof = true a.recNo = 1 + + // Rebuild indexes (empty after ZAP) + if savedIdx != nil { + a.idxState = savedIdx + a.OrderListRebuild() // rebuilds empty indexes + } + return nil } diff --git a/hbrdd/dbf/indexer.go b/hbrdd/dbf/indexer.go index eef680a..3932386 100644 --- a/hbrdd/dbf/indexer.go +++ b/hbrdd/dbf/indexer.go @@ -38,6 +38,9 @@ func (a *DBFArea) ensureIndexState() { func (a *DBFArea) OrderCreate(params hbrdd.OrderCreateParams) error { a.ensureIndexState() + // Disable indexed navigation during key evaluation (GoTo must use natural order) + a.idxState.current = -1 + idxPath := params.FilePath if idxPath == "" { return fmt.Errorf("index file path required") @@ -170,8 +173,58 @@ func (a *DBFArea) OrderListFocus(tagName string) error { } // OrderListRebuild rebuilds all indexes. +// Harbour: ORDLISTREBUILD / REINDEX — recreates all open indexes from current data. func (a *DBFArea) OrderListRebuild() error { - // TODO: reindex all open indexes + if a.idxState == nil || len(a.idxState.indexes) == 0 { + return nil + } + + // Save current index info + savedCurrent := a.idxState.current + type idxInfo struct { + name string + tag string + keyExpr string + } + infos := make([]idxInfo, len(a.idxState.indexes)) + for i := range a.idxState.indexes { + infos[i] = idxInfo{ + name: a.idxState.names[i], + tag: a.idxState.tags[i], + keyExpr: a.idxState.keyExprs[i], + } + } + + // Close all indexes and disable indexed navigation + for _, idx := range a.idxState.indexes { + idx.Close() + } + a.idxState.indexes = nil + a.idxState.names = nil + a.idxState.tags = nil + a.idxState.keyExprs = nil + a.idxState.current = -1 + + // Remove idxState so GoTo uses natural order during rebuild + a.idxState = nil + + // Recreate each index + for _, info := range infos { + err := a.OrderCreate(hbrdd.OrderCreateParams{ + KeyExpr: info.keyExpr, + FilePath: info.name, + TagName: info.tag, + }) + if err != nil { + return fmt.Errorf("rebuild index %s: %w", info.name, err) + } + } + + // Restore active index + if a.idxState != nil && savedCurrent >= 0 && savedCurrent < len(a.idxState.indexes) { + a.idxState.current = savedCurrent + } + return nil } @@ -361,10 +414,16 @@ func (a *DBFArea) evalKeyExprInner(expr string) []byte { return []byte(expr[1 : len(expr)-1]) } + // Strip FIELD-> or _FIELD-> or alias-> prefix (Harbour: M->var, FIELD->var) + fieldName := upper + if idx := strings.Index(fieldName, "->"); idx >= 0 { + fieldName = fieldName[idx+2:] + } + // Simple field name for i := 0; i < a.FieldCount(); i++ { fi := a.GetFieldInfo(i) - if strings.ToUpper(fi.Name) == upper { + if strings.ToUpper(fi.Name) == fieldName { val, _ := a.GetValue(i) return formatKeyValue(val, fi) } diff --git a/hbrt/value.go b/hbrt/value.go index ef54cd5..2b4d6ea 100644 --- a/hbrt/value.go +++ b/hbrt/value.go @@ -155,7 +155,12 @@ func (v Value) AsLong() int64 { return int64(v.scalar) } func (v Value) AsDouble() float64 { return math.Float64frombits(v.scalar) } func (v Value) AsJulian() int64 { return int64(v.scalar) } func (v Value) AsTimeMs() int32 { return int32(v.info & auxMask) } -func (v Value) AsNumInt() int64 { return int64(v.scalar) } +func (v Value) AsNumInt() int64 { + if v.Type() == tDouble { + return int64(math.Float64frombits(v.scalar)) + } + return int64(v.scalar) +} // AsNumDouble returns a double value from any numeric type. func (v Value) AsNumDouble() float64 { diff --git a/hbrtl/register.go b/hbrtl/register.go index 27c9d91..fba1f17 100644 --- a/hbrtl/register.go +++ b/hbrtl/register.go @@ -178,6 +178,9 @@ func RegisterRTL(vm *hbrt.VM) { hbrt.Sym("RECALL", hbrt.FsPublic, rtlDbRecall), hbrt.Sym("PACK", hbrt.FsPublic, rtlDbPack), hbrt.Sym("ZAP", hbrt.FsPublic, rtlDbZap), + hbrt.Sym("__DBPACK", hbrt.FsPublic, rtlDbPack), + hbrt.Sym("__DBZAP", hbrt.FsPublic, rtlDbZap), + hbrt.Sym("DBRECALL", hbrt.FsPublic, rtlDbRecall), // Locate / Filter hbrt.Sym("DBLOCATE", hbrt.FsPublic, rtlDbLocate),