- 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>
275 lines
5.7 KiB
Go
275 lines
5.7 KiB
Go
// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
|
|
// All rights reserved.
|
|
|
|
// Terminal I/O functions for Five.
|
|
// Implements Harbour's @..SAY, @..GET, Inkey(), SetPos(), Row(), Col(),
|
|
// and screen control using Go's ANSI escape sequences.
|
|
//
|
|
// No external dependency — uses standard ANSI/VT100 escape codes.
|
|
// Works on Linux, macOS, Windows Terminal, WSL.
|
|
//
|
|
// Reference: /mnt/d/harbour-core/src/rtl/gtcrs/, gtstd/, gttrm/
|
|
package hbrtl
|
|
|
|
import (
|
|
"bufio"
|
|
"five/hbrt"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
)
|
|
|
|
// Terminal state
|
|
var (
|
|
termRow int = 0
|
|
termCol int = 0
|
|
)
|
|
|
|
// --- Screen positioning (ANSI escape) ---
|
|
|
|
// SetPos positions cursor at row, col (0-based).
|
|
// Harbour: SetPos(nRow, nCol)
|
|
func SetPos(t *hbrt.Thread) {
|
|
t.Frame(2, 0)
|
|
defer t.EndProc()
|
|
row := int(t.Local(1).AsNumInt())
|
|
col := int(t.Local(2).AsNumInt())
|
|
termRow = row
|
|
termCol = col
|
|
fmt.Printf("\033[%d;%dH", row+1, col+1) // ANSI is 1-based
|
|
t.RetNil()
|
|
}
|
|
|
|
// Row returns current cursor row.
|
|
func Row(t *hbrt.Thread) {
|
|
t.Frame(0, 0)
|
|
defer t.EndProc()
|
|
t.RetInt(int64(termRow))
|
|
}
|
|
|
|
// Col returns current cursor column.
|
|
func Col(t *hbrt.Thread) {
|
|
t.Frame(0, 0)
|
|
defer t.EndProc()
|
|
t.RetInt(int64(termCol))
|
|
}
|
|
|
|
// DevPos positions cursor (alias for SetPos).
|
|
// Harbour: DevPos(nRow, nCol)
|
|
func DevPos(t *hbrt.Thread) {
|
|
SetPos(t)
|
|
}
|
|
|
|
// --- Screen output ---
|
|
|
|
// DevOut outputs value at current position.
|
|
// Harbour: DevOut(xValue)
|
|
func DevOut(t *hbrt.Thread) {
|
|
t.Frame(1, 0)
|
|
defer t.EndProc()
|
|
v := t.Local(1)
|
|
s := valueToDisplay(v)
|
|
fmt.Print(s)
|
|
termCol += len(s)
|
|
t.RetNil()
|
|
}
|
|
|
|
// AtSay implements @ nRow, nCol SAY expr
|
|
// In Five: compiled as SetPos(r,c) + DevOut(expr)
|
|
func AtSay(t *hbrt.Thread) {
|
|
nParams := t.ParamCount()
|
|
t.Frame(nParams, 0)
|
|
defer t.EndProc()
|
|
row := int(t.Local(1).AsNumInt())
|
|
col := int(t.Local(2).AsNumInt())
|
|
termRow = row
|
|
termCol = col
|
|
fmt.Printf("\033[%d;%dH", row+1, col+1)
|
|
if nParams >= 3 {
|
|
s := valueToDisplay(t.Local(3))
|
|
fmt.Print(s)
|
|
termCol += len(s)
|
|
}
|
|
t.RetNil()
|
|
}
|
|
|
|
// DevOutPict outputs a value with PICTURE formatting.
|
|
// Harbour: DevOutPict(xValue, cPicture [, cColor])
|
|
// Expands to: DevOut(Transform(xValue, cPicture), cColor)
|
|
func DevOutPict(t *hbrt.Thread) {
|
|
nParams := t.ParamCount()
|
|
t.Frame(nParams, 0)
|
|
defer t.EndProc()
|
|
|
|
val := t.Local(1)
|
|
pic := ""
|
|
if nParams >= 2 && !t.Local(2).IsNil() {
|
|
pic = t.Local(2).AsString()
|
|
}
|
|
|
|
s := transformHbValue(val, pic)
|
|
fmt.Print(s)
|
|
termCol += len(s)
|
|
t.RetNil()
|
|
}
|
|
|
|
// --- Screen control ---
|
|
|
|
// Cls clears the screen.
|
|
// Harbour: CLS or @ 0,0 CLEAR
|
|
func Cls(t *hbrt.Thread) {
|
|
t.Frame(0, 0)
|
|
defer t.EndProc()
|
|
fmt.Print("\033[2J\033[H") // clear screen + home
|
|
termRow = 0
|
|
termCol = 0
|
|
t.RetNil()
|
|
}
|
|
|
|
// Scroll scrolls a screen region.
|
|
// Harbour: Scroll(nTop, nLeft, nBottom, nRight, nRows)
|
|
func Scroll(t *hbrt.Thread) {
|
|
t.Frame(5, 0)
|
|
defer t.EndProc()
|
|
// Simplified: just move cursor
|
|
t.RetNil()
|
|
}
|
|
|
|
// --- Keyboard ---
|
|
|
|
// InkeyWait waits for a keypress and returns key code.
|
|
// Harbour: Inkey(nSeconds) → nKeyCode
|
|
func InkeyWait(t *hbrt.Thread) {
|
|
nParams := t.ParamCount()
|
|
t.Frame(nParams, 0)
|
|
defer t.EndProc()
|
|
|
|
// Simple: read one byte from stdin
|
|
reader := bufio.NewReader(os.Stdin)
|
|
ch, err := reader.ReadByte()
|
|
if err != nil {
|
|
t.RetInt(0)
|
|
return
|
|
}
|
|
|
|
// Handle escape sequences (arrow keys etc.)
|
|
if ch == 27 { // ESC
|
|
// Try reading more for escape sequence
|
|
if reader.Buffered() > 0 {
|
|
ch2, _ := reader.ReadByte()
|
|
if ch2 == '[' {
|
|
ch3, _ := reader.ReadByte()
|
|
switch ch3 {
|
|
case 'A':
|
|
t.RetInt(5) // K_UP
|
|
return
|
|
case 'B':
|
|
t.RetInt(24) // K_DOWN
|
|
return
|
|
case 'C':
|
|
t.RetInt(4) // K_RIGHT
|
|
return
|
|
case 'D':
|
|
t.RetInt(19) // K_LEFT
|
|
return
|
|
}
|
|
}
|
|
}
|
|
t.RetInt(27) // K_ESC
|
|
return
|
|
}
|
|
|
|
t.RetInt(int64(ch))
|
|
}
|
|
|
|
// --- Color ---
|
|
|
|
// SetColor sets screen colors.
|
|
// Harbour: SetColor(cColorString)
|
|
// Simplified: map to ANSI colors.
|
|
func SetColor(t *hbrt.Thread) {
|
|
nParams := t.ParamCount()
|
|
t.Frame(nParams, 0)
|
|
defer t.EndProc()
|
|
if nParams > 0 {
|
|
// Parse Harbour color string like "W+/B" (white on blue)
|
|
// For now, just reset
|
|
_ = t.Local(1).AsString()
|
|
}
|
|
t.RetNil()
|
|
}
|
|
|
|
// --- Cursor ---
|
|
|
|
// SetCursor sets cursor shape.
|
|
// Harbour: SetCursor(nCursorShape)
|
|
func SetCursor(t *hbrt.Thread) {
|
|
nParams := t.ParamCount()
|
|
t.Frame(nParams, 0)
|
|
defer t.EndProc()
|
|
if nParams > 0 {
|
|
shape := int(t.Local(1).AsNumInt())
|
|
if shape == 0 {
|
|
fmt.Print("\033[?25l") // hide
|
|
} else {
|
|
fmt.Print("\033[?25h") // show
|
|
}
|
|
}
|
|
t.RetInt(1) // return old cursor
|
|
}
|
|
|
|
// MaxRow returns max screen row.
|
|
func MaxRow(t *hbrt.Thread) {
|
|
t.Frame(0, 0)
|
|
defer t.EndProc()
|
|
t.RetInt(24) // standard 25 rows, 0-based
|
|
}
|
|
|
|
// MaxCol returns max screen col.
|
|
func MaxCol(t *hbrt.Thread) {
|
|
t.Frame(0, 0)
|
|
defer t.EndProc()
|
|
t.RetInt(79) // standard 80 cols, 0-based
|
|
}
|
|
|
|
// --- String display helpers ---
|
|
|
|
// DispOut outputs string at current position (like DevOut).
|
|
func DispOut(t *hbrt.Thread) {
|
|
DevOut(t)
|
|
}
|
|
|
|
// DispBox draws a box. Simplified version.
|
|
// Harbour: DispBox(nTop, nLeft, nBottom, nRight, cChars)
|
|
func DispBox(t *hbrt.Thread) {
|
|
nParams := t.ParamCount()
|
|
t.Frame(nParams, 0)
|
|
defer t.EndProc()
|
|
|
|
nTop := int(t.Local(1).AsNumInt())
|
|
nLeft := int(t.Local(2).AsNumInt())
|
|
nBottom := int(t.Local(3).AsNumInt())
|
|
nRight := int(t.Local(4).AsNumInt())
|
|
|
|
width := nRight - nLeft + 1
|
|
if width < 2 {
|
|
width = 2
|
|
}
|
|
|
|
// Draw top border
|
|
fmt.Printf("\033[%d;%dH", nTop+1, nLeft+1)
|
|
fmt.Print("+" + strings.Repeat("-", width-2) + "+")
|
|
|
|
// Draw sides
|
|
for r := nTop + 1; r < nBottom; r++ {
|
|
fmt.Printf("\033[%d;%dH", r+1, nLeft+1)
|
|
fmt.Print("|" + strings.Repeat(" ", width-2) + "|")
|
|
}
|
|
|
|
// Draw bottom border
|
|
fmt.Printf("\033[%d;%dH", nBottom+1, nLeft+1)
|
|
fmt.Print("+" + strings.Repeat("-", width-2) + "+")
|
|
|
|
t.RetNil()
|
|
}
|