Major rewrite based on Harbour dbfntx1.c analysis:
NTX B-tree traversal (ntx.go):
- nextKey: rewritten to match hb_ntxTagNextKey exactly
- Advance iKey, check right child, descend via goLeftmost
- Walk up stack on page exhaustion, truncate stackLevel
- prevKey: rewritten to match hb_ntxTagPrevKey
- Check left child (only if iKey < keyCount), descend via goRightmost
- Walk up stack for BOF detection
- goRightmost: internal nodes get iKey=keyCount (rightmost child),
leaf nodes get iKey=keyCount-1 (last key) — matches Harbour
NTX B-tree build (build.go):
- CreateIndex: proper B-tree insertion (insert keys one by one)
- insertKeyBTree: search → insert at leaf → propagate splits up
- pageInsertKey: Harbour-style offset swapping (not data moving)
- pageSplit: collect all entries, split at midpoint, promote separator
- Proper offset table initialization for all pages
Unit tests: all 5 RDD packages PASS
Stress test: partial progress (Seek issues with split pages)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fixes from 77/77 thorough test:
- SOFTSEEK uses CurRecNo() (was requiring recNo>0)
- SEEK reads SET SOFTSEEK at runtime (was compile-time only)
- SkipIndexed skips deleted records when SET DELETED ON
- GoTopIndexed skips deleted at top position
- evalKeyExprInner TrimSpace on fieldName (compound key fix)
- SET INDEX TO uses exprToString (was emitExpr treating as variable)
- hasXBaseCommands scans nested blocks (BEGIN SEQUENCE, IF, FOR, etc.)
77/77 thorough test PASS. Stress test (82 items) in progress.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. SOFTSEEK: use idx.CurRecNo() for positioning (was checking recNo > 0)
- SEEK with SET SOFTSEEK ON now positions at next higher key
- SEEK command reads SET SOFTSEEK at runtime (was compile-time only)
- rtlDbSeek defaults to GetSetSoftSeek() when no explicit param
2. SET DELETED ON + INDEX: SkipIndexed skips deleted records
- GoTopIndexed: skip deleted record at top position
- SkipIndexed: inner loop continues past deleted records
3. Compound key (CITY+NAME): field name TrimSpace before lookup
- evalKeyExprInner: TrimSpace on fieldName after FIELD-> strip
- Fixed "CITY " != "CITY" mismatch from + operator splitting
4. SET INDEX TO filename: treated as string, not variable
- gengo uses exprToString for SET INDEX TO (was emitExpr)
- Prevents identifier being resolved as local variable
5. hasXBaseCommands: recursive scan into nested blocks
- BEGIN SEQUENCE, IF, FOR, DO WHILE, SWITCH bodies now scanned
- Fixes missing hbrdd import for DB commands inside blocks
Thorough test: 77 items (14 sections) covering exact/partial/soft seek,
SET DELETED, duplicate keys, numeric keys, compound keys, empty/single
table, state consistency, order switching, full traversal — all identical.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Core change:
- dbf.KeyEvalFunc: global callback set by gengo before OrderCreate
- evalKeyExprInner default case: calls KeyEvalFunc for unknown functions
- Final fallback: any unresolvable expression → KeyEvalFunc → MacroEval
- valueToKeyBytes: converts MacroEval result to index key bytes
- gengo: sets dbf.KeyEvalFunc = t.MacroEval before OrderCreate, clears after
Examples that now work:
INDEX ON MyFunc(FIELD->NAME) TO idx // UDF in key expression
INDEX ON CityKey(FIELD->CITY, NAME) TO idx // multi-param UDF
INDEX ON Left(MyFunc(NAME), 15) TO idx // nested built-in + UDF
Also fixed:
- SET ORDER TO n: int→string via hbrt.NtoS (was empty string)
- CDX compound leaf decoder: proper bit-packed tag name extraction
- CDX compound recNo = direct byte offset (not page number)
All existing tests pass, NTX 47/47 + CDX 20/20 Harbour compat maintained.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CDX Integration:
- IndexEngine interface: common for NTX Index and CDX Tag
- OrderListAdd: auto-detects .cdx/.ntx extension, opens CDX tags
- decodeCompoundLeaf: proper bit-packed tag directory decoding
(was stub falling through to scanCompoundLeaves with wrong names)
- CDX Tag: added KeyLen(), KeyExpr(), ForExpr(), IsDescending(), Close()
- CDX compound recNo = direct byte offset (not page number)
ORDSCOPE:
- SetScope/ClearScope/SetScopeTop/SetScopeBottom on DBFArea
- GoTopIndexed: seeks to scopeTop, validates within scopeBottom
- GoBottomIndexed: seeks to scopeBottom boundary
- SkipIndexed: stops at scope boundaries (top and bottom)
- OrdScope RTL function registered (nScope: 0=TOP, 1=BOTTOM)
- scopeKeyFromValue: converts Value to padded key bytes
Index Order Management:
- OrderListFocus: handles numeric order ("2" → order 2)
- SET ORDER TO n: gengo emits hbrt.NtoS for int-to-string conversion
- IndexOrd/OrdCount/OrdName/OrdKey: real implementations (were stubs)
- OrderCount/CurrentOrder/OrderName/OrderKeyExpr accessors on DBFArea
- ClearScope on order switch (prevents stale scope)
Cross-read test: Harbour-created CDX → Five reads, 20/20 items match:
NAME/CITY/ID seek, ORDSCOPE count, GoTop/GoBottom all identical
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Five reads DBF + NTX files created by Harbour:
- NAME index: exact/partial seek, GoTop/Bottom, Skip, SoftSeek
- CITY index: duplicate key seek with correct RecNo order
- ID index: numeric key (Str(ID,6)) seek
17/17 items match Harbour output exactly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
sort.Slice is unstable: equal keys had random record order.
Harbour NTX B-tree orders equal keys by ascending RecNo.
Added RecNo tiebreak to sort comparator.
Result: 47/47 (100%) Harbour compatibility on rdd_compat test.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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) <noreply@anthropic.com>
47 test items comparing Harbour and Five output:
- T01-T28: 100% match (CRUD, navigation, SET DELETED)
- T29-T39: 100% match (SEEK exact/partial/softseek)
- T40-T41: Found matches, RecNo differs (duplicate key sort stability)
- T42-T43: 100% match
- T44-T47: Five crashes (PACK with open index)
Known issues found:
- FIELD->NAME syntax not supported in INDEX ON expression
- Modulo % returns Double causing array index hang (Int() workaround)
- PACK crashes when NTX index is open
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- 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>
Complete Harbour-compatible MEMVAR implementation:
- PUBLIC: global scope, persist until program end
- PRIVATE: function scope + called functions, auto-release on return
- Shadowing: PRIVATE can shadow PUBLIC, restored on scope exit
- Nested: multi-level PRIVATE scoping with save/restore stack
- Thread.PushMemvar/PopMemvar: stack-based memvar access
- Thread.DeclarePublic/DeclarePrivate: declaration helpers
- MacroEval: &cVar now looks up memvars (was returning string)
- Shutdown: Phase 4 clears all memvars on all threads
- Case-insensitive: all lookups uppercased
Tests: 12 tests including:
PUBLIC create/update, case-insensitive, PRIVATE basic,
shadow/restore, nested 3-level shadow, new var cleanup,
release, releaseAll, names, thread integration, macro access
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
cmd/five/main.go:
#16: Merge goPath() → alias for findGoBin() (removed 10-line duplicate)
#17: Merge findProjectRoot() → alias for findFiveRoot()
New walkUpForGoMod() helper shared by both strategies
#33-34: Fix path injection in debugPRG
Was: string concat with unescaped path
Now: fmt.Sprintf(%q) for safe Go string escaping
#43: findProjectRoot aliased to findFiveRoot (removes 3rd copy)
Issues resolved: #16,17 (HIGH), #33,34,43 (MEDIUM)
Total fixed: 34/53
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Expanded from 180 lines to 450+ lines per language:
- Real failure stories: EU bank COBOL→Java (€200M), Brazil Clipper→Python (tax error)
- Deep Go analysis: 25 keywords vs 90+, no exceptions by design, hardware future
- AI paradox: code generation vs code understanding gap
- Detailed code comparisons: Go vs Five for discount calculation
- Five principles with battle scars from 30 years of xBase deployment
- Independence manifesto: zero-dependency code ownership
- Epilogue: what will future archaeologists find?
"The measure of a language is not what it can express,
but what it allows a stranger to understand."
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The founding document of Five, written as a manifesto:
- Why 30 years of xBase code must not be discarded
- Why Go is the right foundation for the next 50 years
- Why human-readable code matters more in the AI era
- What was designed: 5 principles, 5 architectures
- The vision: living code that bridges past and future
"Code fades, but thought endures."
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- five-syntax-en/ko: Add Math comparison table (Harbour RTL vs Go math)
- go_math_compare.prg: Detailed English comments explaining each section
- Example lists updated with go_math_compare.prg
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Five's DEFER is Go's defer in PRG syntax.
Same safety guarantee, but without if err != nil pollution.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Five's hidden strength: PRG code is readable by non-developers.
When AI generates code, humans must verify it.
Five's xBase syntax makes this possible.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>