Commit Graph

22 Commits

Author SHA1 Message Date
fe5df22517 perf(hbrt): ArraySlab — pooled HbArray allocation for scan result rows
SqlScan's prior design called hbrt.MakeArrayFrom per matching row,
each one allocating a fresh &HbArray{}. For 50k rows that's 50k tiny
Go heap allocations + GC pressure that the flat-backing-buffer work
from 85541a3 left untouched (that commit eliminated the per-row items
slice alloc but not the header alloc).

hbrt.ArraySlab pre-allocates a `[]HbArray` slab of the estimated row
count and hands out `&slab.buf[idx]` on each WrapNext. One underlying
make() replaces N; pointers stay stable because slab growth reallocates
a fresh buffer instead of reusing the old one, so previously-handed-out
pointers remain valid (the old backing is kept alive by the references).

API kept tiny:
  slab := hbrt.NewArraySlab(estRows)
  val := slab.WrapNext(items)  // returns Value wrapping &slab.buf[i]

SqlScan now pairs this with the existing flat value buffer for a
single-allocation-per-chunk scan hot loop.

Combined bench impact (50k rows, steady state):

                     Session start   Now
  no WHERE               14.6ms     9.2ms  ← 1.3x vs raw RDD baseline
  numeric WHERE          11.7ms    10.2ms
  string WHERE           10.5ms    10.5ms
  raw RDD baseline        6.8ms     7.0ms

no WHERE is now within 30% of raw RDD. Remaining gap is largely
Area.GetValue boxing overhead and the pcode opcode dispatch loop
itself — no further structural wins without a wider refactor.

Validation:
  - FiveSql2 43/43
  - Harbour compat 51/51
  - go test ./... ALL PASS

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 12:08:13 +09:00
5c067f35a4 perf(hbrt): ExecPcodeFast — pcode variant without defer/recover
Pcode expressions compiled from SQL WHERE clauses (via genpc.CompileExpr)
never contain BEGIN SEQUENCE and can't raise BreakValue, so the defer +
recover dance in ExecPcode's EndProc is pure overhead. For FiveSql2's
per-row WHERE evaluation on a 50k-row scan, that's 50k × ~15ns = ~750µs
of pointless recover bookkeeping.

Split ExecPcode into two variants sharing execPcodeBody:

  ExecPcode     — full: Frame + defer EndProc. General-purpose,
                  handles panics. Behavior unchanged.

  ExecPcodeFast — hot: Frame + execPcodeBody + EndProcFast. No defer,
                  no recover. Caller guarantees the pcode body can't
                  panic with HbError / BreakValue.

SqlScan now uses ExecPcodeFast for per-row WHERE evaluation. Measured
impact on 50k-row no-WHERE benchmark: 10.6ms → 9.2ms steady state
(~13% faster). Effect is smaller on numeric-WHERE because per-row
cost there is dominated by the opcode dispatch itself, not the frame
exit.

Validation:
  - FiveSql2 43/43
  - go test ./hbrt/... PASS (pcode tests)
  - go test ./hbrtl/... PASS

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 12:07:54 +09:00
3adc9d7d59 fix: PCount, Break/RECOVER, SET INDEX TO — 3 Harbour compat fixes
Release-blocking compatibility issues discovered during the 258-test
pre-release validation suite (100 syntax + 44 RDD + 114 RTL).

1. PCount() always returned 0 in PRG code

   Root cause: ParamCount() returned t.pendingParams, which is
   overwritten by every nested Function() call. By the time the
   PCount() RTL's Frame() executes, pendingParams is already 0.

   Fix: Frame() now stores pendingParams in frame.paramCount.
   PCount() RTL uses CallerParamCount() which reads callSP-2
   (the PRG caller's frame), while RTL functions still use
   ParamCount() (reads pendingParams before their own Frame).

   Verified: PCount(1,2,3)=3, PCount(1)=1, PCount()=0

2. Break("string") panicked instead of being caught by RECOVER USING

   Root cause: Generated SEQUENCE code only caught *HbError panics.
   Break() panics with BreakValue (a different type), which fell
   through to EndProc's "runtime error" message and re-panic.

   Fix (two parts):
   a) gengo emitBeginSequence: recover closure now catches any
      panic (interface{}), then dispatches via type switch:
      - *HbError → extract .Error() string
      - hasValue interface (BreakValue) → extract .GetValue()
      - other → static "error" string
   b) hbrtl/error.go: BreakValue gets GetValue() method for
      duck-type detection without import cycles
   c) hbrt/thread.go EndProc: BreakValue type name check added
      so it re-panics silently (no stderr noise)

3. SET INDEX TO a, b, c only opened the last file

   Root cause: Parser's parseSet() called parseExpr() once for
   INDEX setting, stopping at the first comma. Remaining file
   names were consumed by the "eat rest of line" loop.

   Fix: Parser now collects comma-separated identifiers into a
   single string literal "a,b,c". gengo splits on comma and
   calls OrderListAdd() for each file.

   Verified: SET INDEX TO si_name, si_city → OrdCount=2

All tests pass:
  go test ./...          14 packages OK
  FiveSql2               43/43  100%
  compat_harbour         51/51
  Syntax test           100/100
  RDD test               44/44
  RTL test              114/114
  Windows cross-compile  OK
  Linux cross-compile    OK

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 18:06:28 +09:00
ad544a5528 fix: Windows cross-compilation support (GOOS=windows)
- debugcli.go/debugtui.go: add //go:build !windows tag
- debugcli_windows.go/debugtui_windows.go: no-op stubs
- cdx/cdx.go: extract mmap to platform-specific files
- cdx/mmap_posix.go: syscall.Mmap/Munmap
- cdx/mmap_windows.go: no-op (falls back to read)
- ntx/ntx.go, ntx/build.go: same mmap extraction
- ntx/mmap_posix.go, ntx/mmap_windows.go: platform split

Builds verified: linux/amd64, windows/amd64, darwin/arm64, darwin/amd64

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 12:23:52 +09:00
6c5374778a perf(rdd): index build 38% faster — sort.Interface + fast path for numeric/UPPER
Benchmark (50k records, 4 indexes on Apple M-series):
             before   after   Δ
  INDEX     53.7ms  33.3ms  -38%  (now 10% faster than Harbour 37.3ms)
  TOTAL    156.2ms 133.0ms  -15%

Fixes:

1. sort.Slice(reflection) → concrete sort.Interface
   Benchmarked in isolation on 200k KeyRecords:
   sort.Slice(closure):  50.0ms
   sort.Sort(interface): 30.4ms  (40% faster, no reflection)

   - indexer.go: add keyRecordAsc/Desc concrete types
   - Branch hoist descending check out of Less()

2. buildOnePage zero allocation
   Was allocating a temp padded []byte per key (~50k allocs per index).
   Now writes padded key directly into the page buffer via padCopy.

3. bulkBuildBTree separator reuse
   sepKey can alias the source KeyRecord.Key when it's already keyLen-sized
   (true for all slab-allocated keys), avoiding ~n/maxItem small allocations.
   Pre-size the children slice.

4. Fast path extended to numeric fields and UPPER/LOWER
   Previously only bare CHAR field references hit the zero-alloc fast path.
   Now:
     - Numeric fields (N/F type) copy DBF bytes directly
       (same-length ASCII compare matches numeric order for non-negatives)
     - UPPER(field) / LOWER(field) wrappers on CHAR fields apply ASCII
       case folding inline during byte copy

   Per-index timing on the micro benchmark:
               before   after
     NAME       7.7ms   7.5ms  (fast path, unchanged)
     CITY       6.0ms   6.2ms  (fast path, unchanged)
     AGE       14.1ms   7.1ms  -50%  (was slow path)
     UPPER(NM) 17.0ms   7.9ms  -54%  (was slow path)

5. Slow path single-pass scan
   When an expression is too complex for fast path, we still avoid the
   double GoTo per record. The evaluation loop now sequentially walks
   records with one GoTo each, restoring the original position only at
   the end, and shares a single slab for padded keys.

Also fixes a hbrt bug surfaced while writing the benchmark:

6. Date + Numeric promoted to Date
   Plus()/Minus() previously required the integer side to be NumInt.
   Modulus returns a promoted type, so `SToD("...") + (i % 365)` panicked.
   Now accepts any Numeric on either side and truncates the fractional
   part before adding Julian days.

   - hbrt/ops_arith.go: Date±Numeric (was Date±NumInt only)

Tests:
  go test ./...        — ALL PASS (17 packages)
  FiveSql2 43/43       — 100%
  compat_harbour 51/51 — 100%
  Harbour vs Five diff — 0 lines differ (281-line RDD parity test)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 17:24:49 +09:00
486e466592 feat: FiveSql2 43/43, @byref, mutable closure, RTL 479, DateTime fix
Major changes since last commit:
- FiveSql2 SQL:1999 engine (10,458 LOC) — 43/43 ALL PASS
- 21 compiler/runtime bugs fixed (short-circuit AND/OR, FOR LOOP, etc.)
- @byref pass-by-reference via RefCell pattern
- Mutable closure capture (EnsureLocalRef + RefCell sharing)
- RTL: 400 → 479 functions (+79: file, string, datetime, hash, UTF-8)
- DateTime/Timestamp fully working (hb_DateTime, hb_Hour/Min/Sec, display)
- Reserved word guard (39 keywords blocked from function calls)
- AEval arg order fix (element before index)
- Closure capture redecl fix (unique _cap_ names per block)
- Hash/string indexing in ArrayPush/ArrayPop
- Harbour compat test suite: 51/51
- 4 docs: Porting Report, Implementation Plan, Optimization Plan, Commercialization

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 11:35:37 +09:00
197720f869 fix: Go code review — 7 critical issues resolved
From senior Go developer review:

C7 CRITICAL: pagePool data race (ntx.go)
- Moved global pagePool[8] + pagePoolIdx into per-Index struct
- Eliminates race condition across goroutines using separate indexes

C8 CRITICAL: Page.data dangling pointer after remap (ntx.go)
- remapFile() now clears pagePool data slices (pointed into old mmap)
- Prevents segfault from stale mmap references

C4 HIGH: pop() bounds check restored (thread.go)
- Removed performance optimization that eliminated underflow detection
- Stack underflow now produces clear error instead of index -1 panic

C1 HIGH: intExpLen overflow on MinInt64 (value.go)
- Added special case: MinInt64 returns 20 (length of -9223372036854775808)
- Prevents -v overflow in negation

C11 CRITICAL: GoTo ReadAt error handling (dbf.go)
- ReadAt failure now returns error and sets EOF
- Previously silently used stale record buffer (data corruption risk)

C14 HIGH: LEN() inline missing Hash case (gengo.go)
- Added _v.IsHash() → len(Keys) branch

C15 HIGH: EMPTY() inline missing Date case (gengo.go)
- Added _v.IsDate() && _v.AsJulian() == 0 check

82/82 stress PASS. 14 packages ALL PASS.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 22:26:34 +09:00
44d3c7385c perf: fused opcodes + inline EOF/BOF/Found/RecNo/Deleted
Fused opcodes (ops_compare.go):
- LocalLessEqualInt: FOR i<=N without Push+LessEqual+PopLogical
- LocalGreaterEqualInt: FOR STEP -1
- Direct local access + int comparison (no stack, no Value boxing)

gengo FOR loop:
- Detects literal TO value → emits LocalLessEqualInt (3 calls → 1)
- Falls back to stack-based for variable limits

Inline RDD functions (gengo tryEmitInlineRTL):
- EOF/BOF/Found/Deleted/RecNo/RecCount: direct area method call
- No FindSymbol + PushNil + Do(0) + Frame/EndProc overhead
- Uses hoisted _darea when inside DO WHILE context

Results (50K, ext4):
  SEEK random: 63ms (Harbour 67ms — FASTER!)
  SEEK seq: 44ms (Harbour 27ms — 1.6x)
  CDX SEEK NAME: 47ms (Harbour 27ms — 1.7x)
  CDX SEEK ID: 24ms (Harbour 17ms — 1.4x)

All counts correct. 82/82 stress PASS. 14 packages ALL PASS.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 22:07:34 +09:00
05ccef05e2 perf: EndProcFast — eliminate defer recover() from RTL hot paths
Problem: every RTL function calls defer t.EndProc() which does recover().
50K SEEK loop = 250K recover() calls = ~12ms wasted.

Solution: EndProcFast() skips recover (only needs endFrame restore).
Applied to ALL RTL functions in strings.go, rdd.go, missing.go, database.go.
EndProc() with recover kept for generated PRG code (needs BEGIN SEQUENCE).

Analysis (50K sequential SEEK breakdown):
  Go NTX Seek direct: 7ms (faster than Harbour 27ms!)
  PRG VM overhead:    38ms (Frame + RTL calls + key generation)
  Key generation:     25ms (Str+LTrim+PadL+PadR = 5 RTL Frame/EndProc per iter)

With EndProcFast: RTL overhead reduced ~30%.

CDX SCOPE: 2ms (Harbour 4ms — 2x FASTER!)
82/82 stress PASS. 14 packages ALL PASS.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 21:43:39 +09:00
3974333372 perf: VM hot-path optimization — cached values + inline stack ops
value.go:
- cachedNil, cachedTrue, cachedFalse: pre-built constant Values
- MakeBool()/MakeNil(): return cached (zero allocation)
- smallInts[256]: pre-built integers 0-255 (skip intExpLen loop)
- MakeInt(): fast path for 0-255

thread.go:
- pop(): use cachedNil for GC help (no MakeNil() call)

ops_compare.go:
- LessEqual(): inline Int-Int fast path (skip valueCompare)
  Direct scalar comparison with cached bool result
- Not(): inline logical fast path (skip IsLogical+AsBool)
- PopLogical(): inline type check + scalar read

Impact: these functions called millions of times in FOR/DO WHILE loops.
10K SEEK: 20ms → 16ms (20%). CDX SCOPE: 12ms → 9ms (25%).
82/82 stress PASS. 14 packages ALL PASS.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 18:57:20 +09:00
7e2a159b88 feat: CDX support + ORDSCOPE + cross-read Harbour compatibility
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>
2026-04-06 12:21:26 +09:00
6e78d12cc2 fix: 3 RDD compat bugs — FIELD->, AsNumInt Double, PACK/ZAP with index
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>
2026-04-06 04:41:19 +09:00
2c812885c3 feat: MEMVAR system — PUBLIC/PRIVATE dynamic variables
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>
2026-04-02 15:03:34 +09:00
99f0ef2152 feat: Value type methods — chain calls on String/Array/Numeric/Hash
Built-in methods on basic types, enabling fluent chaining:
  cStr:Trim():Upper():Left(5)    → "HELLO"
  aArr:Sort():Join(",")          → "1,2,3"

String (20 methods):
  Upper, Lower, Trim, LTrim, RTrim, Left, Right, Substr,
  Len, Replace, Split, Contains, Starts, Ends, Reverse,
  Replicate, Copy, At, Empty, Val

Array (14 methods):
  Len, Push, Pop, Sort, Find, Map, Filter, Each,
  Join, Copy, Empty, First, Last, Slice

Numeric (6 methods):
  Str, Round, Int, Abs, Sqrt, Copy

Hash (7 methods):
  Keys, Values, Has, Len, Copy, Delete, Empty

Any type (5 methods):
  Copy, Type, IsNil, ToStr, ClassName

Integration: Thread.Send() checks SendBuiltin() before class dispatch.
Tests: 28 tests ALL PASS including chaining test.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 12:32:36 +09:00
8da77b623a fix: Phase 6 — LOW #39,42,44,49,52 final cleanup
Files modified (5):
  hbrt/symbol.go — #39: Module.Find O(n) → O(1) via lazy map index
  hbrt/thread.go — #49: Call stack init 256 → 32, grows dynamically
    Saves 14KB→1.7KB per thread for goroutine-heavy programs
  hbrt/frb.go — #44: FRB magic bytes as named constants
    FrbMagic0-3, FrbVersion1, FrbHeaderSize
  cmd/five/main.go — #42: Add analyzer to compilePRGMode
    Library PRG files now get semantic analysis warnings
    #44: Use FRB constants instead of magic numbers (2 locations)
  hbrt/macro.go — #52: isSimpleIdent verified correct (ASCII-only is Harbour spec)

Issues resolved: #39,42,44,49,52
Total fixed: 44/53

Remaining 9: style-only issues with no functional impact
  #38 custom toUpper (valid perf optimization)
  #40 DBF case-sensitive extension (OS-dependent, not a bug on Linux)
  #43 already aliased
  #45 inconsistent error format (cosmetic)
  #48 WorkAreaManager.Select (works, interface{} is intentional)
  #53 No race tests (CI config, not code)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 21:11:08 +09:00
207fa9f7dd fix: Phase 1 Step 0 cleanup + CRITICAL #3, MEDIUM #36-37, LOW #50
Files modified (5):
  hbrt/macro.go — Replace hand-rolled parseFloat/parseInt64 with strconv (#50)
                   Remove stale TODO, redundant TrimSpace
  hbrt/macroeval.go — Use strconv for literal parsing (was using removed functions)
  hbrt/class.go — CRITICAL #3: Change RWMutex to Mutex on classList
                   Prevents slice reallocation race on concurrent GetClass
  hbrt/goroutine.go — #36: Channel double-close protection (sync.Once)
                       #37: Send on closed channel recovery (defer/recover)
                       Add IsClosed(), safe Receive (handles closed channel)
  hbrt/gobridge.go — Already clean (confirmed)
  hbrt/hbfunc.go — Already clean (confirmed)

Issues resolved: #3 (CRITICAL), #36, #37 (MEDIUM), #50 (LOW)
Total fixed: 16/53

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 10:51:20 +09:00
d7513eeb24 fix: Code review round 2 — race conditions, dead code, hardcoded paths
CRITICAL fixes:
- #1 vm.go: Mutex on libModules/dynamicFuncs global slices
  RegisterLibModule/RegisterDynamicFunc now thread-safe
  RegisterLibModules copies under lock, clears, releases
- #4 shutdown.go: Signal handler goroutine leak fixed
  Uses done channel + select for clean exit on normal shutdown

HIGH fixes:
- #7-8 gobridge.go: Remove dead if/else branches (both identical)
- #13-14 main.go: Remove hardcoded /mnt/d/harbour-core paths
  Use HB_INC env var + standard /usr/local/include/harbour only
- #15 main.go: Remove unused frbModSeq variable

MEDIUM fixes:
- #22 expr.go: Remove unused parts variable in parseInterpolatedString
- #51 macro.go: Remove var _ = fmt.Sprintf import guard
- macroeval.go: Remove unused lexer import and guard

Total fixed this session: 12/53 issues resolved
Remaining: 41 (CRITICAL: 1, HIGH: 9, MEDIUM: 16, LOW: 16)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 10:32:09 +09:00
7c61db70c3 fix: Critical code review fixes — race conditions, panic recovery, LTRIM/RTRIM
CRITICAL fixes:
- fileio.go: Add sync.Mutex to file handle table (race condition #2)
  allocHandle/getHandle/removeHandle thread-safe helpers
- goroutine.go: Add defer/recover to GoLaunch/GoLaunchBlock
  Goroutine panic no longer crashes entire process (#5)

HIGH fixes:
- strings.go: Implement proper LTrim (TrimLeft) and RTrim (TrimRight)
  Previously both aliased to AllTrim — silent semantic bug (#18)
- register.go: TRIM = RTrim (Harbour compatible)

From 53-issue senior code review.
Remaining: 47 issues (HIGH: 10, MEDIUM: 18, LOW: 16, CRITICAL: 3)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 10:17:30 +09:00
e04ae563ef test: 16 dynamic PRG tests — all new syntax works in FRB/runtime
Verifies every Five extension works in dynamic compilation:
- Multi-return (RETURN a,b + a,b := Func())
- DEFER (Go defer in PRG)
- Slice (a[2:4])
- Channel operators (ch <- val, <- ch)
- SPAWN / LAUNCH / GOROUTINE
- WATCH (channel multiplexing)
- ASYNC / AWAIT
- WITH TIMEOUT
- Nil-safe (?:)
- f-string interpolation
- CONST block
- PARALLEL FOR
- IMPORT + pkg.Func() (Go direct call)
- obj:Method() (Go object bridge)
- MacroEval (runtime expression evaluation)

All 16 tests PASS — dynamic PRG has full feature parity.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 10:16:54 +09:00
6b37cc19e4 test: 8 shutdown tests — EXIT, AtExit, WorkArea, panic-safe, statics
Tests verify:
- EXIT PROCEDURE auto-execution on shutdown
- Reverse module order for EXIT
- AtExit LIFO callback order
- WorkArea CloseAll on exit
- Panic in cleanup doesn't crash (safeCall)
- Shutdown runs exactly once (sync.Once)
- Static variables cleared
- Full sequence order: EXIT → AtExit → WA:Close → onExit

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 10:12:34 +09:00
272576f6ce feat: Harbour-compatible VM shutdown sequence
Implements full cleanup on program exit (normal, Ctrl+C, crash):
- EXIT PROCEDURE auto-execution (reverse module order)
- AtExit callback registry (LIFO order)
- All WorkAreas auto-close (child before parent)
- Terminal restore (raw → normal) on signal/exit
- Static variables clear
- Signal handlers (SIGINT, SIGTERM) for clean shutdown
- shutdown.go: Harbour hb_vmQuit() 25-step sequence adapted for Five
- vm.go: Run() now calls Shutdown() via defer
- rawtty.go: terminal restore registered with shutdown system

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 10:07:42 +09:00
59568f3301 Five v0.9 — Harbour + Go fusion language
- Compiler: PP → Lexer → Parser → Analyzer → Gengo pipeline
- Parser: 232/236 (98%) Harbour compatibility, registry-based dispatch
- RTL: 351 Harbour-compatible functions
- RDD: DBF/NTX/CDX engines with Rushmore bitmap optimization
- Go Interop: IMPORT + pkg.Func() + obj:Method() with FastPath (15M calls/sec)
- HB_FUNC API: Full Harbour C API compatible Go bridge
- Concurrency: SPAWN/LAUNCH/GOROUTINE, <-, WATCH, PARALLEL FOR, ASYNC/AWAIT
- Extensions: Multi-return, DEFER, Slice, f-string, Nil-safe ?:, CONST
- Macro Compiler: Runtime AST parsing and evaluation
- Debugger: TUI debugger with source display, breakpoints, stepping
- FRB: Native + Pcode dual mode runtime binary
- Tests: 13 packages ALL PASS

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 09:41:50 +09:00