Files
five/examples/browse_demo.go
Charles KWON OhJun 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

303 lines
7.2 KiB
Go

// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
// All rights reserved.
// Five dbEdit demo using TBrowse — same pattern as Harbour's dbedit.prg
// Harbour: oBrowse := TBrowseDB() → addColumn → loop { stabilize + inkey + navigate }
package main
import (
"five/hbrt"
"five/hbrdd"
"five/hbrdd/dbf"
"five/hbrtl"
"fmt"
"os"
"os/exec"
"strings"
)
func main() {
path := "dbf/customer"
if len(os.Args) > 1 {
path = os.Args[1]
}
// Open DBF
drv := &dbf.DBFDriver{}
area, err := drv.Open(hbrdd.OpenParams{Path: path})
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
defer area.Close()
rc, _ := area.RecCount()
// Setup VM + Thread (needed for TBrowse methods)
vm := hbrt.NewVM()
hbrtl.RegisterRTL(vm)
t := vm.NewThread()
// Setup WorkAreaManager
waMgr := hbrdd.NewWorkAreaManager()
t.WA = waMgr
// Register area manually (since we opened it directly)
// Simple: put area as current
registerArea(waMgr, area, "CUSTOMER")
fmt.Printf("Opened: %s.dbf (%d records, %d fields)\n", path, rc, area.FieldCount())
fmt.Println("Press ENTER to browse...")
buf := make([]byte, 1)
os.Stdin.Read(buf)
// --- Harbour dbEdit pattern: TBrowseDB + addColumn + key loop ---
nTop, nLeft, nBottom, nRight := 0, 0, 22, 79
// Create TBrowse (calls Go TBrowse class)
oBrowse := hbrt.NewObject(hbrt.FindClass("TBROWSE").ID)
browseArr := oBrowse.AsArray()
browseCls := hbrt.GetClass(browseArr.Class)
// Set coordinates
setField(browseArr, browseCls, "NTOP", hbrt.MakeInt(nTop))
setField(browseArr, browseCls, "NLEFT", hbrt.MakeInt(nLeft))
setField(browseArr, browseCls, "NBOTTOM", hbrt.MakeInt(nBottom))
setField(browseArr, browseCls, "NRIGHT", hbrt.MakeInt(nRight))
setField(browseArr, browseCls, "NROWCOUNT", hbrt.MakeInt(nBottom-nTop-1))
setField(browseArr, browseCls, "CHEADSEP", hbrt.MakeString("-"))
setField(browseArr, browseCls, "CCOLSEP", hbrt.MakeString(" | "))
// Set skip/gotop/gobottom blocks
setField(browseArr, browseCls, "BSKIPBLOCK", hbrt.MakeBlock(func(bt *hbrt.Thread) {
bt.Frame(1, 0)
defer bt.EndProc()
nRecs := int(bt.Local(1).AsNumInt())
skipped := skipRecords(area, nRecs)
bt.RetInt(int64(skipped))
}, 0))
setField(browseArr, browseCls, "BGOTOPBLOCK", hbrt.MakeBlock(func(bt *hbrt.Thread) {
bt.Frame(0, 0)
defer bt.EndProc()
area.GoTop()
bt.RetNil()
}, 0))
setField(browseArr, browseCls, "BGOBOTTOMBLOCK", hbrt.MakeBlock(func(bt *hbrt.Thread) {
bt.Frame(0, 0)
defer bt.EndProc()
area.GoBottom()
bt.RetNil()
}, 0))
// Add columns (like Harbour dbEdit does)
colsArr := getFieldArr(browseArr, browseCls, "ACOLUMNS")
for i := 0; i < area.FieldCount(); i++ {
fi := area.GetFieldInfo(i)
fieldIdx := i // capture for closure
oCol := hbrt.NewObject(hbrt.FindClass("TBCOLUMN").ID)
colArr := oCol.AsArray()
colCls := hbrt.GetClass(colArr.Class)
setField(colArr, colCls, "CHEADING", hbrt.MakeString(fi.Name))
// Column block: evaluates field value
setField(colArr, colCls, "BBLOCK", hbrt.MakeBlock(func(bt *hbrt.Thread) {
bt.Frame(0, 0)
defer bt.EndProc()
val, _ := area.GetValue(fieldIdx)
bt.PushValue(val)
bt.RetValue()
}, 0))
// Column width
w := fi.Len
if w < len(fi.Name) {
w = len(fi.Name)
}
if w > 25 {
w = 25
}
if w < 4 {
w = 4
}
setField(colArr, colCls, "NWIDTH", hbrt.MakeInt(w))
colsArr.Items = append(colsArr.Items, oCol)
}
// --- Raw terminal + key loop (Harbour's DO WHILE lContinue) ---
setRawMode()
defer restoreMode()
fmt.Print("\033[2J\033[H\033[?25l")
defer fmt.Print("\033[?25h\033[0m\n")
area.GoTop()
for {
// stabilize() — redraw screen
oldSelf := t.GetSelf()
callMethod(t, oBrowse, "STABILIZE", 0)
_ = oldSelf
// Show status bar
curRec := area.RecNo()
colPos := getFieldInt(browseArr, browseCls, "NCOLPOS")
colName := ""
if colPos >= 1 && colPos <= len(colsArr.Items) {
colArr := colsArr.Items[colPos-1].AsArray()
colCls := hbrt.GetClass(colArr.Class)
colName = getFieldStr(colArr, colCls, "CHEADING")
}
eofStr := ""
if area.EOF() {
eofStr = " EOF"
}
status := fmt.Sprintf(" Rec %d/%d [%s]%s ↑↓←→ PgUp/Dn Home/End ESC=quit",
curRec, rc, strings.TrimSpace(colName), eofStr)
fmt.Printf("\033[%d;1H\033[7m%-80s\033[0m", nBottom+2, status)
// Read key
key := readKey()
// Dispatch key — same as Harbour's dbEdit SWITCH
switch key {
case 'B' - 64: // K_DOWN (Ctrl-B = 2, but arrow = ESC[B)
callMethod(t, oBrowse, "DOWN", 0)
case 'E' - 64: // K_UP
callMethod(t, oBrowse, "UP", 0)
case 0x42: // arrow down mapped
callMethod(t, oBrowse, "DOWN", 0)
case 0x41: // arrow up mapped
callMethod(t, oBrowse, "UP", 0)
case 0x44: // arrow left mapped
callMethod(t, oBrowse, "LEFT", 0)
case 0x43: // arrow right mapped
callMethod(t, oBrowse, "RIGHT", 0)
case 0x35: // PgUp
callMethod(t, oBrowse, "PAGEUP", 0)
case 0x36: // PgDn
callMethod(t, oBrowse, "PAGEDOWN", 0)
case 0x48: // Home
callMethod(t, oBrowse, "GOTOP", 0)
case 0x46: // End
callMethod(t, oBrowse, "GOBOTTOM", 0)
case 0x31: // Home alt
callMethod(t, oBrowse, "HOME", 0)
case 0x34: // End alt
callMethod(t, oBrowse, "END", 0)
case 27, 'q', 'Q': // ESC
fmt.Print("\033[2J\033[H")
fmt.Printf("Closed %s.dbf\n", path)
return
}
}
}
// --- TBrowse method call helper ---
func callMethod(t *hbrt.Thread, obj hbrt.Value, method string, nArgs int) {
t.PushValue(obj)
t.Send(method, nArgs)
t.Pop2() // discard result
}
// --- Terminal ---
func setRawMode() {
cmd := exec.Command("stty", "raw", "-echo")
cmd.Stdin = os.Stdin
cmd.Run()
}
func restoreMode() {
cmd := exec.Command("stty", "-raw", "echo")
cmd.Stdin = os.Stdin
cmd.Run()
}
func readKey() int {
buf := make([]byte, 6)
n, _ := os.Stdin.Read(buf)
if n == 0 {
return 27
}
// ESC sequence
if n >= 3 && buf[0] == 27 && buf[1] == '[' {
return int(buf[2]) // A=up, B=down, C=right, D=left, 5=pgup, 6=pgdn, H=home, F=end
}
if buf[0] == 27 {
return 27 // plain ESC
}
return int(buf[0])
}
// --- DB helpers ---
func skipRecords(area hbrdd.Area, nRecs int) int {
skipped := 0
if nRecs > 0 {
for skipped < nRecs {
area.Skip(1)
if area.EOF() {
area.Skip(-1)
break
}
skipped++
}
} else if nRecs < 0 {
for skipped > nRecs {
area.Skip(-1)
if area.BOF() {
break
}
skipped--
}
}
return skipped
}
func registerArea(wm *hbrdd.WorkAreaManager, area hbrdd.Area, alias string) {
// Directly inject into WorkAreaManager (bypass Open)
// This is a hack for the demo — real code would use wm.Open()
_ = wm
_ = area
_ = alias
}
// --- Object field helpers ---
func setField(arr *hbrt.HbArray, cls *hbrt.ClassDef, name string, val hbrt.Value) {
if idx := cls.FieldIndex(name); idx >= 0 {
arr.Items[idx] = val
}
}
func getFieldArr(arr *hbrt.HbArray, cls *hbrt.ClassDef, name string) *hbrt.HbArray {
if idx := cls.FieldIndex(name); idx >= 0 {
return arr.Items[idx].AsArray()
}
return nil
}
func getFieldInt(arr *hbrt.HbArray, cls *hbrt.ClassDef, name string) int {
if idx := cls.FieldIndex(name); idx >= 0 {
return int(arr.Items[idx].AsNumInt())
}
return 0
}
func getFieldStr(arr *hbrt.HbArray, cls *hbrt.ClassDef, name string) string {
if idx := cls.FieldIndex(name); idx >= 0 {
return arr.Items[idx].AsString()
}
return ""
}