feat: Harbour RDD parity — NTX/CDX 100% compatible, FIELD-> works
Five RDD engine now matches Harbour DBFNTX and DBFCDX byte-for-byte
in ordering, seek, navigation, and field access. Verified against
Harbour 3.2.0dev with a 281-line comparison test covering:
- Natural/NAME/CITY/AGE/SALARY/UPPER ordering
- SEEK (exact/not-found), GoTop/GoBottom per order
- DELETE/RECALL with SET DELETED
- CDX compound index read with 5 tags (BYNAME, BYCITY, BYAGE, BYSAL, BYUNAME)
- Reverse traversal
Fixes:
1. FIELD->NAME returned NIL
GetAliasField returned interface{} but runtime expected hbrt.Value,
so the type assertion in PushAliasField failed and pushed NIL.
- workarea.go: change return type to hbrt.Value, handle FIELD/_FIELD
as current-workarea alias, add SetAliasField
- gengo.go: emit SetAliasField() for alias->field := value in both
statement and expression contexts
2. OrdSetFocus(n) silently switched to natural order
v.AsString() returns "" for a numeric Value, so OrderListFocus("")
set current=-1.
- indexrtl.go: convert numeric param via fmt.Sprintf("%d", ...)
3. CDX compound tag order mismatched Harbour
Five decoded the structural B-tree which is alphabetical, but
Harbour sorts tags by TagBlock (file offset = creation order).
- cdx/cdx.go: sort tagEntries by offset ascending after decoding,
matching hb_cdxIndexLoadAvailTags in dbfcdx1.c
4. OutStd()/OutErr() not registered — caused panic on call
- hbrtl/console.go: add rtlOutStd/rtlOutErr implementations
- hbrtl/register.go: register OUTSTD and OUTERR
- analyzer.go: add OUTSTD/OUTERR to RTL known-functions
5. FIELD keyword triggered "undeclared variable" warnings
- analyzer.go: add FIELD, _FIELD, M, MEMVAR as builtin constants
Tests:
go test ./... — ALL PASS (17 packages)
FiveSql2 43/43 — 100%
compat_harbour 51/51 — 100%
Harbour diff — 0 lines differ (281-line comparison)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -467,7 +467,7 @@ var rtlFunctions = map[string]bool{
|
||||
"TYPE": true, "PCOUNT": true, "BREAK": true, "ARRAY": true, "FCOUNT": true,
|
||||
"FIELDNAME": true, "SELECT": true, "FILE": true, "INKEY": true, "TRANSFORM": true,
|
||||
"SETDATEFORMAT": true, "SETEPOCH": true, "SETCENTURY": true,
|
||||
"IIF": true, "IF": true, "STRZERO": true, "OUTSTD": true,
|
||||
"IIF": true, "IF": true, "STRZERO": true, "OUTSTD": true, "OUTERR": true,
|
||||
"CENTER": true, "SOUNDEX": true, "TONE": true,
|
||||
// Terminal
|
||||
"SETPOS": true, "ROW": true, "COL": true, "DEVPOS": true, "DEVOUT": true,
|
||||
@@ -634,6 +634,8 @@ func (a *Analyzer) isBuiltinConstant(name string) bool {
|
||||
"SELF": true, "SUPER": true,
|
||||
// Harbour commands treated as identifiers
|
||||
"QUIT": true, "ERRORLEVEL": true,
|
||||
// Field/Memvar alias prefixes
|
||||
"FIELD": true, "_FIELD": true, "M": true, "MEMVAR": true,
|
||||
// Keyboard constants
|
||||
"K_ESC": true, "K_ENTER": true, "K_UP": true, "K_DOWN": true,
|
||||
"K_LEFT": true, "K_RIGHT": true, "K_PGUP": true, "K_PGDN": true,
|
||||
|
||||
@@ -852,6 +852,17 @@ func (g *Generator) emitAssign(a *ast.AssignExpr, locals localMap) {
|
||||
}
|
||||
}
|
||||
|
||||
// Check for alias->field := value (FIELD->NAME := value)
|
||||
if aliasExpr, ok := a.Left.(*ast.AliasExpr); ok {
|
||||
if aliasIdent, ok2 := aliasExpr.Alias.(*ast.IdentExpr); ok2 {
|
||||
if fieldIdent, ok3 := aliasExpr.Field.(*ast.IdentExpr); ok3 {
|
||||
g.emitExpr(a.Right)
|
||||
g.writeln(fmt.Sprintf(`{ _wa := t.WA.(*hbrdd.WorkAreaManager); _wa.SetAliasField(%q, %q, t.Pop2()) }`, aliasIdent.Name, fieldIdent.Name))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ident, ok := a.Left.(*ast.IdentExpr); ok {
|
||||
if idx, found := locals[strings.ToUpper(ident.Name)]; found {
|
||||
switch a.Op {
|
||||
@@ -1749,6 +1760,17 @@ func (g *Generator) emitCall(e *ast.CallExpr) {
|
||||
|
||||
// emitAssignExpr handles := / += / -= in expression context (e.g. code block body).
|
||||
func (g *Generator) emitAssignExpr(e *ast.AssignExpr) {
|
||||
// alias->field := value in expression context
|
||||
if aliasExpr, ok := e.Left.(*ast.AliasExpr); ok {
|
||||
if aliasIdent, ok2 := aliasExpr.Alias.(*ast.IdentExpr); ok2 {
|
||||
if fieldIdent, ok3 := aliasExpr.Field.(*ast.IdentExpr); ok3 {
|
||||
g.emitExpr(e.Right)
|
||||
g.writeln("t.Dup()")
|
||||
g.writeln(fmt.Sprintf(`{ _wa := t.WA.(*hbrdd.WorkAreaManager); _wa.SetAliasField(%q, %q, t.Pop2()) }`, aliasIdent.Name, fieldIdent.Name))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
if ident, ok := e.Left.(*ast.IdentExpr); ok {
|
||||
if idx, found := g.curLocals[strings.ToUpper(ident.Name)]; found {
|
||||
switch e.Op {
|
||||
|
||||
Reference in New Issue
Block a user