Cumulative season's silent-bug hunting (~62 fixes) across the FiveSql2 SQL engine, the Five compiler/runtime, and the hbrdd RDD layer. Saved as a single checkpoint before refactoring the parser to delegate xBase command translation to the preprocessor. Highlights: FiveSql2 engine (_FiveSql2/src/) - prefix-glob index attach -> explicit convention (<table>_pk.ntx, <table>_uq.ntx, <table>.cdx) — fixes silent multi-row INSERT row-drop - DROP/CREATE TABLE FErase chain extended (.cdx, .fsc, .fsv, .dbt, .fpt) - COUNT(DISTINCT col) parsed + aggregated via hSeen hash - UNION column-count mismatch returns SQL_ERR_GRAMMAR (was silent) - DISTINCT + ORDER BY hidden-col leak fixed (trim before DISTINCT) - Derived table FROM (SELECT...) + JOIN right-side derived - Self-FK CASCADE depth 2+ via SqlGetSingleColPK pre-collect - LAG/LEAD default arg uses SqlEvalRowExpr (handles -N const exprs) - DATE literal round-trip validation (Feb 29 non-leap rejected) - CREATE OR REPLACE VIEW; CREATE VIEW errors on already-exists - AlterTable type dispatcher comma-wrapped (1-char type "A" no longer matches CHARACTER) Compiler / runtime - gengo: HB_ -> FV_ prefix on emitted Go function names (Five identity) - gengo split: emit_block.go, emit_stmt.go, folding.go extracted - parser/stmtreg.go nudges - hbrt: debug TUI/CLI restructure (debugcmd, debugkey, termios_*), windows debug stubs collapsed - thread/vm/value/class/pcinterp tightening from panic traces RDD layer (hbrdd/) - dbf: null bitmap support (null.go + null_test.go), mmap split (mmap_posix.go / mmap_windows.go), byte-level numeric parse - ntx/cdx: windows mmap parity - workarea + mem RDD: cross-area state-bleed fixes RTL (hbrtl/) - errorlog rewrite with platform-specific FD (errorlog_fd_unix / errorlog_fd_other) - sqlscan, sqlhelpers, indexrtl, datetime extensions Gates green at checkpoint: - go test ./... : PASS - FiveSql2 SQL:1999 : 43/43 - Harbour compat : 56/56 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
121 lines
3.3 KiB
Go
121 lines
3.3 KiB
Go
// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
|
|
// All rights reserved.
|
|
|
|
// Interactive CLI debugger for Five.
|
|
// Provides a gdb-like command interface for stepping through PRG code.
|
|
//
|
|
// Commands:
|
|
// s, step — step to next line
|
|
// n, next — step over (don't enter functions)
|
|
// o, out — step out of current function
|
|
// c, cont — continue (run until breakpoint)
|
|
// b <line> — set breakpoint at line
|
|
// d <n> — delete breakpoint
|
|
// bl — list breakpoints
|
|
// p <expr> — print variable value
|
|
// l — list locals
|
|
// bt — show call stack (backtrace)
|
|
// q — quit
|
|
// h — help
|
|
|
|
package hbrt
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
)
|
|
|
|
// Terminal raw/cooked mode helpers live in termios_<os>.go — their ioctl
|
|
// numbers differ per platform (Linux TCGETS vs macOS TIOCGETA).
|
|
|
|
// CLIDebugger creates a DebugCallback for interactive terminal debugging.
|
|
func CLIDebugger() DebugCallback {
|
|
reader := bufio.NewReader(os.Stdin)
|
|
lastCmd := "s" // default repeat command
|
|
|
|
return func(event *DebugEvent) int {
|
|
// Restore terminal to cooked mode for debugger I/O
|
|
fmt.Print("\r\n")
|
|
restoreCooked()
|
|
defer reenterRaw()
|
|
|
|
if event.Reason == "breakpoint" {
|
|
fmt.Printf(" ** Breakpoint at %s:%d\n", event.Module, event.Line)
|
|
}
|
|
fmt.Printf(" %s:%d in %s()\n", event.Module, event.Line, event.Function)
|
|
|
|
// Show source line if available
|
|
showSourceLine(event.Module, event.Line)
|
|
|
|
// Auto-eval watches. Each watch expression is shown alongside
|
|
// its current value so the user doesn't have to re-type them
|
|
// after every step. Failed watches show the eval error inline
|
|
// instead of the value.
|
|
dbgForWatch := event.Thread.VM().Debugger
|
|
if dbgForWatch != nil && len(dbgForWatch.Watches) > 0 {
|
|
fmt.Println(" -- watches --")
|
|
for i, expr := range dbgForWatch.Watches {
|
|
v, evalErr := event.Thread.EvalWithFrameLocals(expr)
|
|
if evalErr != "" {
|
|
fmt.Printf(" [%d] %s ! %s\n", i, expr, evalErr)
|
|
} else {
|
|
fmt.Printf(" [%d] %s = %s\n", i, expr, describeDbgValue(v))
|
|
}
|
|
}
|
|
}
|
|
|
|
// CLI prompt loop — delegates each input line to runDebugCmd.
|
|
// runDebugCmd returns a Dbg* mode to resume execution, or
|
|
// cmdNoMode (-1) when the command just printed output.
|
|
for {
|
|
fmt.Printf("(dbg) ")
|
|
line, err := reader.ReadString('\n')
|
|
if err != nil {
|
|
return DbgContinue
|
|
}
|
|
line = strings.TrimSpace(line)
|
|
if line == "" {
|
|
line = lastCmd
|
|
} else {
|
|
lastCmd = line
|
|
}
|
|
|
|
mode := runDebugCmd(event, line, func(s string) { fmt.Println(s) })
|
|
if mode != cmdNoMode {
|
|
return mode
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// showSourceLine attempts to show the source code around the current line.
|
|
func showSourceLine(module string, line int) {
|
|
data, err := os.ReadFile(module)
|
|
if err != nil {
|
|
return
|
|
}
|
|
lines := strings.Split(string(data), "\n")
|
|
start := line - 3
|
|
if start < 1 {
|
|
start = 1
|
|
}
|
|
end := line + 2
|
|
if end > len(lines) {
|
|
end = len(lines)
|
|
}
|
|
for i := start; i <= end; i++ {
|
|
marker := " "
|
|
if i == line {
|
|
marker = ">>"
|
|
}
|
|
if i-1 < len(lines) {
|
|
fmt.Printf(" %s %4d: %s\n", marker, i, lines[i-1])
|
|
}
|
|
}
|
|
}
|
|
|
|
// Shared helpers (parseBreakArgs, describeDbgValue, runDebugCmd) now
|
|
// live in debugcmd.go so the TUI can reuse them.
|