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>
This commit is contained in:
2026-03-31 09:41:50 +09:00
commit 59568f3301
282 changed files with 66658 additions and 0 deletions

238
examples/basic_test.prg Normal file
View File

@@ -0,0 +1,238 @@
// Five 기본 기능 전수 테스트
// 하나라도 실패하면 기초가 부족한 것
FUNCTION Main()
LOCAL nPass := 0, nFail := 0
? "========================================="
? " Five Basic Feature Test"
? "========================================="
? ""
// 1. 변수와 대입
? "--- 1. Variables ---"
LOCAL a := 10, b := 20, c
c := a + b
nPass += Assert("LOCAL assign", c, 30)
c := "hello"
nPass += Assert("re-assign type", ValType(c), "C")
// 2. 모든 타입
? "--- 2. Types ---"
nPass += Assert("Integer", ValType(42), "N")
nPass += Assert("Double", ValType(3.14), "N")
nPass += Assert("String", ValType("abc"), "C")
nPass += Assert("Logical", ValType(.T.), "L")
nPass += Assert("NIL", ValType(NIL), "U")
nPass += Assert("Array", ValType({1,2}), "A")
nPass += Assert("Block", ValType({|| 1}), "B")
nPass += Assert("Hash", ValType({"a" => 1}), "H")
// 3. 산술 — 모든 연산자
? "--- 3. Arithmetic ---"
nPass += Assert("2+3", 2+3, 5)
nPass += Assert("10-7", 10-7, 3)
nPass += Assert("6*7", 6*7, 42)
nPass += Assert("10%3", 10%3, 1)
nPass += Assert("2**3", 2**3, 8)
nPass += Assert("-5 negate", -(-5), 5)
LOCAL n := 10
n++
nPass += Assert("n++ postfix", n, 11)
n--
nPass += Assert("n-- postfix", n, 10)
n += 5
nPass += Assert("n += 5", n, 15)
n -= 3
nPass += Assert("n -= 3", n, 12)
n *= 2
nPass += Assert("n *= 2", n, 24)
// 4. 비교 — 모든 연산자
? "--- 4. Comparison ---"
nPass += Assert("1=1", 1=1, .T.)
nPass += Assert("1=2", 1=2, .F.)
nPass += Assert("1==1", 1==1, .T.)
nPass += Assert("1!=2", 1!=2, .T.)
nPass += Assert("1<2", 1<2, .T.)
nPass += Assert("2>1", 2>1, .T.)
nPass += Assert("1<=1", 1<=1, .T.)
nPass += Assert("1>=2", 1>=2, .F.)
nPass += Assert("str =", "abc"="abc", .T.)
nPass += Assert("str <", "abc"<"def", .T.)
// 5. 논리
? "--- 5. Logical ---"
nPass += Assert(".T. .AND. .T.", .T. .AND. .T., .T.)
nPass += Assert(".T. .AND. .F.", .T. .AND. .F., .F.)
nPass += Assert(".F. .OR. .T.", .F. .OR. .T., .T.)
nPass += Assert(".NOT. .T.", .NOT. .T., .F.)
// 6. 문자열 함수 — 전부
? "--- 6. String Functions ---"
nPass += Assert("Len", Len("abc"), 3)
nPass += Assert("Upper", Upper("hello"), "HELLO")
nPass += Assert("Lower", Lower("ABC"), "abc")
nPass += Assert("SubStr", SubStr("abcde", 2, 3), "bcd")
nPass += Assert("Left", Left("abcde", 3), "abc")
nPass += Assert("Right", Right("abcde", 3), "cde")
nPass += Assert("AllTrim", AllTrim(" hi "), "hi")
nPass += Assert("Space", Len(Space(10)), 10)
nPass += Assert("Replicate", Replicate("ab", 3), "ababab")
nPass += Assert("At", At("cd", "abcde"), 3)
nPass += Assert("At notfound", At("zz", "abc"), 0)
nPass += Assert("Asc", Asc("A"), 65)
nPass += Assert("Chr", Chr(65), "A")
nPass += Assert("StrTran", StrTran("hello", "l", "r"), "herro")
nPass += Assert("PadR", Len(PadR("ab", 10)), 10)
nPass += Assert("PadL", Len(PadL("ab", 10)), 10)
nPass += Assert("PadC", Len(PadC("ab", 10)), 10)
// 7. 수학 함수
? "--- 7. Math Functions ---"
nPass += Assert("Abs(-5)", Abs(-5), 5)
nPass += Assert("Int(3.9)", Int(3.9), 3)
nPass += Assert("Round(2.555,2)", Round(2.555, 2), 2.56)
nPass += Assert("Max(3,7)", Max(3, 7), 7)
nPass += Assert("Min(3,7)", Min(3, 7), 3)
nPass += Assert("Mod(10,3)", Mod(10, 3), 1)
nPass += Assert("Sqrt(9)", Sqrt(9), 3)
// 8. 타입 변환
? "--- 8. Conversions ---"
nPass += Assert("Val('123')", Val("123"), 123)
nPass += Assert("Empty('')", Empty(""), .T.)
nPass += Assert("Empty(0)", Empty(0), .T.)
nPass += Assert("Empty(.F.)", Empty(.F.), .T.)
nPass += Assert("Empty(1)", Empty(1), .F.)
// 9. 배열 — 전부
? "--- 9. Array ---"
LOCAL arr := {10, 20, 30}
nPass += Assert("arr[1]", arr[1], 10)
nPass += Assert("arr[3]", arr[3], 30)
nPass += Assert("Len(arr)", Len(arr), 3)
AAdd(arr, 40)
nPass += Assert("AAdd", Len(arr), 4)
nPass += Assert("ATail", ATail(arr), 40)
nPass += Assert("AScan found", AScan(arr, 20), 2)
nPass += Assert("AScan not", AScan(arr, 99), 0)
LOCAL sorted := {30, 10, 20}
ASort(sorted)
nPass += Assert("ASort[1]", sorted[1], 10)
nPass += Assert("ASort[3]", sorted[3], 30)
LOCAL cloned := AClone(arr)
nPass += Assert("AClone len", Len(cloned), Len(arr))
// 10. 해시
? "--- 10. Hash ---"
LOCAL h := {"name" => "Kim", "age" => 30}
nPass += Assert("Hash get", hb_HGet(h, "name"), "Kim")
nPass += Assert("HHasKey T", hb_HHasKey(h, "age"), .T.)
nPass += Assert("HHasKey F", hb_HHasKey(h, "xyz"), .F.)
hb_HSet(h, "city", "Seoul")
nPass += Assert("HSet", hb_HGet(h, "city"), "Seoul")
hb_HDel(h, "age")
nPass += Assert("HDel", hb_HHasKey(h, "age"), .F.)
// 11. 제어 흐름
? "--- 11. Control Flow ---"
// IF/ELSEIF/ELSE
LOCAL res := TestIf(100)
nPass += Assert("IF big", res, "big")
res := TestIf(5)
nPass += Assert("IF mid", res, "mid")
res := TestIf(-1)
nPass += Assert("IF small", res, "small")
// FOR
LOCAL sum := 0
FOR n := 1 TO 10
sum += n
NEXT
nPass += Assert("FOR sum", sum, 55)
// FOR EACH
sum := 0
FOR EACH n IN {1, 2, 3, 4, 5}
sum += n
NEXT
nPass += Assert("FOREACH sum", sum, 15)
// DO WHILE
n := 0
sum := 0
DO WHILE n < 5
n++
sum += n
ENDDO
nPass += Assert("WHILE sum", sum, 15)
// EXIT/LOOP
sum := 0
FOR n := 1 TO 100
IF n > 5
EXIT
ENDIF
sum += n
NEXT
nPass += Assert("EXIT", sum, 15)
// 12. 함수
? "--- 12. Functions ---"
nPass += Assert("call", Double(21), 42)
nPass += Assert("nested", Double(Double(5)), 20)
nPass += Assert("recursion", Factorial(5), 120)
nPass += Assert("multi-return", Add3(1, 2, 3), 6)
// 13. 코드 블록
? "--- 13. Code Blocks ---"
LOCAL bAdd := {|a,b| a + b}
nPass += Assert("Eval block", Eval(bAdd, 3, 4), 7)
LOCAL bSquare := {|x| x * x}
nPass += Assert("Eval square", Eval(bSquare, 5), 25)
// 14. 날짜
? "--- 14. Date ---"
LOCAL d := SToD("20260328")
nPass += Assert("Year", Year(d), 2026)
nPass += Assert("Month", Month(d), 3)
nPass += Assert("Day", Day(d), 28)
// Summary
? ""
? "========================================="
? " PASS:", nPass
? "========================================="
RETURN NIL
FUNCTION Assert(cDesc, xGot, xExpected)
IF ValType(xGot) = ValType(xExpected) .AND. xGot = xExpected
RETURN 1
ENDIF
? " FAIL:", cDesc
? " Got:", xGot
? " Exp:", xExpected
RETURN 0
FUNCTION TestIf(n)
IF n > 50
RETURN "big"
ELSEIF n > 0
RETURN "mid"
ELSE
RETURN "small"
ENDIF
FUNCTION Double(x)
RETURN x * 2
FUNCTION Factorial(n)
IF n <= 1
RETURN 1
ENDIF
RETURN n * Factorial(n - 1)
FUNCTION Add3(a, b, c)
RETURN a + b + c

28
examples/browse.prg Normal file
View File

@@ -0,0 +1,28 @@
// Five dbEdit demo — browse customer.dbf
// Build: five build examples/browse.prg -o browse
// Run: ./browse
FUNCTION Main()
LOCAL cPath := "dbf/customer"
? "Opening customer.dbf..."
USE cPath
? "Records:", RecCount()
? "Fields:", FCount()
? ""
? "Press any key to start dbEdit..."
? "(Use arrows, PgUp/PgDn, Home/End, ESC to exit)"
Inkey(0)
CLS
dbEdit(0, 0, 23, 79)
? ""
? "Done!"
USE
RETURN NIL

302
examples/browse_demo.go Normal file
View File

@@ -0,0 +1,302 @@
// 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 ""
}

View File

@@ -0,0 +1,140 @@
// CLASS 기능 전수 테스트
// TBrowse 포팅에 필요한 모든 CLASS 기능
CLASS Counter
DATA nValue INIT 0
DATA nStep INIT 1
DATA cName INIT "default"
METHOD New(cName, nStep)
METHOD Inc()
METHOD Dec()
METHOD GetValue()
METHOD SetValue(n)
METHOD Reset()
METHOD ToString()
ENDCLASS
METHOD New(cName, nStep) CLASS Counter
::cName := cName
IF nStep != NIL
::nStep := nStep
ENDIF
RETURN Self
METHOD Inc() CLASS Counter
::nValue += ::nStep
RETURN Self
METHOD Dec() CLASS Counter
::nValue -= ::nStep
RETURN Self
METHOD GetValue() CLASS Counter
RETURN ::nValue
METHOD SetValue(n) CLASS Counter
::nValue := n
RETURN Self
METHOD Reset() CLASS Counter
::nValue := 0
RETURN Self
METHOD ToString() CLASS Counter
RETURN ::cName + "=" + Str(::nValue)
// Inheritance test
CLASS StepCounter INHERIT FROM Counter
DATA nMaxValue INIT 100
METHOD Inc()
METHOD IsMax()
ENDCLASS
METHOD Inc() CLASS StepCounter
IF ::nValue + ::nStep <= ::nMaxValue
::nValue += ::nStep
ENDIF
RETURN Self
METHOD IsMax() CLASS StepCounter
RETURN ::nValue >= ::nMaxValue
FUNCTION Main()
LOCAL o, o2, nPass := 0
? "=== CLASS Full Test ==="
? ""
// 1. Basic construction
? "--- 1. Construction ---"
o := Counter():New("test", 5)
nPass += Assert("New name", o:cName, "test")
nPass += Assert("New step", o:nStep, 5)
nPass += Assert("Init value", o:nValue, 0)
// 2. Method calls
? "--- 2. Methods ---"
o:Inc()
nPass += Assert("Inc once", o:GetValue(), 5)
o:Inc()
o:Inc()
nPass += Assert("Inc 3x", o:GetValue(), 15)
o:Dec()
nPass += Assert("Dec", o:GetValue(), 10)
// 3. Method chaining (RETURN Self)
? "--- 3. Chaining ---"
o:Reset():Inc():Inc()
nPass += Assert("Chain reset+inc+inc", o:GetValue(), 10)
// 4. SetValue + ToString
? "--- 4. Setters ---"
o:SetValue(42)
nPass += Assert("SetValue", o:GetValue(), 42)
nPass += Assert("ToString", o:ToString(), "test=42")
// 5. Multiple instances
? "--- 5. Multiple instances ---"
o2 := Counter():New("other", 10)
o2:Inc():Inc():Inc()
nPass += Assert("Instance 2", o2:GetValue(), 30)
nPass += Assert("Instance 1 unchanged", o:GetValue(), 42)
// 6. Inheritance
? "--- 6. Inheritance ---"
LOCAL oStep := StepCounter():New("step", 25)
oStep:nMaxValue := 50
oStep:Inc()
nPass += Assert("StepCounter inc", oStep:GetValue(), 25)
oStep:Inc()
nPass += Assert("StepCounter inc 2", oStep:GetValue(), 50)
oStep:Inc()
nPass += Assert("StepCounter max", oStep:GetValue(), 50)
nPass += Assert("IsMax", oStep:IsMax(), .T.)
// 7. Inherited method
nPass += Assert("Inherited ToString", oStep:ToString(), "step=50")
// 8. Field access as getter
? "--- 7. Field access ---"
nPass += Assert("Field getter", o:cName, "test")
nPass += Assert("Field getter 2", oStep:nMaxValue, 50)
// Summary
? ""
? "========================"
? " CLASS PASS:", nPass
? "========================"
RETURN NIL
FUNCTION Assert(cDesc, xGot, xExpected)
IF ValType(xGot) = ValType(xExpected) .AND. xGot = xExpected
RETURN 1
ENDIF
? " FAIL:", cDesc
? " Got:", xGot
? " Exp:", xExpected
RETURN 0

17
examples/class_test.prg Normal file
View File

@@ -0,0 +1,17 @@
FUNCTION Main()
? "=== Five CLASS Test ==="
? ""
? "CLASS system features:"
? " - DATA fields with INIT defaults"
? " - METHOD dispatch (obj:method())"
? " - :: Self access in methods"
? " - INHERIT FROM (parent class)"
? " - Operator overloading"
? " - Field getter/setter (obj:field, obj:_field := val)"
? ""
? "Macro system features:"
? " - &variable (runtime name resolution)"
? " - &(expression) (runtime expression evaluation)"
? ""
? "CLASS + Macro ready!"
RETURN NIL

31
examples/dbedit_debug.prg Normal file
View File

@@ -0,0 +1,31 @@
// Minimal debug: just stabilize once and print
FUNCTION Main()
LOCAL oBrowse, oCol
USE "dbf/customer"
? "Step 1: USE OK, records:", RecCount()
oBrowse := TBrowseDB(2, 0, 22, 79)
? "Step 2: TBrowse created"
oCol := TBColumnNew("ID", {|| FieldGet(1)})
oBrowse:addColumn(oCol)
? "Step 3: Column added"
oCol := TBColumnNew("FIRST", {|| FieldGet(2)})
oBrowse:addColumn(oCol)
? "Step 4: Column 2 added, count:", oBrowse:colCount()
? "Step 5: Calling stabilize..."
oBrowse:stabilize()
? "Step 6: stabilize done"
? "Step 7: Calling Inkey..."
Inkey(0)
? "Step 8: Inkey returned"
USE
? "Done!"
RETURN NIL

69
examples/dbedit_main.prg Normal file
View File

@@ -0,0 +1,69 @@
// dbEdit using compiled TBrowse — no ? output before browse
FUNCTION Main()
LOCAL oBrowse, oCol, nKey
USE "dbf/customer"
oBrowse := TBrowseDB(1, 0, 22, 79)
oCol := TBColumnNew("ID", {|| FieldGet(1)})
oBrowse:addColumn(oCol)
oCol := TBColumnNew("FIRST", {|| FieldGet(2)})
oBrowse:addColumn(oCol)
oCol := TBColumnNew("LAST", {|| FieldGet(3)})
oBrowse:addColumn(oCol)
oCol := TBColumnNew("STREET", {|| FieldGet(4)})
oBrowse:addColumn(oCol)
oCol := TBColumnNew("CITY", {|| FieldGet(5)})
oBrowse:addColumn(oCol)
oCol := TBColumnNew("STATE", {|| FieldGet(6)})
oBrowse:addColumn(oCol)
oCol := TBColumnNew("ZIP", {|| FieldGet(7)})
oBrowse:addColumn(oCol)
oCol := TBColumnNew("HIREDATE", {|| FieldGet(8)})
oBrowse:addColumn(oCol)
oCol := TBColumnNew("MARRIED", {|| FieldGet(9)})
oBrowse:addColumn(oCol)
oCol := TBColumnNew("AGE", {|| FieldGet(10)})
oBrowse:addColumn(oCol)
oCol := TBColumnNew("SALARY", {|| FieldGet(11)})
oBrowse:addColumn(oCol)
oCol := TBColumnNew("NOTES", {|| FieldGet(12)})
oBrowse:addColumn(oCol)
CLS
SetCursor(0)
SetPos(0, 0)
DevOut("customer.dbf - 500 records - ESC to quit")
DO WHILE .T.
oBrowse:forceStable()
nKey := Inkey(0)
DO CASE
CASE nKey = 5
oBrowse:up()
CASE nKey = 24
oBrowse:down()
CASE nKey = 19
oBrowse:left()
CASE nKey = 4
oBrowse:right()
CASE nKey = 18
oBrowse:pageUp()
CASE nKey = 3
oBrowse:pageDown()
CASE nKey = 1
oBrowse:goTop()
CASE nKey = 6
oBrowse:goBottom()
CASE nKey = 27
EXIT
ENDCASE
ENDDO
CLS
SetCursor(1)
USE
RETURN NIL

10
examples/dbedit_prg.prg Normal file
View File

@@ -0,0 +1,10 @@
// Five dbEdit demo — compiled through gengo
FUNCTION Main()
USE "dbf/customer"
dbEdit(0, 0, 22, 79)
USE
RETURN NIL

18
examples/dbf_test.prg Normal file
View File

@@ -0,0 +1,18 @@
FUNCTION Main()
? "=== Five DBF Test ==="
? "Creating test database..."
// TODO: USE/CREATE integration in generated code needs
// WorkAreaManager initialization in main().
// For now, test via unit tests in hbrdd/dbf/.
? "DBF engine ready!"
? " - DBF file format: byte-compatible with Harbour"
? " - Field types: C, N, L, D, M, I, B, @, +, =, ^, Y (19 types)"
? " - NTX index: B-tree SEEK, SKIP, GoTop/Bottom, INDEX ON"
? " - CDX index: bit-packed compression, compound tags, linked leaves"
? " - FPT memo: Big-Endian header, block read/write"
? " - 6 lock schemes: Clipper, Clipper2, VFP, VFPX, HB32, HB64"
? ""
? "All DBF components implemented!"
RETURN NIL

66
examples/dbfview.prg Normal file
View File

@@ -0,0 +1,66 @@
// Five DBF Viewer — browse database with TBrowse
// Usage: ./dbfview (opens dbf/customer.dbf)
// Keys: Up/Down/Left/Right PgUp/PgDn Home/End ESC=quit
// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
FUNCTION Main()
LOCAL oBrowse, oCol, nKey, i, nFields
CLS
SetCursor(0)
USE "dbf/customer"
nFields := FCount()
IF nFields = 0
? "Cannot open database"
Inkey(0)
RETURN NIL
ENDIF
// Title
@ 0, 0 SAY PadR("customer.dbf - " + AllTrim(Str(RecCount())) + " records, " + AllTrim(Str(nFields)) + " fields - ESC to quit", 80)
// Build browse
oBrowse := TBrowseDB(1, 0, 22, 79)
FOR i := 1 TO nFields
oCol := TBColumnNew(FieldName(i), FieldBlock(i))
oBrowse:addColumn(oCol)
NEXT
// Browse loop
DO WHILE .T.
oBrowse:forceStable()
// Status
@ 23, 0 SAY PadR("Rec:" + AllTrim(Str(RecNo())) + "/" + AllTrim(Str(RecCount())) + IIF(Eof()," EOF","") + IIF(Deleted()," DEL",""), 80)
nKey := Inkey(0)
DO CASE
CASE nKey = 5
oBrowse:up()
CASE nKey = 24
oBrowse:down()
CASE nKey = 19
oBrowse:left()
CASE nKey = 4
oBrowse:right()
CASE nKey = 18
oBrowse:pageUp()
CASE nKey = 3
oBrowse:pageDown()
CASE nKey = 1
oBrowse:goTop()
CASE nKey = 6
oBrowse:goBottom()
CASE nKey = 27
EXIT
ENDCASE
ENDDO
CLS
SetCursor(1)
USE
RETURN NIL

11
examples/debug2.prg Normal file
View File

@@ -0,0 +1,11 @@
FUNCTION Main()
? "Test 1:", MyTest("hello", "hello")
? "Test 2:", MyTest(42, 42)
? "Test 3:", MyTest(.T., .T.)
RETURN NIL
FUNCTION MyTest(a, b)
IF a = b
RETURN "PASS"
ENDIF
RETURN "FAIL"

18
examples/debug3.prg Normal file
View File

@@ -0,0 +1,18 @@
FUNCTION Main()
LOCAL x
x := MyTest("hello", "hello")
? "Result:", x
RETURN NIL
FUNCTION MyTest(a, b)
? "a:", a
? "b:", b
? "type a:", ValType(a)
? "type b:", ValType(b)
IF a = b
? "EQUAL"
RETURN "PASS"
ELSE
? "NOT EQUAL"
ENDIF
RETURN "FAIL"

21
examples/debug_test.prg Normal file
View File

@@ -0,0 +1,21 @@
FUNCTION Main()
LOCAL a, b
a := "N"
b := "N"
? "a =", a
? "b =", b
IF a = b
? "a = b: TRUE"
ELSE
? "a = b: FALSE"
ENDIF
IF ValType(42) = "N"
? "ValType test: TRUE"
ELSE
? "ValType test: FALSE"
ENDIF
RETURN NIL

102
examples/frb_demo.prg Normal file
View File

@@ -0,0 +1,102 @@
// Five FRB (Five Runtime Binary) Demo
// Shows all FRB capabilities: file-based, in-memory compile, one-shot
// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
//
// Build: five build examples/frb_demo.prg -o frb_demo
// Prep: five frb examples/frb_mathlib.prg -o mathlib.frb
// Run: ./frb_demo
FUNCTION Main()
? "========================================="
? " Five FRB (Five Runtime Binary) Demo"
? "========================================="
? ""
// -----------------------------------------------
// 1. Load pre-compiled FRB from file
// -----------------------------------------------
? "--- 1. File-based FRB ---"
? ""
LOCAL pMath := FrbLoad("mathlib.frb")
IF pMath = NIL
? " (skipped: mathlib.frb not found)"
? " Build it: five frb examples/frb_mathlib.prg -o mathlib.frb"
ELSE
? " Loaded mathlib.frb"
? " CircleArea(5.0) =", FrbDo(pMath, "CIRCLEAREA", 5.0)
? " Fibonacci(10) =", FrbDo(pMath, "FIBONACCI", 10)
? " IsPrime(97) =", FrbDo(pMath, "ISPRIME", 97)
FrbUnload(pMath)
? " Unloaded."
ENDIF
? ""
// -----------------------------------------------
// 2. Compile PRG source at runtime (in-memory)
// -----------------------------------------------
? "--- 2. In-Memory Compilation ---"
? ""
LOCAL cSource := ;
'FUNCTION Reverse(cStr)' + Chr(10) + ;
' LOCAL i, cResult := ""' + Chr(10) + ;
' FOR i := Len(cStr) TO 1 STEP -1' + Chr(10) + ;
' cResult += SubStr(cStr, i, 1)' + Chr(10) + ;
' NEXT' + Chr(10) + ;
' RETURN cResult' + Chr(10) + ;
'FUNCTION Repeat(cStr, n)' + Chr(10) + ;
' RETURN Replicate(cStr, n)' + Chr(10)
? " Compiling PRG source at runtime..."
LOCAL pStr := FrbCompile(cSource)
IF pStr != NIL
? " Reverse('Hello') =", FrbDo(pStr, "REVERSE", "Hello")
? " Repeat('Go!', 3) =", FrbDo(pStr, "REPEAT", "Go!", 3)
FrbUnload(pStr)
? " Unloaded."
ELSE
? " ERROR: Compile failed"
ENDIF
? ""
// -----------------------------------------------
// 3. One-shot: compile + run + unload
// -----------------------------------------------
? "--- 3. One-Shot FrbExec ---"
? ""
LOCAL cProgram := ;
'FUNCTION Main()' + Chr(10) + ;
' LOCAL i, nSum := 0' + Chr(10) + ;
' FOR i := 1 TO 100' + Chr(10) + ;
' nSum += i' + Chr(10) + ;
' NEXT' + Chr(10) + ;
' RETURN nSum' + Chr(10)
? " Sum of 1..100 =", FrbExec(cProgram)
? ""
// -----------------------------------------------
// 4. Dynamic code with goroutine
// -----------------------------------------------
? "--- 4. Dynamic Code + Goroutine ---"
? ""
LOCAL cAsync := ;
'FUNCTION Worker(ch, n)' + Chr(10) + ;
' ChSend(ch, n * n)' + Chr(10) + ;
' RETURN NIL' + Chr(10)
LOCAL pAsync := FrbCompile(cAsync)
IF pAsync != NIL
LOCAL ch := Channel(1)
Go("WORKER", ch, 7)
? " 7^2 from dynamic goroutine =", ChReceive(ch)
FrbUnload(pAsync)
ENDIF
? ""
? "========================================="
? " Done! PRG code compiled at runtime"
? " and executed at native Go speed."
? "========================================="
RETURN NIL

53
examples/frb_mathlib.prg Normal file
View File

@@ -0,0 +1,53 @@
// FRB Math Library — pre-compiled module loaded at runtime
// Build: five frb examples/frb_mathlib.prg -o mathlib.frb
// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
FUNCTION CircleArea(nRadius)
RETURN 3.14159265 * nRadius * nRadius
FUNCTION Fibonacci(n)
LOCAL a := 0, b := 1, i, temp
IF n <= 0
RETURN 0
ENDIF
IF n = 1
RETURN 1
ENDIF
FOR i := 2 TO n
temp := a + b
a := b
b := temp
NEXT
RETURN b
FUNCTION IsPrime(n)
LOCAL i
IF n < 2
RETURN .F.
ENDIF
IF n = 2
RETURN .T.
ENDIF
IF n % 2 = 0
RETURN .F.
ENDIF
FOR i := 3 TO Int(Sqrt(n)) STEP 2
IF n % i = 0
RETURN .F.
ENDIF
NEXT
RETURN .T.
FUNCTION Factorial(n)
IF n <= 1
RETURN 1
ENDIF
RETURN n * Factorial(n - 1)
FUNCTION GCD(a, b)
DO WHILE b != 0
LOCAL temp := b
b := a % b
a := temp
ENDDO
RETURN a

14
examples/frb_module.prg Normal file
View File

@@ -0,0 +1,14 @@
// FRB test module — loaded at runtime
// Compile: five frb examples/frb_module.prg -o mylib.frb
FUNCTION Hello(cName)
RETURN "Hello, " + cName + "! (from FRB module)"
FUNCTION Add(a, b)
RETURN a + b
FUNCTION Factorial(n)
IF n <= 1
RETURN 1
ENDIF
RETURN n * Factorial(n - 1)

20
examples/functions.prg Normal file
View File

@@ -0,0 +1,20 @@
FUNCTION Double(n)
RETURN n * 2
FUNCTION Add(a, b)
RETURN a + b
FUNCTION Main()
LOCAL result
result := Double(21)
? "Double(21) =", result
result := Add(10, 20)
? "Add(10,20) =", result
result := Double(Add(3, 4))
? "Double(Add(3,4)) =", result
? "Done!"
RETURN NIL

493
examples/get_five.prg Normal file
View File

@@ -0,0 +1,493 @@
// Five GET System — simplified port of Harbour tget.prg + tgetlist.prg
// Compiles via gengo to native binary
// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
// GetNew(nRow, nCol, bBlock, cVarName, cPicture, cColorSpec) — create Get object
// Harbour pattern: bBlock = {|x| IIF(x == NIL, var, var := x)}
FUNCTION GetNew(nRow, nCol, bBlock, cVarName, cPicture, cColorSpec)
LOCAL oGet, xVal
IF nRow = NIL
nRow := Row()
ENDIF
IF nCol = NIL
nCol := Col()
ENDIF
// Get current value from block
xVal := Eval(bBlock)
oGet := Get():New()
oGet:nRow := nRow
oGet:nCol := nCol
oGet:bBlock := bBlock
oGet:cName := cVarName
oGet:cPicture := cPicture
oGet:xOriginal := xVal
oGet:cType := ValType(xVal)
oGet:nPos := 1
oGet:lChanged := .F.
oGet:lClear := .F.
oGet:lHasFocus := .F.
oGet:xExitState := 0
oGet:bPostBlock := NIL
oGet:bPreBlock := NIL
IF cColorSpec != NIL
oGet:cColorSpec := cColorSpec
ELSE
oGet:cColorSpec := "W/N,W+/B"
ENDIF
// Build display buffer
IF cPicture != NIL .AND. Len(cPicture) > 0
oGet:cBuffer := Transform(xVal, cPicture)
ELSE
oGet:cBuffer := __GetDefaultBuffer(xVal)
ENDIF
oGet:nDispLen := Len(oGet:cBuffer)
RETURN oGet
// Default buffer: format value for editing
FUNCTION __GetDefaultBuffer(xVal)
LOCAL cType := ValType(xVal)
IF cType = "C"
RETURN xVal
ELSEIF cType = "N"
RETURN Str(xVal)
ELSEIF cType = "D"
RETURN DToC(xVal)
ELSEIF cType = "L"
IF xVal
RETURN "T"
ELSE
RETURN "F"
ENDIF
ENDIF
RETURN ""
// === Get Class ===
CLASS Get
DATA nRow INIT 0
DATA nCol INIT 0
DATA bBlock
DATA cName INIT ""
DATA cPicture
DATA cType INIT "C"
DATA cBuffer INIT ""
DATA nPos INIT 1
DATA nDispLen INIT 0
DATA lChanged INIT .F.
DATA lClear INIT .F.
DATA lHasFocus INIT .F.
DATA xOriginal
DATA bPostBlock
DATA bPreBlock
DATA cColorSpec INIT "W/N,W+/B"
DATA xExitState INIT 0
METHOD New()
METHOD input(cChar)
METHOD display()
METHOD setFocus()
METHOD killFocus()
METHOD varGet()
METHOD varPut(xValue)
METHOD assign()
METHOD unTransform()
METHOD updateBuffer()
METHOD insert(cChar)
METHOD overStrike(cChar)
METHOD backSpace()
METHOD delete()
METHOD home()
METHOD end()
METHOD left()
METHOD right()
METHOD toDecPos()
METHOD delEnd()
ENDCLASS
METHOD New() CLASS Get
RETURN Self
METHOD display() CLASS Get
SetPos(::nRow, ::nCol)
IF ::lHasFocus
DevOut(Chr(27) + "[7m" + ::cBuffer + Chr(27) + "[0m")
ELSE
DevOut(::cBuffer)
ENDIF
RETURN Self
METHOD setFocus() CLASS Get
::lHasFocus := .T.
::xOriginal := Eval(::bBlock)
::updateBuffer()
::nPos := 1
::lClear := .T.
::lChanged := .F.
::display()
SetPos(::nRow, ::nCol + ::nPos - 1)
SetCursor(1)
RETURN Self
METHOD killFocus() CLASS Get
IF ::lChanged
::assign()
ENDIF
::lHasFocus := .F.
::display()
SetCursor(0)
RETURN Self
METHOD varGet() CLASS Get
RETURN Eval(::bBlock)
METHOD varPut(xValue) CLASS Get
Eval(::bBlock, xValue)
RETURN xValue
METHOD assign() CLASS Get
LOCAL xVal
xVal := ::unTransform()
::varPut(xVal)
RETURN Self
METHOD unTransform() CLASS Get
LOCAL cBuf
cBuf := ::cBuffer
IF ::cType = "N"
cBuf := AllTrim(cBuf)
RETURN Val(cBuf)
ELSEIF ::cType = "D"
RETURN CToD(AllTrim(cBuf))
ELSEIF ::cType = "L"
cBuf := Upper(AllTrim(cBuf))
RETURN (cBuf = "T" .OR. cBuf = "Y" .OR. cBuf = ".T.")
ENDIF
RETURN cBuf
METHOD updateBuffer() CLASS Get
LOCAL xVal
xVal := Eval(::bBlock)
IF ::cPicture != NIL .AND. Len(::cPicture) > 0
::cBuffer := Transform(xVal, ::cPicture)
ELSE
::cBuffer := __GetDefaultBuffer(xVal)
ENDIF
IF ::nDispLen > 0 .AND. Len(::cBuffer) < ::nDispLen
::cBuffer := PadR(::cBuffer, ::nDispLen)
ENDIF
RETURN Self
// Input() — validate character based on field type and picture mask (Harbour compatible)
METHOD input(cChar) CLASS Get
LOCAL cPic
// Type-based filtering
IF ::cType = "N"
IF cChar = "-"
// minus allowed anywhere in numeric
ELSEIF cChar = "." .OR. cChar = ","
::toDecPos()
RETURN ""
ELSEIF !(cChar $ "0123456789+")
RETURN ""
ENDIF
ELSEIF ::cType = "D"
IF !(cChar $ "0123456789")
RETURN ""
ENDIF
ELSEIF ::cType = "L"
IF !(Upper(cChar) $ "YNTF")
RETURN ""
ENDIF
ENDIF
// Picture mask filtering
IF ::cPicture != NIL .AND. Len(::cPicture) > 0
IF Left(::cPicture, 1) = "@"
// Function picture — apply uppercase if @!
IF "!" $ Upper(::cPicture)
cChar := Upper(cChar)
ENDIF
ELSE
// Mask picture — check character at current position
IF ::nPos <= Len(::cPicture)
cPic := Upper(SubStr(::cPicture, ::nPos, 1))
IF cPic = "A"
IF !(cChar >= "A" .AND. cChar <= "Z") .AND. !(cChar >= "a" .AND. cChar <= "z")
RETURN ""
ENDIF
ELSEIF cPic = "9"
IF !(cChar >= "0" .AND. cChar <= "9") .AND. !(cChar $ "-+")
RETURN ""
ENDIF
IF !(::cType = "N") .AND. cChar $ "-+"
RETURN ""
ENDIF
ELSEIF cPic = "#"
IF !(cChar >= "0" .AND. cChar <= "9") .AND. cChar != " " .AND. !(cChar $ ".+-")
RETURN ""
ENDIF
ELSEIF cPic = "N"
IF !(cChar >= "A" .AND. cChar <= "Z") .AND. !(cChar >= "a" .AND. cChar <= "z") .AND. !(cChar >= "0" .AND. cChar <= "9")
RETURN ""
ENDIF
ELSEIF cPic = "!"
cChar := Upper(cChar)
ELSEIF cPic = "L" .OR. cPic = "Y"
IF !(Upper(cChar) $ "YNTF")
RETURN ""
ENDIF
ENDIF
// X = any character, pass through
ENDIF
ENDIF
ENDIF
RETURN cChar
METHOD insert(cChar) CLASS Get
LOCAL cLeft, cRight
cChar := ::input(Left(cChar, 1))
IF cChar = ""
RETURN Self
ENDIF
IF ::lClear
::cBuffer := Space(::nDispLen)
::nPos := 1
::lClear := .F.
ENDIF
IF ::nPos <= ::nDispLen
cLeft := Left(::cBuffer, ::nPos - 1) + cChar
cRight := SubStr(::cBuffer, ::nPos, ::nDispLen - ::nPos)
::cBuffer := Left(cLeft + cRight, ::nDispLen)
::nPos++
::lChanged := .T.
ENDIF
RETURN Self
METHOD overStrike(cChar) CLASS Get
cChar := ::input(Left(cChar, 1))
IF cChar = ""
RETURN Self
ENDIF
IF ::lClear
::cBuffer := Space(::nDispLen)
::nPos := 1
::lClear := .F.
ENDIF
IF ::nPos <= ::nDispLen
::cBuffer := Left(::cBuffer, ::nPos - 1) + cChar + SubStr(::cBuffer, ::nPos + 1)
::nPos++
::lChanged := .T.
ENDIF
RETURN Self
METHOD backSpace() CLASS Get
::lClear := .F.
IF ::nPos > 1
::nPos--
::cBuffer := Left(::cBuffer, ::nPos - 1) + SubStr(::cBuffer, ::nPos + 1) + " "
::lChanged := .T.
ENDIF
RETURN Self
METHOD delete() CLASS Get
::lClear := .F.
IF ::nPos <= ::nDispLen
::cBuffer := Left(::cBuffer, ::nPos - 1) + SubStr(::cBuffer, ::nPos + 1) + " "
::lChanged := .T.
ENDIF
RETURN Self
METHOD home() CLASS Get
::nPos := 1
::lClear := .F.
RETURN Self
METHOD end() CLASS Get
::nPos := Len(AllTrim(::cBuffer)) + 1
IF ::nPos > ::nDispLen
::nPos := ::nDispLen
ENDIF
::lClear := .F.
RETURN Self
METHOD left() CLASS Get
::lClear := .F.
IF ::nPos > 1
::nPos--
ENDIF
RETURN Self
METHOD right() CLASS Get
::lClear := .F.
IF ::nPos < ::nDispLen
::nPos++
ENDIF
RETURN Self
METHOD toDecPos() CLASS Get
LOCAL nDot
::lClear := .F.
nDot := At(".", ::cBuffer)
IF nDot > 0
::nPos := nDot + 1
ENDIF
RETURN Self
METHOD delEnd() CLASS Get
::lClear := .F.
IF ::nPos <= ::nDispLen
::cBuffer := Left(::cBuffer, ::nPos - 1) + Space(::nDispLen - ::nPos + 1)
::lChanged := .T.
ENDIF
RETURN Self
// === ReadModal — process GETLIST ===
FUNCTION ReadModal(aGetList)
LOCAL i, oGet, nKey, lDone, nLen, lInsert
nLen := Len(aGetList)
IF nLen = 0
RETURN .F.
ENDIF
lInsert := .F.
i := 1
lDone := .F.
oGet := aGetList[i]
// Pre-validate (WHEN)
IF oGet:bPreBlock != NIL
IF !Eval(oGet:bPreBlock)
RETURN .F.
ENDIF
ENDIF
oGet:setFocus()
DO WHILE !lDone
SetPos(oGet:nRow, oGet:nCol + oGet:nPos - 1)
SetCursor(1)
nKey := Inkey(0)
SetCursor(0)
DO CASE
CASE nKey = 13 .OR. nKey = 10 // Enter (CR or LF) — next field or exit
oGet:killFocus()
IF oGet:bPostBlock != NIL
IF !Eval(oGet:bPostBlock)
oGet:setFocus()
LOOP
ENDIF
ENDIF
i++
IF i > nLen
lDone := .T.
ELSE
oGet := aGetList[i]
IF oGet:bPreBlock != NIL
IF !Eval(oGet:bPreBlock)
i++
IF i > nLen
lDone := .T.
ELSE
oGet := aGetList[i]
ENDIF
LOOP
ENDIF
ENDIF
oGet:setFocus()
ENDIF
CASE nKey = 27 // ESC — abort
oGet:killFocus()
lDone := .T.
CASE nKey = 5 // Up — previous field
oGet:killFocus()
IF oGet:bPostBlock != NIL
IF !Eval(oGet:bPostBlock)
oGet:setFocus()
LOOP
ENDIF
ENDIF
IF i > 1
i--
oGet := aGetList[i]
oGet:setFocus()
ELSE
oGet:setFocus()
ENDIF
CASE nKey = 24 .OR. nKey = 9 // Down or Tab — next field
oGet:killFocus()
IF oGet:bPostBlock != NIL
IF !Eval(oGet:bPostBlock)
oGet:setFocus()
LOOP
ENDIF
ENDIF
i++
IF i > nLen
i := nLen
oGet := aGetList[i]
oGet:setFocus()
ELSE
oGet := aGetList[i]
oGet:setFocus()
ENDIF
CASE nKey = 19 // Left
oGet:left()
oGet:display()
CASE nKey = 4 // Right
oGet:right()
oGet:display()
CASE nKey = 1 // Home
oGet:home()
oGet:display()
CASE nKey = 6 // End
oGet:end()
oGet:display()
CASE nKey = 8 .OR. nKey = 127 // Backspace
oGet:backSpace()
oGet:display()
CASE nKey = 7 // Del
oGet:delete()
oGet:display()
CASE nKey = 25 // Ctrl+Y — delete to end
oGet:delEnd()
oGet:display()
CASE nKey = 22 // Ins — toggle insert
lInsert := !lInsert
CASE nKey >= 32 .AND. nKey <= 255 // Printable character
IF lInsert
oGet:insert(Chr(nKey))
ELSE
oGet:overStrike(Chr(nKey))
ENDIF
oGet:display()
ENDCASE
ENDDO
SetCursor(1)
RETURN .T.

139
examples/go_channel.prg Normal file
View File

@@ -0,0 +1,139 @@
// Five Channel Operators — Why ch <- and <- ch matter
//
// 기존: ChSend(ch, val) / ChReceive(ch) — 함수 호출
// 신규: ch <- val / <- ch — 연산자
//
// 연산자의 장점:
// 1. 짧다: ch <- val vs ChSend(ch, val)
// 2. 읽기 쉽다: 화살표 방향 = 데이터 흐름
// 3. WATCH와 자연스럽게 결합
// ====================================================
// 예제 1: 생산자-소비자 (Worker Pool)
// ====================================================
PROCEDURE Main()
LOCAL chJobs, chResults
LOCAL i, nSum, nResult
? "=== Producer-Consumer Pool ==="
chJobs := Channel()
chResults := Channel()
// Worker 3개 가동
SPAWN {|| Worker(chJobs, chResults) }
SPAWN {|| Worker(chJobs, chResults) }
SPAWN {|| Worker(chJobs, chResults) }
// 작업 10개 전송
SPAWN {|| Producer(chJobs, 10) }
// 결과 수집
nSum := 0
FOR i := 1 TO 10
nResult := <- chResults // 결과 수신
nSum += nResult
?? Str(nResult, 5)
NEXT
?
? "Total:", nSum
?
// ====================================================
// 예제 2: WATCH — 먼저 온 채널 선택
// ====================================================
? "=== Race: Fastest Server ==="
TestRace()
?
// ====================================================
// 예제 3: Pipeline (단계별 처리)
// ====================================================
? "=== Pipeline: x → x*2 → x+10 ==="
TestPipeline()
?
? "Done."
RETURN
// Worker: 채널에서 받아 제곱 후 전송
FUNCTION Worker(chIn, chOut)
LOCAL nJob
nJob := <- chIn // ← 작업 수신
chOut <- nJob * nJob // ← 결과 전송
RETURN NIL
// Producer: 숫자 n개를 채널로 전송
FUNCTION Producer(ch, nCount)
LOCAL i
FOR i := 1 TO nCount
ch <- i // ← 전송
NEXT
RETURN NIL
// Race: 여러 채널 중 먼저 도착한 것 선택
PROCEDURE TestRace()
LOCAL chA, chB, chTimeout, cResult
chA := Channel()
chB := Channel()
chTimeout := Channel()
SPAWN {|| DelayAndSend(0.1, chA, "Server A (100ms)") }
SPAWN {|| DelayAndSend(0.5, chB, "Server B (500ms)") }
SPAWN {|| DelayAndSend(1.0, chTimeout, "TIMEOUT") }
WATCH
CASE cResult := <- chA
? " Winner:", cResult
CASE cResult := <- chB
? " Winner:", cResult
CASE <- chTimeout
? " TIMEOUT!"
END WATCH
RETURN
// Pipeline: Stage1 → Stage2 → 출력
PROCEDURE TestPipeline()
LOCAL chStage1, chStage2
LOCAL i, nVal
chStage1 := Channel()
chStage2 := Channel()
// Stage 1: 숫자 생성
SPAWN {|| PipeGenerate(chStage1, 5) }
// Stage 2: 2배로 변환
SPAWN {|| PipeDouble(chStage1, chStage2, 5) }
// Stage 3: 결과 출력
FOR i := 1 TO 5
nVal := <- chStage2 // Stage2 결과 수신
? " Input:", i, " Output:", nVal
NEXT
RETURN
FUNCTION PipeGenerate(ch, n)
LOCAL i
FOR i := 1 TO n
ch <- i // 숫자 전송
NEXT
RETURN NIL
FUNCTION DelayAndSend(nSec, ch, cMsg)
Sleep(nSec)
ch <- cMsg
RETURN NIL
FUNCTION PipeDouble(chIn, chOut, n)
LOCAL i, v
FOR i := 1 TO n
v := <- chIn // 수신
chOut <- v * 2 // 2배 후 전송
NEXT
RETURN NIL

147
examples/go_concurrent.prg Normal file
View File

@@ -0,0 +1,147 @@
// Five Example: Concurrent Data Processing Pipeline
//
// Go goroutines + channels for parallel processing.
// 10만 건 레코드를 CPU 코어 수만큼 병렬 처리.
PROCEDURE Main()
LOCAL nRecords, aResult
nRecords := 100000
? "=== Five Concurrent Data Pipeline ==="
? "Processing", nRecords, "records with Go goroutines"
?
aResult := GoPipeline(nRecords)
? "Results:"
? " Total records:", aResult["total"]
? " Total amount: ", aResult["amount"]
? " Avg per record:", aResult["average"]
? " Max single: ", aResult["max"]
? " Min single: ", aResult["min"]
? " Processing ms:", aResult["elapsed_ms"]
? " Records/sec: ", aResult["throughput"]
?
? "Category breakdown:"
? " Electronics: ", aResult["cat_electronics"]
? " Clothing: ", aResult["cat_clothing"]
? " Food: ", aResult["cat_food"]
? " Books: ", aResult["cat_books"]
RETURN
#pragma BEGINDUMP
import (
"five/hbrt"
"fmt"
"math"
"math/rand"
"runtime"
"strings"
"sync"
"time"
)
func init() {
hbrt.HB_FUNC("GOPIPELINE", goPipeline)
}
type record struct {
id int
category string
amount float64
quantity int
}
type summary struct {
total int
amount float64
max, min float64
categories map[string]float64
}
func goPipeline(ctx *hbrt.HBContext) {
nRecords := ctx.ParNIDef(1, 100000)
numWorkers := runtime.NumCPU()
start := time.Now()
// Stage 1: Generate records (simulates DB read)
recordCh := make(chan record, 1000)
go func() {
categories := []string{"Electronics", "Clothing", "Food", "Books"}
for i := 0; i < nRecords; i++ {
recordCh <- record{
id: i + 1,
category: categories[rand.Intn(len(categories))],
amount: math.Round(rand.Float64()*1000*100) / 100,
quantity: rand.Intn(50) + 1,
}
}
close(recordCh)
}()
// Stage 2: Transform (parallel workers)
type transformed struct {
category string
total float64
}
transformCh := make(chan transformed, 1000)
var wg sync.WaitGroup
for w := 0; w < numWorkers; w++ {
wg.Add(1)
go func() {
defer wg.Done()
for r := range recordCh {
total := r.amount * float64(r.quantity)
total = math.Round(total*100) / 100
transformCh <- transformed{category: r.category, total: total}
}
}()
}
go func() {
wg.Wait()
close(transformCh)
}()
// Stage 3: Aggregate
sum := summary{
min: math.MaxFloat64,
categories: make(map[string]float64),
}
for t := range transformCh {
sum.total++
sum.amount += t.total
if t.total > sum.max {
sum.max = t.total
}
if t.total < sum.min {
sum.min = t.total
}
sum.categories[t.category] += t.total
}
elapsed := time.Since(start)
// Build result hash for PRG
result := ctx.HashNew()
ctx.HashAdd(result, hbrt.MakeString("total"), hbrt.MakeInt(sum.total))
ctx.HashAdd(result, hbrt.MakeString("amount"), hbrt.MakeDouble(sum.amount, 0, 0))
ctx.HashAdd(result, hbrt.MakeString("average"), hbrt.MakeDouble(sum.amount/float64(sum.total), 0, 0))
ctx.HashAdd(result, hbrt.MakeString("max"), hbrt.MakeDouble(sum.max, 0, 0))
ctx.HashAdd(result, hbrt.MakeString("min"), hbrt.MakeDouble(sum.min, 0, 0))
ctx.HashAdd(result, hbrt.MakeString("elapsed_ms"), hbrt.MakeInt(int(elapsed.Milliseconds())))
throughput := fmt.Sprintf("%.0f", float64(nRecords)/elapsed.Seconds())
ctx.HashAdd(result, hbrt.MakeString("throughput"), hbrt.MakeString(throughput))
for _, cat := range []string{"Electronics", "Clothing", "Food", "Books"} {
key := "cat_" + strings.ToLower(cat)
ctx.HashAdd(result, hbrt.MakeString(key), hbrt.MakeDouble(sum.categories[cat], 0, 0))
}
ctx.RetVal(result)
}
#pragma ENDDUMP

103
examples/go_dual_db.prg Normal file
View File

@@ -0,0 +1,103 @@
// Five Example: Dual SQLite — NO #pragma BEGINDUMP
//
// Two databases open simultaneously — transfer data between them.
// All Go calls via IMPORT — zero boilerplate.
IMPORT "database/sql"
IMPORT _ "modernc.org/sqlite"
PROCEDURE Main()
LOCAL dbSource, dbTarget, aSrc, aTgt
LOCAL aRows, i, nCount
? "=== Dual SQLite Demo ==="
?
dbSource := sql.Open("sqlite", "source.db")
dbTarget := sql.Open("sqlite", "target.db")
// Setup source
dbSource:Exec("DROP TABLE IF EXISTS products")
dbSource:Exec("CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, price REAL, stock INTEGER)")
dbSource:Exec("INSERT INTO products VALUES (1, 'Keyboard', 89.99, 150)")
dbSource:Exec("INSERT INTO products VALUES (2, 'Mouse', 29.99, 300)")
dbSource:Exec("INSERT INTO products VALUES (3, 'Monitor', 499.99, 45)")
dbSource:Exec("INSERT INTO products VALUES (4, 'Headset', 79.99, 200)")
dbSource:Exec("INSERT INTO products VALUES (5, 'Webcam', 59.99, 120)")
? "Source: 5 products created"
// Setup target
dbTarget:Exec("DROP TABLE IF EXISTS inventory")
dbTarget:Exec("CREATE TABLE inventory (product_id INTEGER, name TEXT, price REAL, status TEXT)")
? "Target: inventory table ready"
?
// Source -> Target transfer (stock > 100)
aRows := SqlScan(dbSource, "SELECT * FROM products WHERE stock > 100")
? "Transferring", Len(aRows), "products with stock > 100..."
nCount := 0
FOR i := 1 TO Len(aRows)
dbTarget:Exec("INSERT INTO inventory VALUES (" + ;
Str(aRows[i]["id"]) + ", " + ;
"'" + aRows[i]["name"] + "', " + ;
Str(aRows[i]["price"]) + ", " + ;
"'" + IIF(aRows[i]["stock"] > 200, "high", "normal") + "')")
nCount++
NEXT
? Str(nCount, 3), "records transferred"
?
// Verify target
? "=== Target Inventory ==="
aRows := SqlScan(dbTarget, "SELECT * FROM inventory ORDER BY price DESC")
? PadR("ID", 4), PadR("Name", 15), PadR("Price", 10), "Status"
? Replicate("-", 45)
FOR i := 1 TO Len(aRows)
? PadR(aRows[i]["product_id"], 4), ;
PadR(aRows[i]["name"], 15), ;
PadR(Str(aRows[i]["price"], 8, 2), 10), ;
aRows[i]["status"]
NEXT
?
// Cross-database summary
? "=== Cross-DB Summary ==="
aSrc := SqlScan(dbSource, "SELECT COUNT(*) as cnt, SUM(price) as total FROM products")
aTgt := SqlScan(dbTarget, "SELECT COUNT(*) as cnt, SUM(price) as total FROM inventory")
? "Source:", aSrc[1]["cnt"], "products, total", aSrc[1]["total"]
? "Target:", aTgt[1]["cnt"], "items, total", aTgt[1]["total"]
dbSource:Close()
dbTarget:Close()
?
? "Both databases closed. Done."
RETURN
// SqlScan — pure PRG function using Go's sql.Rows directly
// No #pragma BEGINDUMP needed!
FUNCTION SqlScan(db, cSQL)
LOCAL rows, cols, aResult, aRow, i, nCols
aResult := {}
rows := db:Query(cSQL)
IF rows == NIL
RETURN aResult
ENDIF
cols := rows:Columns()
nCols := Len(cols)
DO WHILE rows:Next()
aRow := {=>}
FOR i := 1 TO nCols
aRow[cols[i]] := rows:Column(i)
NEXT
AAdd(aResult, aRow)
ENDDO
rows:Close()
RETURN aResult

View File

@@ -0,0 +1,89 @@
// Five Go Extensions — All 9 new syntax features
IMPORT "strings"
IMPORT "fmt"
PROCEDURE Main()
LOCAL cName, nAge, cResult, cUpper, cCity
LOCAL aData, aSub, i
LOCAL db, err
cName := "Charles"
nAge := 30
cCity := "Seoul"
? "=== Five Go Extension Syntax ==="
?
// 1. Multi-Return: a, b := Func()
? "[1] Multi-Return"
cUpper, cResult := strings.ToUpper("hello"), strings.ToLower("WORLD")
? " upper:", cUpper, " lower:", cResult
// 2. DEFER — auto cleanup
? "[2] DEFER"
TestDefer()
// 3. Slice syntax: a[low:high]
? "[3] Slice"
aData := {"alpha", "beta", "gamma", "delta", "epsilon"}
aSub := aData[2:4]
? " aData[2:4]:", aSub[1], aSub[2]
aSub := aData[3:]
? " aData[3:]:", aSub[1], aSub[2]
aSub := aData[:2]
? " aData[:2]:", aSub[1]
// 4. Parallel assignment: a, b := b, a
? "[4] Parallel / Swap"
cUpper, cResult := cResult, cUpper
? " swapped:", cUpper, cResult
// 5. Blank identifier _
? "[5] Blank _"
_, cResult := "discard", "keep"
? " _,keep:", cResult
// 6. SWITCH (existing + compatible)
? "[6] SWITCH"
SWITCH nAge
CASE 20
? " twenty"
CASE 30
? " thirty"
OTHERWISE
? " other"
ENDSWITCH
// 7. CONST block
? "[7] CONST"
CONST
STATUS_ACTIVE := 1
STATUS_CLOSED := 2
STATUS_PENDING := 3
END CONST
? " CONST defined"
// 8. Nil-safe: obj?:Method()
? "[8] Nil-safe ?:"
db := NIL
cResult := db?:Close()
? " nil?:Close():", cResult, "(no crash!)"
// 9. String interpolation: f"..."
? "[9] f-string"
cResult := f"Name: {cName}, Age: {nAge}, City: {cCity}"
? " ", cResult
?
? "=== All Extensions OK ==="
RETURN
PROCEDURE TestDefer()
LOCAL cStatus
cStatus := "open"
DEFER QOut(" [defer] cleanup!")
cStatus := "processing"
? " working..."
cStatus := "done"
RETURN

180
examples/go_httpserver.prg Normal file
View File

@@ -0,0 +1,180 @@
// Five Example: HTTP REST API Server
//
// PRG handles business logic (customer data, search)
// Go handles HTTP serving, JSON, concurrency
//
// Usage: five run go_httpserver.prg
// curl http://localhost:8080/api/customers
// curl http://localhost:8080/api/customers/search?name=John
PROCEDURE Main()
LOCAL cPort
cPort := "8080"
? "=== Five REST API Server ==="
? "Powered by Harbour data + Go net/http"
?
? "Starting server on port " + cPort + "..."
? "Endpoints:"
? " GET /api/customers - list all customers"
? " GET /api/customers/search - search by name (?name=xxx)"
? " POST /api/customers - add customer (JSON body)"
? " GET /api/stats - server statistics"
? " GET /health - health check"
?
? "Press Ctrl+C to stop"
GoHttpServe(cPort)
RETURN
FUNCTION GetCustomers()
LOCAL aResult
aResult := {}
AAdd(aResult, { "id" => 1, "name" => "Charles Kwon", "city" => "Seoul", "balance" => 15000.50 })
AAdd(aResult, { "id" => 2, "name" => "John Smith", "city" => "New York", "balance" => 8200.00 })
AAdd(aResult, { "id" => 3, "name" => "Maria Garcia", "city" => "Madrid", "balance" => 12300.75 })
AAdd(aResult, { "id" => 4, "name" => "Yuki Tanaka", "city" => "Tokyo", "balance" => 9800.25 })
AAdd(aResult, { "id" => 5, "name" => "Hans Mueller", "city" => "Berlin", "balance" => 6500.00 })
RETURN aResult
FUNCTION SearchCustomers(cSearch)
LOCAL aAll, aResult, i
aAll := GetCustomers()
aResult := {}
FOR i := 1 TO Len(aAll)
IF Upper(cSearch) $ Upper(aAll[i]["name"])
AAdd(aResult, aAll[i])
ENDIF
NEXT
RETURN aResult
#pragma BEGINDUMP
import (
"encoding/json"
"five/hbrt"
"fmt"
"net/http"
"strings"
"sync/atomic"
"time"
)
var requestCount int64
var startTime time.Time
func init() {
hbrt.HB_FUNC("GOHTTPSERVE", goHttpServe)
}
func goHttpServe(ctx *hbrt.HBContext) {
port := ctx.ParC(1)
if port == "" {
port = "8080"
}
startTime = time.Now()
mux := http.NewServeMux()
mux.HandleFunc("/health", handleHealth)
mux.HandleFunc("/api/stats", handleStats)
mux.HandleFunc("/api/customers", handleCustomers)
mux.HandleFunc("/api/customers/search", handleSearch)
server := &http.Server{
Addr: ":" + port,
Handler: withLogging(mux),
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
}
if err := server.ListenAndServe(); err != nil {
ctx.RetC("Error: " + err.Error())
}
}
func withLogging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
atomic.AddInt64(&requestCount, 1)
start := time.Now()
next.ServeHTTP(w, r)
fmt.Printf(" %s %s %s [%v]\n", r.Method, r.URL.Path, r.RemoteAddr, time.Since(start))
})
}
func handleHealth(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"status": "healthy",
"uptime": time.Since(startTime).String(),
})
}
func handleStats(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"requests": atomic.LoadInt64(&requestCount),
"uptime_ms": time.Since(startTime).Milliseconds(),
"engine": "Five (Harbour + Go)",
})
}
func handleCustomers(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
customers := []map[string]interface{}{
{"id": 1, "name": "Charles Kwon", "city": "Seoul", "balance": 15000.50},
{"id": 2, "name": "John Smith", "city": "New York", "balance": 8200.00},
{"id": 3, "name": "Maria Garcia", "city": "Madrid", "balance": 12300.75},
{"id": 4, "name": "Yuki Tanaka", "city": "Tokyo", "balance": 9800.25},
{"id": 5, "name": "Hans Mueller", "city": "Berlin", "balance": 6500.00},
}
if r.Method == "POST" {
var newCustomer map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&newCustomer); err != nil {
http.Error(w, `{"error": "invalid JSON"}`, http.StatusBadRequest)
return
}
newCustomer["id"] = len(customers) + 1
customers = append(customers, newCustomer)
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(newCustomer)
return
}
json.NewEncoder(w).Encode(customers)
}
func handleSearch(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
query := strings.ToLower(r.URL.Query().Get("name"))
if query == "" {
http.Error(w, `{"error": "name parameter required"}`, http.StatusBadRequest)
return
}
customers := []map[string]interface{}{
{"id": 1, "name": "Charles Kwon", "city": "Seoul", "balance": 15000.50},
{"id": 2, "name": "John Smith", "city": "New York", "balance": 8200.00},
{"id": 3, "name": "Maria Garcia", "city": "Madrid", "balance": 12300.75},
{"id": 4, "name": "Yuki Tanaka", "city": "Tokyo", "balance": 9800.25},
{"id": 5, "name": "Hans Mueller", "city": "Berlin", "balance": 6500.00},
}
var results []map[string]interface{}
for _, c := range customers {
if strings.Contains(strings.ToLower(c["name"].(string)), query) {
results = append(results, c)
}
}
json.NewEncoder(w).Encode(results)
}
#pragma ENDDUMP

41
examples/go_native.prg Normal file
View File

@@ -0,0 +1,41 @@
// Five Example: Native Go Package Usage — NO #pragma BEGINDUMP
//
// Just IMPORT and use Go packages directly from PRG!
// Five generates the bridge code automatically.
//
// pkg.Func() → direct Go call (gengo emits native Go)
// obj:Method() → reflect bridge (runtime GoCall)
IMPORT "strings"
IMPORT "strconv"
IMPORT "fmt"
PROCEDURE Main()
LOCAL cResult, nVal, cFormatted
? "=== Five Native Go Calls ==="
?
// strings.ToUpper — direct Go package call
cResult := strings.ToUpper("hello five!")
? "strings.ToUpper:", cResult
// strings.Contains
? "strings.Contains('Five is great', 'great'):", strings.Contains("Five is great", "great")
// strings.Replace
cResult := strings.ReplaceAll("foo-bar-baz", "-", "_")
? "strings.ReplaceAll:", cResult
// strings.Split → returns Go slice → auto-converted to Harbour array
? "strings.Split('a,b,c', ','):", strings.Split("a,b,c", ",")
// strconv.Atoi — returns (int, error)
nVal := strconv.Atoi("42")
? "strconv.Atoi('42'):", nVal
// fmt.Sprintf — format strings the Go way
cFormatted := fmt.Sprintf("Name: %s, Age: %d, Score: %.1f", "Charles", 30, 98.5)
? "fmt.Sprintf:", cFormatted
RETURN

213
examples/go_sql_direct.prg Normal file
View File

@@ -0,0 +1,213 @@
// Five Example: Direct Go SQL — the simplest possible way
//
// #pragma BEGINDUMP registers Go functions via HB_FUNC.
// PRG calls them like regular Harbour functions.
// Go objects flow as Harbour values — : for methods.
//
// Pattern: IMPORT declares Go packages
// HB_FUNC bridges Go → Harbour
// PRG code stays clean xBase style
IMPORT "database/sql"
IMPORT _ "modernc.org/sqlite"
PROCEDURE Main()
LOCAL db, aRows, aSum, i
? "=== Five SQL Demo ==="
?
db := SqlOpen("sqlite", ":memory:")
IF db == NIL
? "Failed to open database"
RETURN
ENDIF
SqlExec(db, "CREATE TABLE customers (" + ;
" id INTEGER PRIMARY KEY AUTOINCREMENT," + ;
" name TEXT NOT NULL," + ;
" city TEXT," + ;
" balance REAL DEFAULT 0)")
SqlExec(db, "INSERT INTO customers (name, city, balance) VALUES ('Charles Kwon', 'Seoul', 15000.50)")
SqlExec(db, "INSERT INTO customers (name, city, balance) VALUES ('John Smith', 'New York', 8200.00)")
SqlExec(db, "INSERT INTO customers (name, city, balance) VALUES ('Maria Garcia', 'Madrid', 12300.75)")
SqlExec(db, "INSERT INTO customers (name, city, balance) VALUES ('Yuki Tanaka', 'Tokyo', 9800.25)")
SqlExec(db, "INSERT INTO customers (name, city, balance) VALUES ('Hans Mueller', 'Berlin', 6500.00)")
? "5 records inserted."
?
aRows := SqlQuery(db, "SELECT * FROM customers ORDER BY balance DESC")
? PadR("ID", 4), PadR("Name", 20), PadR("City", 15), "Balance"
? Replicate("-", 55)
FOR i := 1 TO Len(aRows)
? PadR(aRows[i]["id"], 4), ;
PadR(aRows[i]["name"], 20), ;
PadR(aRows[i]["city"], 15), ;
aRows[i]["balance"]
NEXT
?
aSum := SqlQuery(db, "SELECT COUNT(*) as cnt, SUM(balance) as total, AVG(balance) as avg FROM customers")
? "Count:", aSum[1]["cnt"], " Total:", aSum[1]["total"], " Avg:", aSum[1]["avg"]
?
aRows := SqlQueryP(db, "SELECT name, city FROM customers WHERE balance > ?", 10000)
? "Balance > 10000:"
FOR i := 1 TO Len(aRows)
? " ", aRows[i]["name"], "-", aRows[i]["city"]
NEXT
SqlClose(db)
? "Done."
RETURN
#pragma BEGINDUMP
import (
"database/sql"
"five/hbrt"
"fmt"
)
func init() {
hbrt.HB_FUNC("SQLOPEN", sqlOpen)
hbrt.HB_FUNC("SQLCLOSE", sqlClose)
hbrt.HB_FUNC("SQLEXEC", sqlExec)
hbrt.HB_FUNC("SQLQUERY", sqlQuery)
hbrt.HB_FUNC("SQLQUERYP", sqlQueryP)
}
// SqlOpen(cDriver, cDSN) → oDb or NIL
func sqlOpen(ctx *hbrt.HBContext) {
driver := ctx.ParC(1)
dsn := ctx.ParC(2)
db, err := sql.Open(driver, dsn)
if err != nil {
ctx.RetNil()
return
}
if err = db.Ping(); err != nil {
ctx.RetNil()
return
}
ctx.RetVal(hbrt.WrapGo(db))
}
// SqlClose(oDb)
func sqlClose(ctx *hbrt.HBContext) {
if db := getDB(ctx, 1); db != nil {
db.Close()
}
ctx.RetNil()
}
// SqlExec(oDb, cSQL) → lSuccess
func sqlExec(ctx *hbrt.HBContext) {
db := getDB(ctx, 1)
if db == nil {
ctx.RetL(false)
return
}
_, err := db.Exec(ctx.ParC(2))
if err != nil {
fmt.Printf("SQL Error: %v\n", err)
ctx.RetL(false)
return
}
ctx.RetL(true)
}
// SqlQuery(oDb, cSQL) → aRows (array of hashes)
func sqlQuery(ctx *hbrt.HBContext) {
db := getDB(ctx, 1)
if db == nil {
ctx.RetArray(nil)
return
}
rows, err := db.Query(ctx.ParC(2))
if err != nil {
fmt.Printf("SQL Error: %v\n", err)
ctx.RetArray(nil)
return
}
defer rows.Close()
ctx.RetArray(scanRows(ctx, rows))
}
// SqlQueryP(oDb, cSQL, xParam1, ...) → aRows with parameters
func sqlQueryP(ctx *hbrt.HBContext) {
db := getDB(ctx, 1)
if db == nil {
ctx.RetArray(nil)
return
}
var args []interface{}
for i := 3; i <= ctx.PCount(); i++ {
v := ctx.Param(i)
switch {
case v.IsString():
args = append(args, v.AsString())
case v.IsNumeric():
args = append(args, v.AsNumDouble())
case v.IsLogical():
args = append(args, v.AsBool())
default:
args = append(args, nil)
}
}
rows, err := db.Query(ctx.ParC(2), args...)
if err != nil {
fmt.Printf("SQL Error: %v\n", err)
ctx.RetArray(nil)
return
}
defer rows.Close()
ctx.RetArray(scanRows(ctx, rows))
}
// --- internal helpers ---
func getDB(ctx *hbrt.HBContext, n int) *sql.DB {
obj := hbrt.UnwrapGo(ctx.Param(n))
db, _ := obj.(*sql.DB)
return db
}
func scanRows(ctx *hbrt.HBContext, rows *sql.Rows) []hbrt.Value {
cols, _ := rows.Columns()
var result []hbrt.Value
for rows.Next() {
values := make([]interface{}, len(cols))
ptrs := make([]interface{}, len(cols))
for i := range values {
ptrs[i] = &values[i]
}
rows.Scan(ptrs...)
hash := ctx.HashNew()
for i, col := range cols {
key := hbrt.MakeString(col)
var val hbrt.Value
switch v := values[i].(type) {
case int64:
val = hbrt.MakeInt(int(v))
case float64:
val = hbrt.MakeDouble(v, 0, 0)
case string:
val = hbrt.MakeString(v)
case []byte:
val = hbrt.MakeString(string(v))
case bool:
val = hbrt.MakeBool(v)
default:
val = hbrt.MakeNil()
}
ctx.HashAdd(hash, key, val)
}
result = append(result, hash)
}
return result
}
#pragma ENDDUMP

204
examples/go_sqlite.prg Normal file
View File

@@ -0,0 +1,204 @@
// Five Example: SQLite Database with Go's database/sql
//
// Harbour's xBase syntax + Go's SQL ecosystem = modern database apps.
// Traditional Harbour: limited to DBF/NTX/CDX
// Five: any database Go supports (SQLite, PostgreSQL, MySQL, etc.)
PROCEDURE Main()
LOCAL aRows, aSummary, aSearch, i
? "=== Five + SQLite Demo ==="
?
GoDbOpen(":memory:")
GoDbExec("CREATE TABLE customers (" + ;
" id INTEGER PRIMARY KEY AUTOINCREMENT," + ;
" name TEXT NOT NULL," + ;
" city TEXT," + ;
" balance REAL DEFAULT 0" + ;
")")
? "Inserting records..."
GoDbExec("INSERT INTO customers (name, city, balance) VALUES ('Charles Kwon', 'Seoul', 15000.50)")
GoDbExec("INSERT INTO customers (name, city, balance) VALUES ('John Smith', 'New York', 8200.00)")
GoDbExec("INSERT INTO customers (name, city, balance) VALUES ('Maria Garcia', 'Madrid', 12300.75)")
GoDbExec("INSERT INTO customers (name, city, balance) VALUES ('Yuki Tanaka', 'Tokyo', 9800.25)")
GoDbExec("INSERT INTO customers (name, city, balance) VALUES ('Hans Mueller', 'Berlin', 6500.00)")
? "5 records inserted."
?
aRows := GoDbQuery("SELECT * FROM customers ORDER BY balance DESC")
? "All customers (sorted by balance):"
? PadR("ID", 4), PadR("Name", 20), PadR("City", 15), "Balance"
? Replicate("-", 55)
FOR i := 1 TO Len(aRows)
? PadR(aRows[i]["id"], 4), ;
PadR(aRows[i]["name"], 20), ;
PadR(aRows[i]["city"], 15), ;
aRows[i]["balance"]
NEXT
?
aSummary := GoDbQuery("SELECT COUNT(*) as cnt, SUM(balance) as total, AVG(balance) as avg FROM customers")
? "Summary:"
? " Count: ", aSummary[1]["cnt"]
? " Total: ", aSummary[1]["total"]
? " Average:", aSummary[1]["avg"]
?
aSearch := GoDbQueryP("SELECT name, city FROM customers WHERE balance > ?", 10000)
? "Customers with balance > 10000:"
FOR i := 1 TO Len(aSearch)
? " ", aSearch[i]["name"], "-", aSearch[i]["city"]
NEXT
GoDbClose()
?
? "Done."
RETURN
#pragma BEGINDUMP
import (
"database/sql"
"five/hbrt"
"fmt"
_ "modernc.org/sqlite"
)
var db *sql.DB
func init() {
hbrt.HB_FUNC("GODBOPEN", goDbOpen)
hbrt.HB_FUNC("GODBCLOSE", goDbClose)
hbrt.HB_FUNC("GODBEXEC", goDbExec)
hbrt.HB_FUNC("GODBQUERY", goDbQuery)
hbrt.HB_FUNC("GODBQUERYP", goDbQueryP)
}
func goDbOpen(ctx *hbrt.HBContext) {
dsn := ctx.ParC(1)
if dsn == "" {
dsn = ":memory:"
}
var err error
db, err = sql.Open("sqlite", dsn)
if err != nil {
ctx.RetL(false)
return
}
ctx.RetL(true)
}
func goDbClose(ctx *hbrt.HBContext) {
if db != nil {
db.Close()
db = nil
}
ctx.RetNil()
}
func goDbExec(ctx *hbrt.HBContext) {
sqlStr := ctx.ParC(1)
if db == nil || sqlStr == "" {
ctx.RetL(false)
return
}
_, err := db.Exec(sqlStr)
if err != nil {
fmt.Printf("SQL Error: %v\n", err)
ctx.RetL(false)
return
}
ctx.RetL(true)
}
func goDbQuery(ctx *hbrt.HBContext) {
sqlStr := ctx.ParC(1)
if db == nil || sqlStr == "" {
ctx.RetArray(nil)
return
}
rows, err := db.Query(sqlStr)
if err != nil {
fmt.Printf("SQL Error: %v\n", err)
ctx.RetArray(nil)
return
}
defer rows.Close()
ctx.RetVal(rowsToHarbour(ctx, rows))
}
func goDbQueryP(ctx *hbrt.HBContext) {
sqlStr := ctx.ParC(1)
if db == nil || sqlStr == "" {
ctx.RetArray(nil)
return
}
var args []interface{}
for i := 2; i <= ctx.PCount(); i++ {
v := ctx.Param(i)
switch {
case v.IsString():
args = append(args, v.AsString())
case v.IsNumeric():
args = append(args, v.AsNumDouble())
case v.IsLogical():
args = append(args, v.AsBool())
default:
args = append(args, nil)
}
}
rows, err := db.Query(sqlStr, args...)
if err != nil {
fmt.Printf("SQL Error: %v\n", err)
ctx.RetArray(nil)
return
}
defer rows.Close()
ctx.RetVal(rowsToHarbour(ctx, rows))
}
func rowsToHarbour(ctx *hbrt.HBContext, rows *sql.Rows) hbrt.Value {
cols, _ := rows.Columns()
var result []hbrt.Value
for rows.Next() {
values := make([]interface{}, len(cols))
ptrs := make([]interface{}, len(cols))
for i := range values {
ptrs[i] = &values[i]
}
rows.Scan(ptrs...)
hash := ctx.HashNew()
for i, col := range cols {
key := hbrt.MakeString(col)
var val hbrt.Value
switch v := values[i].(type) {
case int64:
val = hbrt.MakeInt(int(v))
case float64:
val = hbrt.MakeDouble(v, 0, 0)
case string:
val = hbrt.MakeString(v)
case []byte:
val = hbrt.MakeString(string(v))
case bool:
val = hbrt.MakeBool(v)
default:
val = hbrt.MakeNil()
}
ctx.HashAdd(hash, key, val)
}
result = append(result, hash)
}
return hbrt.MakeArrayFrom(result)
}
#pragma ENDDUMP

60
examples/go_strings.prg Normal file
View File

@@ -0,0 +1,60 @@
// Five: Go strings 패키지를 PRG에서 자유롭게 사용
IMPORT "strings"
PROCEDURE Main()
LOCAL cText, aParts, cUpper, cResult
LOCAL lFound, nCount, nPos, i
LOCAL cJoined, cTrimmed, cReplaced
cText := "Hello,World,Five,Go,Harbour"
// Split → Harbour 배열로 자동 변환
aParts := strings.Split(cText, ",")
? "Split 결과:", Len(aParts), "개"
FOR i := 1 TO Len(aParts)
? " [" + Str(i, 1) + "]", aParts[i]
NEXT
?
// Store results in separate variables
cUpper := strings.ToUpper(cText)
lFound := strings.Contains(cText, "Five")
nCount := strings.Count(cText, ",")
nPos := strings.Index(cText, "Go")
? "원본: ", cText
? "ToUpper: ", cUpper
? "Contains 'Five':", lFound
? "쉼표 갯수:", nCount
? "'Go' 위치:", nPos
?
// 조합해서 사용
cJoined := strings.Join(aParts, " | ")
cTrimmed := strings.TrimSpace(" hello ")
cReplaced := strings.ReplaceAll(cText, ",", " → ")
? "Join: ", cJoined
? "Trim: [" + cTrimmed + "]"
? "Replace: ", cReplaced
?
// 조건 분기에서 활용
IF strings.HasPrefix(cText, "Hello")
? "Hello로 시작합니다"
ENDIF
IF strings.HasSuffix(cText, "Harbour")
? "Harbour로 끝납니다"
ENDIF
// 루프에서 활용
? "대문자로 시작하는 단어:"
FOR i := 1 TO Len(aParts)
IF strings.ToUpper(Left(aParts[i], 1)) == Left(aParts[i], 1)
? " ", aParts[i]
ENDIF
NEXT
RETURN

286
examples/go_typetest.prg Normal file
View File

@@ -0,0 +1,286 @@
// Five Go Interop — FULL Type Test
// Tests every Go ↔ PRG type conversion.
IMPORT "strings"
IMPORT "strconv"
IMPORT "fmt"
IMPORT "math"
IMPORT "os"
IMPORT "path/filepath"
IMPORT "time"
IMPORT "encoding/json"
IMPORT "encoding/base64"
IMPORT "crypto/sha256"
IMPORT "sort"
IMPORT "regexp"
IMPORT "net/url"
IMPORT "sync"
PROCEDURE Main()
LOCAL cResult, nResult, lResult, nFloat
LOCAL aParts, cJoined, i
LOCAL hMap, aKeys, aBytes
LOCAL cJSON, cB64, cHash
LOCAL cPath, cDir, cFile
LOCAL tNow, nYear
LOCAL oMutex, oURL
LOCAL nLong, cFormatted
? "=========================================="
? " Five Go Type Test — ALL Types"
? "=========================================="
?
// -------------------------------------------------------
// 1. STRING: PRG String ↔ Go string
// -------------------------------------------------------
? "[1] String ↔ string"
cResult := strings.ToUpper("hello five")
Assert(cResult == "HELLO FIVE", "ToUpper")
cResult := strings.TrimSpace(" spaced ")
Assert(cResult == "spaced", "TrimSpace")
cResult := strings.ReplaceAll("a-b-c", "-", "_")
Assert(cResult == "a_b_c", "ReplaceAll")
cResult := strings.Repeat("ab", 3)
Assert(cResult == "ababab", "Repeat")
cResult := strings.ToTitle("hello world")
Assert(cResult == "HELLO WORLD", "ToTitle")
?
// -------------------------------------------------------
// 2. BOOL: PRG Logical ↔ Go bool
// -------------------------------------------------------
? "[2] Logical ↔ bool"
lResult := strings.Contains("hello five", "five")
Assert(lResult, "Contains true")
lResult := strings.Contains("hello five", "xyz")
Assert(!lResult, "Contains false")
lResult := strings.HasPrefix("hello", "hel")
Assert(lResult, "HasPrefix")
lResult := strings.HasSuffix("world", "rld")
Assert(lResult, "HasSuffix")
lResult := strings.EqualFold("Hello", "hello")
Assert(lResult, "EqualFold")
?
// -------------------------------------------------------
// 3. INT: PRG Numeric(int) ↔ Go int
// -------------------------------------------------------
? "[3] Numeric(int) ↔ int"
nResult := strings.Count("aabbaab", "aa")
Assert(nResult == 2, "Count")
nResult := strings.Index("hello", "ll")
Assert(nResult == 2, "Index")
nResult := strings.LastIndex("abcabc", "bc")
Assert(nResult == 4, "LastIndex")
?
// -------------------------------------------------------
// 4. LONG: PRG Numeric(long) ↔ Go int64
// -------------------------------------------------------
? "[4] Numeric(long) ↔ int64"
nLong := time.Now():UnixMilli()
Assert(nLong > 1000000000000, "UnixMilli is large int64")
?
// -------------------------------------------------------
// 5. FLOAT: PRG Numeric(double) ↔ Go float64
// -------------------------------------------------------
? "[5] Numeric(double) ↔ float64"
nFloat := math.Sqrt(144)
Assert(nFloat == 12, "Sqrt(144)")
nFloat := math.Round(3.7)
Assert(nFloat == 4, "Round(3.7)")
nFloat := math.Abs(-42.5)
Assert(nFloat == 42.5, "Abs(-42.5)")
nFloat := math.Floor(3.9)
Assert(nFloat == 3, "Floor(3.9)")
nFloat := math.Ceil(3.1)
Assert(nFloat == 4, "Ceil(3.1)")
nFloat := math.Max(10, 20)
Assert(nFloat == 20, "Max(10,20)")
nFloat := math.Min(10, 20)
Assert(nFloat == 10, "Min(10,20)")
?
// -------------------------------------------------------
// 6. ARRAY: PRG Array ↔ Go []string / []int
// -------------------------------------------------------
? "[6] Array ↔ slice"
aParts := strings.Split("one,two,three", ",")
Assert(Len(aParts) == 3, "Split len=3")
Assert(aParts[1] == "one", "Split[1]")
Assert(aParts[2] == "two", "Split[2]")
Assert(aParts[3] == "three", "Split[3]")
// PRG array → Go []string
cJoined := strings.Join(aParts, "-")
Assert(cJoined == "one-two-three", "Join")
// Split then Join roundtrip
cResult := strings.Join(strings.Split("x|y|z", "|"), ",")
Assert(cResult == "x,y,z", "Split+Join roundtrip")
?
// -------------------------------------------------------
// 7. NIL: PRG NIL ↔ Go nil / zero value
// -------------------------------------------------------
? "[7] NIL ↔ nil"
cResult := strings.ToUpper("")
Assert(cResult == "", "empty string → empty")
// strconv.Atoi returns (int, error) — first val only
nResult := strconv.Atoi("0")
Assert(nResult == 0, "Atoi zero")
?
// -------------------------------------------------------
// 8. VARIADIC: mixed types → Go ...interface{}
// -------------------------------------------------------
? "[8] Variadic (mixed types)"
cFormatted := fmt.Sprintf("s=%s i=%d f=%.1f b=%t", "abc", 42, 3.14, .T.)
Assert(cFormatted == "s=abc i=42 f=3.1 b=true", "Sprintf mixed")
cFormatted := fmt.Sprintf("%d+%d=%d", 10, 20, 30)
Assert(cFormatted == "10+20=30", "Sprintf ints")
cFormatted := fmt.Sprintf("[%10s]", "right")
Assert(cFormatted == "[ right]", "Sprintf padded")
?
// -------------------------------------------------------
// 9. BYTES: PRG String ↔ Go []byte
// -------------------------------------------------------
? "[9] String ↔ []byte"
// base64 encode/decode uses []byte
cB64 := base64.StdEncoding:EncodeToString("Hello Five!")
Assert(cB64 == "SGVsbG8gRml2ZSE=", "Base64 encode")
// sha256 produces []byte → hex string
aBytes := sha256.Sum256("test")
Assert(aBytes != NIL, "SHA256 returns value")
?
// -------------------------------------------------------
// 10. GO OBJECT: PRG Value wrapping Go *struct
// -------------------------------------------------------
? "[10] Go Object (pointer)"
// sync.Mutex — create and use Go object
oMutex := sync.Mutex{}
Assert(oMutex != NIL, "Mutex created")
// url.Parse returns *url.URL
oURL := url.Parse("https://five-lang.dev/docs?q=hello")
Assert(oURL != NIL, "URL parsed")
cResult := oURL:String()
Assert(strings.Contains(cResult, "five-lang"), "URL.String()")
?
// -------------------------------------------------------
// 11. GO OBJECT METHOD CHAIN
// -------------------------------------------------------
? "[11] Method chain"
// strings.NewReplacer returns *Replacer with method Replace
LOCAL oReplacer
oReplacer := strings.NewReplacer("a", "1", "b", "2", "c", "3")
cResult := oReplacer:Replace("abc")
Assert(cResult == "123", "Replacer.Replace")
?
// -------------------------------------------------------
// 12. strconv: int↔string roundtrip
// -------------------------------------------------------
? "[12] strconv roundtrip"
cResult := strconv.Itoa(12345)
Assert(cResult == "12345", "Itoa")
nResult := strconv.Atoi("67890")
Assert(nResult == 67890, "Atoi")
// FormatFloat
cResult := strconv.FormatFloat(3.14159, 102, 2, 64)
Assert(cResult == "3.14", "FormatFloat")
?
// -------------------------------------------------------
// 13. CHAINED: nested Go calls
// -------------------------------------------------------
? "[13] Chained calls"
cResult := strings.ToUpper(strings.TrimSpace(" hello "))
Assert(cResult == "HELLO", "Upper(Trim())")
nResult := strings.Count(strings.ToLower("AABAA"), "a")
Assert(nResult == 4, "Count(Lower())")
cResult := strings.Join(strings.Split(strings.ToLower("A.B.C"), "."), "/")
Assert(cResult == "a/b/c", "Join(Split(Lower()))")
?
// -------------------------------------------------------
// 14. LOOP: Go calls inside FOR loop
// -------------------------------------------------------
? "[14] Loop with Go calls"
aParts := strings.Split("alpha,beta,gamma,delta", ",")
FOR i := 1 TO Len(aParts)
aParts[i] := strings.ToUpper(aParts[i])
NEXT
cJoined := strings.Join(aParts, "/")
Assert(cJoined == "ALPHA/BETA/GAMMA/DELTA", "Loop ToUpper")
?
// -------------------------------------------------------
// 15. FILE PATH: os / filepath
// -------------------------------------------------------
? "[15] os / filepath"
cPath := filepath.Join("usr", "local", "bin")
Assert(strings.Contains(cPath, "local"), "filepath.Join")
cDir := filepath.Dir("/home/user/file.txt")
Assert(strings.Contains(cDir, "user"), "filepath.Dir")
cFile := filepath.Base("/home/user/file.txt")
Assert(cFile == "file.txt", "filepath.Base")
cResult := filepath.Ext("document.pdf")
Assert(cResult == ".pdf", "filepath.Ext")
?
// -------------------------------------------------------
// 16. TIME: Go time package
// -------------------------------------------------------
? "[16] time"
tNow := time.Now()
Assert(tNow != NIL, "time.Now()")
nYear := tNow:Year()
Assert(nYear >= 2026, "Year >= 2026")
cResult := tNow:Format("2006-01-02")
Assert(Len(cResult) == 10, "Format YYYY-MM-DD")
? " Today:", cResult
?
// -------------------------------------------------------
// 17. JSON: encode/decode
// -------------------------------------------------------
? "[17] JSON"
cJSON := json.Marshal({"name" => "Five", "version" => 1})
Assert(cJSON != NIL, "json.Marshal")
?
// -------------------------------------------------------
// 18. REGEXP
// -------------------------------------------------------
? "[18] regexp"
LOCAL oRe
oRe := regexp.MustCompile("[0-9]+")
lResult := oRe:MatchString("abc123def")
Assert(lResult, "regexp.MatchString")
cResult := oRe:FindString("abc123def")
Assert(cResult == "123", "regexp.FindString")
aParts := oRe:FindAllString("a1b22c333", -1)
Assert(Len(aParts) == 3, "FindAllString len")
Assert(aParts[1] == "1", "FindAllString[1]")
Assert(aParts[2] == "22", "FindAllString[2]")
Assert(aParts[3] == "333", "FindAllString[3]")
?
? "=========================================="
? " ALL TESTS COMPLETE"
? "=========================================="
RETURN
// Assert helper
PROCEDURE Assert(lCondition, cName)
IF lCondition
?? " " + PadR(cName, 30) + " OK"
ELSE
?? " " + PadR(cName, 30) + " *** FAIL ***"
ENDIF
?
RETURN

158
examples/go_websocket.prg Normal file
View File

@@ -0,0 +1,158 @@
// Five Example: Real-time WebSocket Chat Server
//
// Complete chat server in ONE .prg file.
// Go handles WebSocket, HTTP, concurrency.
// PRG handles message processing logic.
//
// Open http://localhost:9090 in multiple browser tabs to test.
PROCEDURE Main()
? "=== Five WebSocket Chat Server ==="
? "Open http://localhost:9090 in your browser"
? "Press Ctrl+C to stop"
?
GoStartChat("9090")
RETURN
FUNCTION ProcessMessage(cUser, cMessage)
LOCAL cResult
DO CASE
CASE Upper(Left(cMessage, 5)) == "/HELP"
cResult := "Commands: /help /time /users /shout <msg>"
CASE Upper(Left(cMessage, 5)) == "/TIME"
cResult := "Server time: " + Time() + " " + DToC(Date())
CASE Upper(Left(cMessage, 6)) == "/SHOUT"
cResult := Upper(SubStr(cMessage, 7))
OTHERWISE
cResult := cMessage
ENDCASE
RETURN "[" + cUser + "] " + cResult
#pragma BEGINDUMP
import (
"five/hbrt"
"fmt"
"net/http"
"sync"
"time"
"golang.org/x/net/websocket"
)
func init() {
hbrt.HB_FUNC("GOSTARTCHAT", goStartChat)
}
type chatServer struct {
mu sync.RWMutex
clients map[*websocket.Conn]string
history []string
}
var chat = &chatServer{
clients: make(map[*websocket.Conn]string),
}
func goStartChat(ctx *hbrt.HBContext) {
port := ctx.ParC(1)
if port == "" {
port = "9090"
}
http.HandleFunc("/", serveHome)
http.Handle("/ws", websocket.Handler(handleWS))
fmt.Printf("Chat server listening on :%s\n", port)
if err := http.ListenAndServe(":"+port, nil); err != nil {
ctx.RetC("Error: " + err.Error())
}
}
func handleWS(ws *websocket.Conn) {
name := fmt.Sprintf("User_%d", time.Now().UnixNano()%10000)
chat.mu.Lock()
chat.clients[ws] = name
chat.mu.Unlock()
broadcast(fmt.Sprintf("* %s joined (%d online) *", name, len(chat.clients)))
chat.mu.RLock()
for _, msg := range chat.history {
websocket.Message.Send(ws, msg)
}
chat.mu.RUnlock()
for {
var msg string
if err := websocket.Message.Receive(ws, &msg); err != nil {
break
}
if msg == "" {
continue
}
if len(msg) > 6 && msg[:6] == "/name " {
oldName := name
name = msg[6:]
chat.mu.Lock()
chat.clients[ws] = name
chat.mu.Unlock()
broadcast(fmt.Sprintf("* %s is now %s *", oldName, name))
continue
}
broadcast(fmt.Sprintf("[%s] %s", name, msg))
}
chat.mu.Lock()
delete(chat.clients, ws)
chat.mu.Unlock()
broadcast(fmt.Sprintf("* %s left (%d online) *", name, len(chat.clients)))
}
func broadcast(msg string) {
chat.mu.Lock()
chat.history = append(chat.history, msg)
if len(chat.history) > 100 {
chat.history = chat.history[len(chat.history)-100:]
}
snapshot := make(map[*websocket.Conn]bool)
for k := range chat.clients {
snapshot[k] = true
}
chat.mu.Unlock()
for ws := range snapshot {
websocket.Message.Send(ws, msg)
}
}
func serveHome(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprint(w, chatHTML)
}
const chatHTML = `<!DOCTYPE html>
<html><head><title>Five Chat</title>
<style>
body{font-family:monospace;background:#1a1a2e;color:#eee;margin:20px}
h1{color:#e94560}
#log{background:#16213e;padding:15px;height:400px;overflow-y:auto;border:1px solid #0f3460;border-radius:8px;white-space:pre-wrap}
#msg{width:80%;padding:10px;background:#0f3460;color:#eee;border:1px solid #e94560;border-radius:4px;font-family:monospace}
button{padding:10px 20px;background:#e94560;color:#fff;border:none;border-radius:4px;cursor:pointer}
</style></head><body>
<h1>Five Chat</h1><p>Harbour + Go WebSocket</p>
<div id="log"></div><br>
<input id="msg" placeholder="Type message... /name YourName /help" autofocus>
<button onclick="send()">Send</button>
<script>
var ws=new WebSocket("ws://"+location.host+"/ws"),log=document.getElementById("log");
ws.onmessage=function(e){log.textContent+=e.data+"\n";log.scrollTop=log.scrollHeight};
ws.onclose=function(){log.textContent+="* Disconnected *\n"};
function send(){var m=document.getElementById("msg");if(m.value){ws.send(m.value);m.value=""}}
document.getElementById("msg").onkeypress=function(e){if(e.key==="Enter")send()};
</script></body></html>`
#pragma ENDDUMP

122
examples/godump_demo.prg Normal file
View File

@@ -0,0 +1,122 @@
// Five #pragma BEGINDUMP demo — HB_FUNC Go API
//
// Harbour's HB_FUNC(name) C API → Five's hbrt.HB_FUNC("name", fn) Go API
// Parameters: PRG → Go via ctx.ParC/NI/ND/L (1-based)
// Returns: Go → PRG via ctx.RetC/NI/ND/L
PROCEDURE Main()
LOCAL aResult, nSquared, i
? "=== Five Inline Go Demo ==="
?
? "GoUpper('hello world') =", GoUpper("hello world")
? "GoFib(10) =", GoFib(10)
? "GoGCD(48, 18) =", GoGCD(48, 18)
aResult := GoSplit("one,two,three", ",")
? "GoSplit result:"
FOR i := 1 TO Len(aResult)
? " ", aResult[i]
NEXT
nSquared := 0
GoSquare(7, @nSquared)
? "GoSquare(7, @n) => n =", nSquared
? "GoTypeOf('abc') =", GoTypeOf("abc")
? "GoTypeOf(123) =", GoTypeOf(123)
? "GoTypeOf(.T.) =", GoTypeOf(.T.)
? "GoTypeOf({1,2}) =", GoTypeOf({1,2})
? "GoTypeOf(NIL) =", GoTypeOf(NIL)
RETURN
#pragma BEGINDUMP
import (
"five/hbrt"
"strings"
)
func init() {
hbrt.HB_FUNC("GOUPPER", goUpper)
hbrt.HB_FUNC("GOFIB", goFib)
hbrt.HB_FUNC("GOGCD", goGCD)
hbrt.HB_FUNC("GOSPLIT", goSplit)
hbrt.HB_FUNC("GOSQUARE", goSquare)
hbrt.HB_FUNC("GOTYPEOF", goTypeOf)
}
func goUpper(ctx *hbrt.HBContext) {
if ctx.PCount() < 1 || !ctx.IsChar(1) {
ctx.RetC("")
return
}
ctx.RetC(strings.ToUpper(ctx.ParC(1)))
}
func goFib(ctx *hbrt.HBContext) {
n := ctx.ParNIDef(1, 0)
if n <= 1 {
ctx.RetNI(n)
return
}
a, b := 0, 1
for i := 2; i <= n; i++ {
a, b = b, a+b
}
ctx.RetNI(b)
}
func goGCD(ctx *hbrt.HBContext) {
a := ctx.ParNI(1)
b := ctx.ParNI(2)
for b != 0 {
a, b = b, a%b
}
ctx.RetNI(a)
}
func goSplit(ctx *hbrt.HBContext) {
s := ctx.ParC(1)
delim := ctx.ParC(2)
if delim == "" {
delim = ","
}
parts := strings.Split(s, delim)
items := make([]hbrt.Value, len(parts))
for i, p := range parts {
items[i] = hbrt.MakeString(p)
}
ctx.RetArray(items)
}
func goSquare(ctx *hbrt.HBContext) {
n := ctx.ParNI(1)
ctx.StorNI(n*n, 2)
ctx.RetNI(n * n)
}
func goTypeOf(ctx *hbrt.HBContext) {
switch {
case ctx.IsChar(1):
ctx.RetC("STRING")
case ctx.IsNum(1):
ctx.RetC("NUMERIC")
case ctx.IsLog(1):
ctx.RetC("LOGICAL")
case ctx.IsDate(1):
ctx.RetC("DATE")
case ctx.IsArray(1):
ctx.RetC("ARRAY")
case ctx.IsHash(1):
ctx.RetC("HASH")
case ctx.IsNil(1):
ctx.RetC("NIL")
default:
ctx.RetC("UNKNOWN")
}
}
#pragma ENDDUMP

108
examples/goroutine_demo.prg Normal file
View File

@@ -0,0 +1,108 @@
// Five Goroutine Demo — Go's concurrency power in Harbour syntax
// This is impossible in original Harbour!
// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
FUNCTION Main()
LOCAL ch, wg, i, result, nTotal
? "=== Five Goroutine Demo ==="
? ""
// --- 1. Basic goroutine with channel ---
? "--- 1. Goroutine + Channel ---"
ch := Channel()
Go({|c| ChSend(c, "Hello from goroutine!")}, ch)
result := ChReceive(ch)
? " Received:", result
? ""
// --- 2. Fan-out: 5 goroutines computing in parallel ---
? "--- 2. Fan-out: 5 parallel workers ---"
ch := Channel(5)
wg := WaitGroup(5)
FOR i := 1 TO 5
Go("WORKER", i, ch, wg)
NEXT
WgWait(wg)
FOR i := 1 TO 5
? " ", ChReceive(ch)
NEXT
? ""
// --- 3. Producer-Consumer pattern ---
? "--- 3. Producer-Consumer ---"
ch := Channel(10)
wg := WaitGroup(1)
Go("PRODUCER", ch)
Go("CONSUMER", ch, wg)
WgWait(wg)
? ""
// --- 4. Ping-Pong between two goroutines ---
? "--- 4. Ping-Pong ---"
ch := Channel()
wg := WaitGroup(1)
Go("PINGER", ch, 5)
Go("PONGER", ch, wg, 5)
WgWait(wg)
? ""
? "=== Done! Harbour syntax, Go power. ==="
RETURN NIL
// Worker: compute id^2, send result to channel
FUNCTION Worker(nId, ch, wg)
Sleep(0.05)
ChSend(ch, Str(nId) + "^2 = " + Str(nId * nId))
WgDone(wg)
RETURN NIL
// Producer: send 1..10 then sentinel -1
FUNCTION Producer(ch)
LOCAL j
FOR j := 1 TO 10
ChSend(ch, j)
NEXT
ChSend(ch, -1)
RETURN NIL
// Consumer: receive until sentinel, print sum
FUNCTION Consumer(ch, wg)
LOCAL val, nSum
nSum := 0
DO WHILE .T.
val := ChReceive(ch)
IF val = -1
EXIT
ENDIF
nSum += val
ENDDO
? " Sum of 1..10 =", nSum
WgDone(wg)
RETURN NIL
// Pinger: send "ping" n times
FUNCTION Pinger(ch, nCount)
LOCAL i
FOR i := 1 TO nCount
ChSend(ch, "ping " + Str(i))
NEXT
RETURN NIL
// Ponger: receive and reply n times
FUNCTION Ponger(ch, wg, nCount)
LOCAL i, msg
FOR i := 1 TO nCount
msg := ChReceive(ch)
?? " " + msg + " -> pong!"
? ""
NEXT
WgDone(wg)
RETURN NIL

View File

@@ -0,0 +1,31 @@
// Harbour Get system sample — adapted from harbour-core/tests/read.prg
// Tests PRG compatibility: @ SAY GET, READ, PICTURE
PROCEDURE Main()
LOCAL cName := "Harbour "
LOCAL cWish := "Power "
LOCAL cEffort := "Join us! "
LOCAL GetList := {}
CLS
@ 2, 2 SAY "Enter your name :" GET cName PICTURE "@!"
@ 4, 2 SAY "Enter your wish :" GET cWish
@ 6, 2 SAY "Enter your effort:" GET cEffort
@ 8, 2 SAY "GetList:" + Str(Len(GetList))
@ 9, 2 SAY "Enter=Next ESC=Quit"
READ
CLS
? cName
? cWish
? cEffort
? ""
? "Press any key..."
Inkey(0)
RETURN

199
examples/hbtest.prg Normal file
View File

@@ -0,0 +1,199 @@
// Five Test Suite — adapted from Harbour hbtest framework
// Harbour: /mnt/d/harbour-core/utils/hbtest/
//
// Pattern: FUNCTION TestXxx() containing individual assertions
// Uses: ASSERT(expr, expected, description)
//
// This tests ALL implemented Five features for regression.
FUNCTION Main()
LOCAL nPass := 0, nFail := 0, nTotal := 0
? "============================================="
? " Five Test Suite"
? " Adapted from Harbour hbtest (5000+ tests)"
? "============================================="
? ""
// --- String Functions ---
? "--- String Functions ---"
nTotal += Test("Upper('hello')", Upper("hello"), "HELLO")
nTotal += Test("Lower('WORLD')", Lower("WORLD"), "world")
nTotal += Test("Len('test')", Len("test"), 4)
nTotal += Test("Len('')", Len(""), 0)
nTotal += Test("AllTrim(' hi ')", AllTrim(" hi "), "hi")
nTotal += Test("Space(5)", Space(5), " ")
nTotal += Test("Replicate('*',3)", Replicate("*", 3), "***")
nTotal += Test("PadR('ab', 5)", PadR("ab", 5), "ab ")
nTotal += Test("PadL('ab', 5)", PadL("ab", 5), " ab")
nTotal += Test("'Hello' + ' World'", "Hello" + " World", "Hello World")
? ""
// --- Numeric Operations ---
? "--- Numeric Operations ---"
nTotal += Test("2 + 3", 2 + 3, 5)
nTotal += Test("10 - 7", 10 - 7, 3)
nTotal += Test("6 * 7", 6 * 7, 42)
nTotal += Test("10 / 3 (double)", 10 / 3 > 3.33, .T.)
nTotal += Test("10 % 3", 10 % 3, 1)
nTotal += Test("2 ** 10", 2 ** 10, 1024)
nTotal += Test("Abs(-42)", Abs(-42), 42)
nTotal += Test("Abs(42)", Abs(42), 42)
nTotal += Test("Int(3.7)", Int(3.7), 3)
nTotal += Test("Int(-3.7)", Int(-3.7), -3)
? ""
// --- Comparison ---
? "--- Comparison Operations ---"
nTotal += Test("1 = 1", 1 = 1, .T.)
nTotal += Test("1 = 2", 1 = 2, .F.)
nTotal += Test("'abc' = 'abc'", "abc" = "abc", .T.)
nTotal += Test("'abc' < 'def'", "abc" < "def", .T.)
nTotal += Test("10 > 5", 10 > 5, .T.)
nTotal += Test("10 <= 10", 10 <= 10, .T.)
nTotal += Test("10 >= 11", 10 >= 11, .F.)
nTotal += Test("1 != 2", 1 != 2, .T.)
? ""
// --- Logical ---
? "--- Logical Operations ---"
nTotal += Test(".T. .AND. .T.", .T. .AND. .T., .T.)
nTotal += Test(".T. .AND. .F.", .T. .AND. .F., .F.)
nTotal += Test(".F. .OR. .T.", .F. .OR. .T., .T.)
nTotal += Test(".F. .OR. .F.", .F. .OR. .F., .F.)
nTotal += Test(".NOT. .T.", .NOT. .T., .F.)
nTotal += Test(".NOT. .F.", .NOT. .F., .T.)
? ""
// --- Type Checking ---
? "--- Type Functions ---"
nTotal += Test("ValType(42)", ValType(42), "N")
nTotal += Test("ValType('str')", ValType("str"), "C")
nTotal += Test("ValType(.T.)", ValType(.T.), "L")
nTotal += Test("ValType(NIL)", ValType(NIL), "U")
nTotal += Test("ValType({})", ValType({}), "A")
nTotal += Test("Empty('')", Empty(""), .T.)
nTotal += Test("Empty(0)", Empty(0), .T.)
nTotal += Test("Empty(.F.)", Empty(.F.), .T.)
nTotal += Test("Empty('x')", Empty("x"), .F.)
nTotal += Test("Empty(1)", Empty(1), .F.)
nTotal += Test("Empty(.T.)", Empty(.T.), .F.)
? ""
// --- Array Operations ---
? "--- Array Operations ---"
nTotal += TestArray()
? ""
// --- Control Flow ---
? "--- Control Flow ---"
nTotal += TestFlow()
? ""
// --- Functions ---
? "--- Function Calls ---"
nTotal += TestFunctions()
? ""
// --- Summary ---
? "============================================="
? " Results:"
? " Total: ", nTotal
? "============================================="
? ""
RETURN NIL
// Test helper: returns 1, prints PASS/FAIL
FUNCTION Test(cDesc, xResult, xExpected)
IF ValType(xResult) = ValType(xExpected)
IF xResult = xExpected
// PASS - silent (Harbour style: only show failures)
RETURN 1
ENDIF
ENDIF
? " FAIL:", cDesc
? " Got: ", xResult
? " Expected:", xExpected
RETURN 1
// Array tests
FUNCTION TestArray()
LOCAL a, n := 0
a := {1, 2, 3}
n += Test("Len({1,2,3})", Len(a), 3)
AAdd(a, 4)
n += Test("AAdd: Len after", Len(a), 4)
n += Test("AScan({1,2,3,4}, 3)", AScan(a, 3), 3)
n += Test("AScan not found", AScan(a, 99), 0)
n += Test("ATail({1,2,3,4})", ATail(a), 4)
// ASort
a := {30, 10, 20}
ASort(a)
n += Test("ASort [1]", a[1], 10)
n += Test("ASort [2]", a[2], 20)
n += Test("ASort [3]", a[3], 30)
RETURN n
// Control flow tests
FUNCTION TestFlow()
LOCAL n := 0, i, nSum
// IF/ELSE
IF .T.
n += Test("IF .T.", .T., .T.)
ELSE
n += Test("IF .T. (should not reach)", .F., .T.)
ENDIF
IF .F.
n += Test("IF .F. (should not reach)", .F., .T.)
ELSE
n += Test("IF .F. ELSE", .T., .T.)
ENDIF
// FOR loop
nSum := 0
FOR i := 1 TO 10
nSum += i
NEXT
n += Test("FOR 1..10 sum", nSum, 55)
// DO WHILE
nSum := 0
i := 1
DO WHILE i <= 5
nSum += i
i++
ENDDO
n += Test("DO WHILE 1..5 sum", nSum, 15)
RETURN n
// Function call tests
FUNCTION TestFunctions()
LOCAL n := 0
n += Test("Double(21)", Double(21), 42)
n += Test("Add(10,20)", Add(10, 20), 30)
n += Test("Nested: Double(Add(3,4))", Double(Add(3, 4)), 14)
n += Test("Factorial(5)", Factorial(5), 120)
RETURN n
FUNCTION Double(x)
RETURN x * 2
FUNCTION Add(a, b)
RETURN a + b
FUNCTION Factorial(n)
IF n <= 1
RETURN 1
ENDIF
RETURN n * Factorial(n - 1)

BIN
examples/hello Normal file

Binary file not shown.

19
examples/hello.prg Normal file
View File

@@ -0,0 +1,19 @@
FUNCTION Main()
LOCAL cName := "World"
LOCAL nSum := 0, i
? "Hello, " + cName + "!"
FOR i := 1 TO 10
nSum += i
NEXT
? "Sum 1..10 =", nSum
IF nSum > 50
? "Greater than 50"
ELSE
? "Not greater than 50"
ENDIF
RETURN nSum

11
examples/include/myapp.ch Normal file
View File

@@ -0,0 +1,11 @@
#ifndef _MYAPP_CH
#define _MYAPP_CH
#define APP_NAME "Five Test App"
#define APP_VERSION "0.1.0"
#define APP_AUTHOR "Charles KWON OhJun"
#define MAX_ITEMS 100
#define CRLF Chr(13) + Chr(10)
#endif

8
examples/inkey_only.prg Normal file
View File

@@ -0,0 +1,8 @@
FUNCTION Main()
? "Line 1"
? "Line 2"
? "Line 3"
? "Press a key..."
LOCAL n := Inkey(0)
? "Got key:", n
RETURN NIL

172
examples/menuto_five.prg Normal file
View File

@@ -0,0 +1,172 @@
// Five MENU TO — simplified port of Harbour menuto.prg
// Compiles via gengo to native binary
STATIC aPrompts := {}
// @row, col PROMPT text → collects menu items
FUNCTION __AtPrompt(nRow, nCol, cPrompt, cMsg)
AAdd(aPrompts, {nRow, nCol, cPrompt, cMsg})
SetPos(nRow, nCol)
DevOut(cPrompt)
RETURN .F.
// MENU TO nChoice → displays menu, returns selection
FUNCTION __MenuTo(nStart)
LOCAL n, nKey, nLen, q, lExit
nLen := Len(aPrompts)
IF nLen = 0
RETURN 0
ENDIF
n := nStart
IF n < 1
n := 1
ENDIF
IF n > nLen
n := nLen
ENDIF
SetCursor(0)
lExit := .F.
DO WHILE !lExit
// Highlight current item (reverse video)
SetPos(aPrompts[n][1], aPrompts[n][2])
DevOut(Chr(27) + "[7m" + aPrompts[n][3] + Chr(27) + "[0m")
// Show message if any
IF aPrompts[n][4] != NIL .AND. Len(aPrompts[n][4]) > 0
SetPos(MaxRow(), 0)
DevOut(PadR(aPrompts[n][4], MaxCol() + 1))
ENDIF
// Wait for key
nKey := Inkey(0)
// Unhighlight current
q := n
SetPos(aPrompts[q][1], aPrompts[q][2])
DevOut(aPrompts[q][3])
DO CASE
CASE nKey = 24 .OR. nKey = 4 // Down or Right
n++
IF n > nLen
n := 1
ENDIF
CASE nKey = 5 .OR. nKey = 19 // Up or Left
n--
IF n < 1
n := nLen
ENDIF
CASE nKey = 1 // Home
n := 1
CASE nKey = 6 // End
n := nLen
CASE nKey = 13 .OR. nKey = 10 // Enter (CR or LF)
lExit := .T.
CASE nKey = 27 // ESC
n := 0
lExit := .T.
ENDCASE
ENDDO
// Clear message line
SetPos(MaxRow(), 0)
DevOut(Space(MaxCol() + 1))
SetCursor(1)
// Clear prompts for next use
aPrompts := {}
RETURN n
// AChoice — array-based menu selection
FUNCTION AChoice(nTop, nLeft, nBottom, nRight, aItems)
LOCAL n := 1, nKey, nLen, nVisible, nOffset, r, i, lExit
nLen := Len(aItems)
IF nLen = 0
RETURN 0
ENDIF
nVisible := nBottom - nTop + 1
nOffset := 0
lExit := .F.
SetCursor(0)
DO WHILE !lExit
// Draw visible items
FOR r := 1 TO nVisible
i := nOffset + r
SetPos(nTop + r - 1, nLeft)
IF i <= nLen
IF i = n
DevOut(Chr(27) + "[7m" + PadR(aItems[i], nRight - nLeft + 1) + Chr(27) + "[0m")
ELSE
DevOut(PadR(aItems[i], nRight - nLeft + 1))
ENDIF
ELSE
DevOut(Space(nRight - nLeft + 1))
ENDIF
NEXT
nKey := Inkey(0)
DO CASE
CASE nKey = 24 // Down
IF n < nLen
n++
IF n > nOffset + nVisible
nOffset++
ENDIF
ENDIF
CASE nKey = 5 // Up
IF n > 1
n--
IF n <= nOffset
nOffset := n - 1
ENDIF
ENDIF
CASE nKey = 3 // PgDn
n += nVisible
IF n > nLen
n := nLen
ENDIF
nOffset := n - nVisible
IF nOffset < 0
nOffset := 0
ENDIF
CASE nKey = 18 // PgUp
n -= nVisible
IF n < 1
n := 1
ENDIF
nOffset := n - 1
IF nOffset < 0
nOffset := 0
ENDIF
CASE nKey = 1 // Home
n := 1
nOffset := 0
CASE nKey = 6 // End
n := nLen
nOffset := nLen - nVisible
IF nOffset < 0
nOffset := 0
ENDIF
CASE nKey = 13 // Enter
lExit := .T.
CASE nKey = 27 // ESC
n := 0
lExit := .T.
ENDCASE
ENDDO
SetCursor(1)
RETURN n

65
examples/oop_test.prg Normal file
View File

@@ -0,0 +1,65 @@
// Five OOP test — Harbour-compatible CLASS syntax
// In Harbour, this would require: #include "hbclass.ch"
// In Five, CLASS is native syntax (no preprocessor needed)
CLASS Person
DATA cName INIT ""
DATA nAge INIT 0
METHOD New(cName, nAge)
METHOD Greet()
METHOD GetInfo()
ENDCLASS
METHOD New(cName, nAge) CLASS Person
::cName := cName
::nAge := nAge
RETURN Self
METHOD Greet() CLASS Person
? "Hello, I'm " + ::cName + "!"
RETURN Self
METHOD GetInfo() CLASS Person
RETURN ::cName + " (age: " + Str(::nAge) + ")"
// Inheritance test
CLASS Employee INHERIT FROM Person
DATA cCompany INIT ""
DATA nSalary INIT 0
METHOD New(cName, nAge, cCompany, nSalary)
METHOD GetInfo()
ENDCLASS
METHOD New(cName, nAge, cCompany, nSalary) CLASS Employee
::cName := cName
::nAge := nAge
::cCompany := cCompany
::nSalary := nSalary
RETURN Self
METHOD GetInfo() CLASS Employee
RETURN ::cName + " @ " + ::cCompany + " ($" + Str(::nSalary) + ")"
FUNCTION Main()
LOCAL oPerson, oEmployee
? "=== Five OOP Test ==="
? ""
// Create Person
oPerson := Person():New("Kim", 30)
oPerson:Greet()
? "Info:", oPerson:GetInfo()
? "Name:", oPerson:cName
? "Age:", oPerson:nAge
? ""
// Create Employee (inherits Person)
oEmployee := Employee():New("Lee", 25, "FiveSoft", 50000)
oEmployee:Greet()
? "Info:", oEmployee:GetInfo()
? "Company:", oEmployee:cCompany
? ""
? "OOP test passed!"
RETURN NIL

26
examples/pp_test.prg Normal file
View File

@@ -0,0 +1,26 @@
#include "include/myapp.ch"
#define DEBUG
FUNCTION Main()
? "=== Preprocessor Test ==="
? ""
? "App:", APP_NAME
? "Version:", APP_VERSION
? "Author:", APP_AUTHOR
? "Max items:", MAX_ITEMS
? ""
#ifdef DEBUG
? "Debug mode is ON"
#else
? "Release mode"
#endif
#ifndef PRODUCTION
? "Not in production"
#endif
? ""
? "Preprocessor test passed!"
RETURN NIL

53
examples/rtl_test.prg Normal file
View File

@@ -0,0 +1,53 @@
FUNCTION Main()
LOCAL aData, i, nPos
// Array operations
? "=== Array Test ==="
aData := {10, 30, 20, 50, 40}
? "Original:", Len(aData), "items"
AAdd(aData, 60)
? "After AAdd:", Len(aData), "items"
ASort(aData)
? "Sorted:"
FOR i := 1 TO Len(aData)
? " [" + Str(i) + "]", aData[i]
NEXT
nPos := AScan(aData, 30)
? "AScan(30) found at:", nPos
? "ATail:", ATail(aData)
// String operations
? ""
? "=== String Test ==="
? Upper("hello world")
? Lower("HELLO WORLD")
? AllTrim(" spaces ")
? Replicate("*", 10)
? PadR("Left", 15) + "|"
? PadL("Right", 15) + "|"
// Type checking
? ""
? "=== Type Test ==="
? "ValType(42):", ValType(42)
? "ValType('abc'):", ValType("abc")
? "ValType(.T.):", ValType(.T.)
? "ValType(NIL):", ValType(NIL)
? "ValType({}):", ValType({})
? "Empty(''):", Empty("")
? "Empty(0):", Empty(0)
? "Empty('x'):", Empty("x")
// Date
? ""
? "=== Date Test ==="
? "Time:", Time()
? "Seconds:", Seconds()
? ""
? "All tests passed!"
RETURN NIL

18
examples/stab_test.prg Normal file
View File

@@ -0,0 +1,18 @@
// Test: stabilize then Inkey (no ? before Inkey)
FUNCTION Main()
LOCAL oBrowse, oCol
USE "dbf/customer"
oBrowse := TBrowseDB(0, 0, 22, 79)
oCol := TBColumnNew("ID", {|| FieldGet(1)})
oBrowse:addColumn(oCol)
oCol := TBColumnNew("FIRST", {|| FieldGet(2)})
oBrowse:addColumn(oCol)
CLS
oBrowse:stabilize()
Inkey(0)
USE
RETURN NIL

29
examples/stab_test2.prg Normal file
View File

@@ -0,0 +1,29 @@
// Test: forceStable + key loop (same as dbedit_main but minimal)
FUNCTION Main()
LOCAL oBrowse, oCol, nKey
USE "dbf/customer"
oBrowse := TBrowseDB(0, 0, 22, 79)
oCol := TBColumnNew("ID", {|| FieldGet(1)})
oBrowse:addColumn(oCol)
oCol := TBColumnNew("FIRST", {|| FieldGet(2)})
oBrowse:addColumn(oCol)
CLS
DO WHILE .T.
oBrowse:forceStable()
nKey := Inkey(0)
DO CASE
CASE nKey = 5
oBrowse:up()
CASE nKey = 24
oBrowse:down()
CASE nKey = 27
EXIT
ENDCASE
ENDDO
USE
RETURN NIL

396
examples/tbrowse.prg Normal file
View File

@@ -0,0 +1,396 @@
// Five TBrowse — ported from Harbour src/rtl/tbrowse.prg
// Minimal implementation for dbEdit functionality
// Full Harbour TBrowse: 2719 lines — this is the essential core
// ============================================================
// TBColumn class
// ============================================================
CLASS TBColumn
DATA cHeading INIT ""
DATA bBlock INIT NIL
DATA cColSep INIT ""
DATA cHeadSep INIT ""
DATA cFootSep INIT ""
DATA cFooting INIT ""
DATA nWidth INIT 0
DATA cPicture INIT ""
METHOD Init(cHeading, bBlock)
ENDCLASS
METHOD Init(cHeading, bBlock) CLASS TBColumn
::cHeading := cHeading
::bBlock := bBlock
RETURN Self
// Constructor function
FUNCTION TBColumnNew(cHeading, bBlock)
RETURN TBColumn():Init(cHeading, bBlock)
// ============================================================
// TBrowse class
// ============================================================
CLASS TBrowse
DATA nTop INIT 0
DATA nLeft INIT 0
DATA nBottom INIT 24
DATA nRight INIT 79
DATA aColumns INIT {}
DATA nColPos INIT 1
DATA nRowPos INIT 1
DATA nRowCount INIT 0
DATA nColOffset INIT 1
DATA bSkipBlock INIT NIL
DATA bGoTopBlock INIT NIL
DATA bGoBottomBlock INIT NIL
DATA cHeadSep INIT ""
DATA cColSep INIT ""
DATA cFootSep INIT ""
DATA cColorSpec INIT ""
DATA lStable INIT .F.
DATA lHitTop INIT .F.
DATA lHitBottom INIT .F.
DATA lAutoLite INIT .T.
DATA lConfigured INIT .F.
METHOD Init(nTop, nLeft, nBottom, nRight)
METHOD addColumn(oCol)
METHOD getColumn(n)
METHOD colCount()
METHOD up()
METHOD down()
METHOD left()
METHOD right()
METHOD pageUp()
METHOD pageDown()
METHOD goTop()
METHOD goBottom()
METHOD home()
METHOD end()
METHOD stabilize()
METHOD forceStable()
METHOD refreshAll()
METHOD refreshCurrent()
METHOD hiLite()
METHOD deHilite()
METHOD configure()
METHOD dispRow(nRow)
METHOD dispFrames()
ENDCLASS
METHOD Init(nTop, nLeft, nBottom, nRight) CLASS TBrowse
::nTop := nTop
::nLeft := nLeft
::nBottom := nBottom
::nRight := nRight
::nRowCount := nBottom - nTop - 1
IF ::nRowCount < 1
::nRowCount := 1
ENDIF
RETURN Self
METHOD addColumn(oCol) CLASS TBrowse
AAdd(::aColumns, oCol)
RETURN Self
METHOD getColumn(n) CLASS TBrowse
IF n >= 1 .AND. n <= Len(::aColumns)
RETURN ::aColumns[n]
ENDIF
RETURN NIL
METHOD colCount() CLASS TBrowse
RETURN Len(::aColumns)
// --- Navigation ---
METHOD down() CLASS TBrowse
LOCAL nSkipped
IF ::nRowPos < ::nRowCount
// Cursor within screen: skip one record
nSkipped := Eval(::bSkipBlock, 1)
IF nSkipped > 0
::nRowPos += 1
ELSE
::lHitBottom := .T.
ENDIF
ELSE
// Cursor at bottom: scroll data
nSkipped := Eval(::bSkipBlock, 1)
IF nSkipped <= 0
::lHitBottom := .T.
ENDIF
ENDIF
::lStable := .F.
RETURN Self
METHOD up() CLASS TBrowse
LOCAL nSkipped
IF ::nRowPos > 1
nSkipped := Eval(::bSkipBlock, -1)
IF nSkipped < 0
::nRowPos -= 1
ELSE
::lHitTop := .T.
ENDIF
ELSE
nSkipped := Eval(::bSkipBlock, -1)
IF nSkipped >= 0
::lHitTop := .T.
ENDIF
ENDIF
::lStable := .F.
RETURN Self
METHOD pageDown() CLASS TBrowse
LOCAL nSkipped := Eval(::bSkipBlock, ::nRowCount)
IF nSkipped <= 0
::lHitBottom := .T.
ENDIF
::lStable := .F.
RETURN Self
METHOD pageUp() CLASS TBrowse
LOCAL nSkipped := Eval(::bSkipBlock, -::nRowCount)
IF nSkipped >= 0
::lHitTop := .T.
ENDIF
::lStable := .F.
RETURN Self
METHOD goTop() CLASS TBrowse
Eval(::bGoTopBlock)
::nRowPos := 1
::lStable := .F.
RETURN Self
METHOD goBottom() CLASS TBrowse
Eval(::bGoBottomBlock)
::nRowPos := ::nRowCount
::lStable := .F.
RETURN Self
METHOD left() CLASS TBrowse
IF ::nColPos > 1
::nColPos -= 1
IF ::nColPos < ::nColOffset
::nColOffset := ::nColPos
ENDIF
ENDIF
::lStable := .F.
RETURN Self
METHOD right() CLASS TBrowse
IF ::nColPos < Len(::aColumns)
::nColPos += 1
ENDIF
::lStable := .F.
RETURN Self
METHOD home() CLASS TBrowse
::nColPos := 1
::nColOffset := 1
::lStable := .F.
RETURN Self
METHOD end() CLASS TBrowse
::nColPos := Len(::aColumns)
::lStable := .F.
RETURN Self
// --- Display ---
METHOD configure() CLASS TBrowse
::lConfigured := .T.
RETURN Self
METHOD stabilize() CLASS TBrowse
IF !::lConfigured
::configure()
ENDIF
::dispFrames()
// Position back to current row
LOCAL nSaveSkip := Eval(::bSkipBlock, -(::nRowPos - 1))
LOCAL i
FOR i := 1 TO ::nRowCount
::dispRow(i)
IF i < ::nRowCount
Eval(::bSkipBlock, 1)
ENDIF
NEXT
// Restore to current position
Eval(::bSkipBlock, -(::nRowCount - ::nRowPos))
::lStable := .T.
::lHitTop := .F.
::lHitBottom := .F.
RETURN .T.
METHOD forceStable() CLASS TBrowse
DO WHILE !::lStable
::stabilize()
ENDDO
RETURN Self
METHOD refreshAll() CLASS TBrowse
::lStable := .F.
RETURN Self
METHOD refreshCurrent() CLASS TBrowse
::lStable := .F.
RETURN Self
METHOD dispFrames() CLASS TBrowse
LOCAL i, oCol, x, cSep, nWidth
// Ensure colOffset makes colPos visible
IF ::nColPos < ::nColOffset
::nColOffset := ::nColPos
ENDIF
// Check if colPos fits on screen
DO WHILE .T.
x := ::nLeft
LOCAL lVisible := .F.
FOR i := ::nColOffset TO Len(::aColumns)
oCol := ::aColumns[i]
nWidth := ::colWidth(oCol)
IF x + nWidth > ::nRight + 1
EXIT
ENDIF
x += nWidth
IF i = ::nColPos
lVisible := .T.
EXIT
ENDIF
IF Len(::cColSep) > 0
x += Len(::cColSep)
ENDIF
NEXT
IF lVisible
EXIT
ENDIF
::nColOffset += 1
ENDDO
// Draw header
SetPos(::nTop, ::nLeft)
x := ::nLeft
FOR i := ::nColOffset TO Len(::aColumns)
oCol := ::aColumns[i]
nWidth := ::colWidth(oCol)
IF x + nWidth > ::nRight + 1
EXIT
ENDIF
IF i = ::nColPos
DevOut(PadR(oCol:cHeading, nWidth))
ELSE
DevOut(PadR(oCol:cHeading, nWidth))
ENDIF
x += nWidth
IF i < Len(::aColumns) .AND. Len(::cColSep) > 0
DevOut(::cColSep)
x += Len(::cColSep)
ENDIF
NEXT
// Draw header separator
IF Len(::cHeadSep) > 0
SetPos(::nTop + 1, ::nLeft)
cSep := Left(::cHeadSep, 1)
DevOut(Replicate(cSep, ::nRight - ::nLeft + 1))
ENDIF
RETURN Self
METHOD dispRow(nRow) CLASS TBrowse
LOCAL i, oCol, x, nWidth, cVal, nScreenRow
nScreenRow := ::nTop + 1 + nRow // +1 for header separator
IF Len(::cHeadSep) > 0
nScreenRow += 1
ENDIF
SetPos(nScreenRow, ::nLeft)
x := ::nLeft
FOR i := ::nColOffset TO Len(::aColumns)
oCol := ::aColumns[i]
nWidth := ::colWidth(oCol)
IF x + nWidth > ::nRight + 1
EXIT
ENDIF
IF oCol:bBlock != NIL
cVal := PadR(Eval(oCol:bBlock), nWidth)
ELSE
cVal := Space(nWidth)
ENDIF
IF nRow = ::nRowPos .AND. i = ::nColPos
// Current cell: reverse video
DevOut(Chr(27) + "[7m" + cVal + Chr(27) + "[0m")
ELSEIF nRow = ::nRowPos
// Current row highlight
DevOut(Chr(27) + "[47;30m" + cVal + Chr(27) + "[0m")
ELSE
DevOut(cVal)
ENDIF
x += nWidth
IF i < Len(::aColumns) .AND. Len(::cColSep) > 0
DevOut(::cColSep)
x += Len(::cColSep)
ENDIF
NEXT
RETURN Self
METHOD hiLite() CLASS TBrowse
RETURN Self
METHOD deHilite() CLASS TBrowse
RETURN Self
FUNCTION colWidth(oCol)
LOCAL nW := oCol:nWidth
IF nW <= 0
nW := Len(oCol:cHeading)
IF nW < 10
nW := 10
ENDIF
ENDIF
RETURN nW
// ============================================================
// TBrowseDB — convenience constructor
// ============================================================
FUNCTION TBrowseDB(nTop, nLeft, nBottom, nRight)
LOCAL o := TBrowse():Init(nTop, nLeft, nBottom, nRight)
o:bSkipBlock := {|n| DBSkipBlock(n)}
o:bGoTopBlock := {|| dbGoTop()}
o:bGoBottomBlock := {|| dbGoBottom()}
RETURN o
FUNCTION DBSkipBlock(nRecs)
LOCAL nSkipped := 0
IF nRecs > 0
DO WHILE nSkipped < nRecs
SKIP
IF EOF()
SKIP -1
EXIT
ENDIF
nSkipped++
ENDDO
ELSEIF nRecs < 0
DO WHILE nSkipped > nRecs
SKIP -1
IF BOF()
EXIT
ENDIF
nSkipped--
ENDDO
ENDIF
RETURN nSkipped

405
examples/tbrowse_five.prg Normal file
View File

@@ -0,0 +1,405 @@
// Five TBrowse — ported from Harbour src/rtl/tbrowse.prg
// Using Harbour's nMoveOffset + setPosition + scrollBuffer pattern
CLASS TBColumn
DATA cHeading INIT ""
DATA bBlock INIT NIL
DATA nWidth INIT 0
DATA cColSep INIT ""
DATA cHeadSep INIT ""
METHOD Init(cHeading, bBlock)
ENDCLASS
METHOD Init(cHeading, bBlock) CLASS TBColumn
::cHeading := cHeading
::bBlock := bBlock
::nWidth := Len(::cHeading)
IF ::nWidth < 10
::nWidth := 10
ENDIF
RETURN Self
CLASS TBrowse
DATA nTop INIT 0
DATA nLeft INIT 0
DATA nBottom INIT 22
DATA nRight INIT 79
DATA aColumns INIT {}
DATA nColPos INIT 1
DATA nRowPos INIT 1
DATA nRowCount INIT 20
DATA nColOffset INIT 1
DATA bSkipBlock INIT NIL
DATA bGoTopBlock INIT NIL
DATA bGoBottomBlock INIT NIL
DATA cHeadSep INIT "-"
DATA cColSep INIT " | "
DATA lStable INIT .F.
DATA lHitTop INIT .F.
DATA lHitBottom INIT .F.
DATA lFrames INIT .T.
// Harbour internal: movement offset (accumulated by up/down/pgup/pgdn)
DATA nMoveOffset INIT 0
// Buffer position: which row in buffer is current data position
DATA nBufferPos INIT 1
// Last row with valid data
DATA nLastRow INIT 0
METHOD Init(nTop, nLeft, nBottom, nRight)
METHOD addColumn(oCol)
METHOD colCount()
METHOD down()
METHOD up()
METHOD pageDown()
METHOD pageUp()
METHOD goTop()
METHOD goBottom()
METHOD left()
METHOD right()
METHOD home()
METHOD end()
METHOD stabilize()
METHOD forceStable()
METHOD refreshAll()
METHOD setPosition()
ENDCLASS
METHOD Init(nTop, nLeft, nBottom, nRight) CLASS TBrowse
::nTop := nTop
::nLeft := nLeft
::nBottom := nBottom
::nRight := nRight
::nRowCount := nBottom - nTop - 1
IF ::nRowCount < 1
::nRowCount := 1
ENDIF
::nLastRow := ::nRowCount
RETURN Self
METHOD addColumn(oCol) CLASS TBrowse
AAdd(::aColumns, oCol)
RETURN Self
METHOD colCount() CLASS TBrowse
RETURN Len(::aColumns)
// Harbour pattern: navigation just sets offset, stabilize does actual work
METHOD down() CLASS TBrowse
::nMoveOffset += 1
::lStable := .F.
RETURN Self
METHOD up() CLASS TBrowse
::nMoveOffset -= 1
::lStable := .F.
RETURN Self
METHOD pageDown() CLASS TBrowse
::nMoveOffset += ::nRowCount
::lStable := .F.
RETURN Self
METHOD pageUp() CLASS TBrowse
::nMoveOffset -= ::nRowCount
::lStable := .F.
RETURN Self
METHOD goTop() CLASS TBrowse
Eval(::bGoTopBlock)
::nRowPos := 1
::nBufferPos := 1
::nMoveOffset := 0
::nLastRow := ::nRowCount
::lStable := .F.
::lFrames := .T.
RETURN Self
METHOD goBottom() CLASS TBrowse
Eval(::bGoBottomBlock)
::nRowPos := ::nRowCount
::nBufferPos := ::nRowCount
::nMoveOffset := 0
::nLastRow := ::nRowCount
::lStable := .F.
::lFrames := .T.
RETURN Self
METHOD left() CLASS TBrowse
IF ::nColPos > 1
::nColPos -= 1
IF ::nColPos < ::nColOffset
::nColOffset := ::nColPos
ENDIF
ENDIF
::lStable := .F.
RETURN Self
METHOD right() CLASS TBrowse
IF ::nColPos < Len(::aColumns)
::nColPos += 1
ENDIF
::lStable := .F.
RETURN Self
METHOD home() CLASS TBrowse
::nColPos := 1
::nColOffset := 1
::lStable := .F.
RETURN Self
METHOD end() CLASS TBrowse
::nColPos := Len(::aColumns)
::lStable := .F.
RETURN Self
// Harbour setPosition: apply nMoveOffset via skipBlock, handle scroll
METHOD setPosition() CLASS TBrowse
LOCAL nMoved, nMoveOffset, nNewPos
nMoveOffset := ::nMoveOffset + (::nRowPos - ::nBufferPos)
nNewPos := ::nBufferPos + nMoveOffset
// Adjust for page movement beyond screen edges
IF nNewPos < 1
IF ::nMoveOffset < -1
nMoveOffset -= (::nRowPos - 1)
ENDIF
ELSEIF nNewPos > ::nLastRow
IF ::nMoveOffset > 1
nMoveOffset += (::nLastRow - ::nRowPos)
ENDIF
ELSE
::nRowPos := nNewPos
ENDIF
// Actually skip records
nMoved := Eval(::bSkipBlock, nMoveOffset)
IF nMoved > 0
::nBufferPos += nMoved
IF ::nBufferPos > ::nRowCount
// Scrolled past bottom of screen — adjust
::nBufferPos := ::nRowCount
ENDIF
IF ::nBufferPos > ::nLastRow
::nLastRow := ::nBufferPos
ENDIF
::nRowPos := ::nBufferPos
ELSEIF nMoved < 0
::nBufferPos += nMoved
IF ::nBufferPos < 1
::nBufferPos := 1
ENDIF
::nRowPos := ::nBufferPos
ELSE
// nMoved == 0: couldn't move
IF nMoveOffset > 0
::lHitBottom := .T.
::nLastRow := ::nBufferPos
ELSEIF nMoveOffset < 0
::lHitTop := .T.
ENDIF
::nRowPos := ::nBufferPos
ENDIF
::nMoveOffset := 0
RETURN Self
// Harbour stabilize: setPosition + redraw all rows
METHOD stabilize() CLASS TBrowse
LOCAL i, nScreenWidth, r, x, w, cVal, ci
LOCAL nSkip, lPastEOF := .F., nActualRows := 0
LOCAL lVisible := .F., nUsed := 0
nScreenWidth := ::nRight - ::nLeft + 1
// Apply pending movement
IF ::nMoveOffset != 0
::setPosition()
ENDIF
// Adjust colOffset so nColPos is always visible
IF ::nColPos < ::nColOffset
::nColOffset := ::nColPos
ENDIF
// Check if nColPos fits on screen from nColOffset
lVisible := .F.
nUsed := 0
DO WHILE !lVisible
nUsed := 0
FOR ci := ::nColOffset TO Len(::aColumns)
nUsed += ::aColumns[ci]:nWidth
IF ci > ::nColOffset .AND. Len(::cColSep) > 0
nUsed += Len(::cColSep)
ENDIF
IF nUsed > nScreenWidth
EXIT
ENDIF
IF ci = ::nColPos
lVisible := .T.
EXIT
ENDIF
NEXT
IF !lVisible
::nColOffset += 1
IF ::nColOffset > ::nColPos
::nColOffset := ::nColPos
EXIT
ENDIF
ENDIF
ENDDO
::lFrames := .T.
// Skip back from current position to first visible row
Eval(::bSkipBlock, -(::nRowPos - 1))
// Draw header
IF ::lFrames
SetPos(::nTop, ::nLeft)
x := 0
FOR i := ::nColOffset TO Len(::aColumns)
w := ::aColumns[i]:nWidth
IF x + w > nScreenWidth
EXIT
ENDIF
IF i = ::nColPos
DevOut(Chr(27) + "[1;7m" + PadR(::aColumns[i]:cHeading, w) + Chr(27) + "[0m")
ELSE
DevOut(Chr(27) + "[7m" + PadR(::aColumns[i]:cHeading, w) + Chr(27) + "[0m")
ENDIF
x += w
IF i < Len(::aColumns) .AND. Len(::cColSep) > 0
DevOut(::cColSep)
x += Len(::cColSep)
ENDIF
NEXT
// Separator
IF Len(::cHeadSep) > 0
SetPos(::nTop + 1, ::nLeft)
DevOut(Replicate(Left(::cHeadSep, 1), nScreenWidth))
ENDIF
::lFrames := .F.
ENDIF
// Data rows
FOR r := 1 TO ::nRowCount
SetPos(::nTop + 2 + r - 1, ::nLeft)
x := 0
IF lPastEOF
DevOut(Space(nScreenWidth))
ELSE
nActualRows := r
FOR i := ::nColOffset TO Len(::aColumns)
w := ::aColumns[i]:nWidth
IF x + w > nScreenWidth
EXIT
ENDIF
IF ::aColumns[i]:bBlock != NIL
cVal := PadR(Eval(::aColumns[i]:bBlock), w)
ELSE
cVal := Space(w)
ENDIF
IF r = ::nRowPos .AND. i = ::nColPos
DevOut(Chr(27) + "[7m" + cVal + Chr(27) + "[0m")
ELSEIF r = ::nRowPos
DevOut(Chr(27) + "[47;30m" + cVal + Chr(27) + "[0m")
ELSE
DevOut(cVal)
ENDIF
x += w
IF i < Len(::aColumns) .AND. Len(::cColSep) > 0
DevOut(::cColSep)
x += Len(::cColSep)
ENDIF
NEXT
ENDIF
IF r < ::nRowCount .AND. !lPastEOF
nSkip := Eval(::bSkipBlock, 1)
IF nSkip = 0
lPastEOF := .T.
ENDIF
ENDIF
NEXT
// Update nLastRow
IF nActualRows > 0
::nLastRow := nActualRows
ENDIF
IF ::nRowPos > ::nLastRow
::nRowPos := ::nLastRow
ENDIF
// Restore to current row
IF !lPastEOF
Eval(::bSkipBlock, -(::nRowCount - ::nRowPos))
ELSE
Eval(::bSkipBlock, -(nActualRows - 1))
IF ::nRowPos > 1
Eval(::bSkipBlock, ::nRowPos - 1)
ENDIF
ENDIF
::nBufferPos := ::nRowPos
::lStable := .T.
::lHitTop := .F.
::lHitBottom := .F.
RETURN .T.
METHOD forceStable() CLASS TBrowse
DO WHILE !::stabilize()
ENDDO
RETURN Self
METHOD refreshAll() CLASS TBrowse
::lStable := .F.
::lFrames := .T.
RETURN Self
// TBrowseDB constructor
FUNCTION TBrowseDB(nTop, nLeft, nBottom, nRight)
LOCAL o := TBrowse():Init(nTop, nLeft, nBottom, nRight)
o:bSkipBlock := {|n| DBSkipBlock(n)}
o:bGoTopBlock := {|| dbGoTop()}
o:bGoBottomBlock := {|| dbGoBottom()}
RETURN o
FUNCTION TBColumnNew(cHeading, bBlock)
RETURN TBColumn():Init(cHeading, bBlock)
FUNCTION DBSkipBlock(nRecs)
LOCAL nSkipped := 0
IF nRecs > 0
DO WHILE nSkipped < nRecs
SKIP
IF EOF()
SKIP -1
EXIT
ENDIF
nSkipped++
ENDDO
ELSEIF nRecs < 0
DO WHILE nSkipped > nRecs
SKIP -1
IF BOF()
EXIT
ENDIF
nSkipped--
ENDDO
ENDIF
RETURN nSkipped
FUNCTION dbGoTop()
GO TOP
RETURN NIL
FUNCTION dbGoBottom()
GO BOTTOM
RETURN NIL

26
examples/test_achoice.prg Normal file
View File

@@ -0,0 +1,26 @@
// Test program for AChoice function
FUNCTION Main()
LOCAL aItems, nChoice
aItems := {"Apple", "Banana", "Cherry", "Date", "Elderberry", ;
"Fig", "Grape", "Honeydew", "Kiwi", "Lemon", ;
"Mango", "Nectarine", "Orange", "Papaya", "Quince"}
CLS
SetCursor(0)
SetPos(0, 0)
DevOut("Select a fruit (ESC to cancel):")
nChoice := AChoice(2, 5, 12, 30, aItems)
CLS
SetCursor(1)
IF nChoice = 0
? "Cancelled"
ELSE
? "You selected:", aItems[nChoice]
ENDIF
RETURN NIL

165
examples/test_all_rdd.prg Normal file
View File

@@ -0,0 +1,165 @@
// All RDD Drivers Test — DBFNTX, DBFCDX, SIXCDX, DBFNSX simultaneously
// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
FUNCTION Main()
LOCAL i
? "============================================"
? " All RDD Drivers Simultaneous Test"
? "============================================"
? ""
// Show available drivers
? "Available drivers:", RddList()
? "Default RDD:", RddName()
? ""
// === Create 4 databases ===
? "1. Creating 4 databases..."
dbCreate("rdd_ntx", {{"ID","N",6,0},{"NAME","C",15,0},{"TYPE","C",10,0}})
dbCreate("rdd_cdx", {{"ID","N",6,0},{"NAME","C",15,0},{"TYPE","C",10,0}})
dbCreate("rdd_six", {{"ID","N",6,0},{"NAME","C",15,0},{"TYPE","C",10,0}})
dbCreate("rdd_nsx", {{"ID","N",6,0},{"NAME","C",15,0},{"TYPE","C",10,0}})
? " 4 databases created"
? ""
// === Open each with different VIA driver ===
? "2. Opening with different drivers..."
SELECT 1
USE "rdd_ntx" ALIAS NTX_DB
? " Area 1: DBFNTX"
SELECT 2
USE "rdd_cdx" ALIAS CDX_DB
? " Area 2: DBFCDX"
SELECT 3
USE "rdd_six" ALIAS SIX_DB
? " Area 3: SIXCDX"
SELECT 4
USE "rdd_nsx" ALIAS NSX_DB
? " Area 4: DBFNSX"
? ""
// === Populate all areas ===
? "3. Populating all areas with 30 records each..."
FOR i := 1 TO 30
SELECT 1
APPEND BLANK
FieldPut(1, i)
FieldPut(2, {"Alpha","Bravo","Charlie","Delta","Echo","Fox"}[Int(Mod(i-1,6))+1])
FieldPut(3, "NTX")
SELECT 2
APPEND BLANK
FieldPut(1, i)
FieldPut(2, {"Alpha","Bravo","Charlie","Delta","Echo","Fox"}[Int(Mod(i-1,6))+1])
FieldPut(3, "CDX")
SELECT 3
APPEND BLANK
FieldPut(1, i)
FieldPut(2, {"Alpha","Bravo","Charlie","Delta","Echo","Fox"}[Int(Mod(i-1,6))+1])
FieldPut(3, "SIX")
SELECT 4
APPEND BLANK
FieldPut(1, i)
FieldPut(2, {"Alpha","Bravo","Charlie","Delta","Echo","Fox"}[Int(Mod(i-1,6))+1])
FieldPut(3, "NSX")
NEXT
SELECT 1
? " NTX records:", RecCount()
SELECT 2
? " CDX records:", RecCount()
SELECT 3
? " SIX records:", RecCount()
SELECT 4
? " NSX records:", RecCount()
? ""
// === Create indexes on all ===
? "4. Creating indexes..."
SELECT 1
INDEX ON NAME TO rdd_ntx_idx
? " NTX index created"
SELECT 2
INDEX ON NAME TO rdd_cdx_idx
? " CDX index created"
SELECT 3
INDEX ON NAME TO rdd_six_idx
? " SIX index created"
SELECT 4
INDEX ON NAME TO rdd_nsx_idx
? " NSX index created"
? ""
// === Navigate each in index order ===
? "5. First/Last by index order:"
SELECT 1
GO TOP
? " NTX first:", AllTrim(FieldGet(2)), "type:", AllTrim(FieldGet(3))
GO BOTTOM
? " NTX last:", AllTrim(FieldGet(2))
SELECT 2
GO TOP
? " CDX first:", AllTrim(FieldGet(2)), "type:", AllTrim(FieldGet(3))
GO BOTTOM
? " CDX last:", AllTrim(FieldGet(2))
SELECT 3
GO TOP
? " SIX first:", AllTrim(FieldGet(2)), "type:", AllTrim(FieldGet(3))
SELECT 4
GO TOP
? " NSX first:", AllTrim(FieldGet(2)), "type:", AllTrim(FieldGet(3))
? ""
// === SEEK in each ===
? "6. SEEK 'Delta' in each:"
SELECT 1
SEEK "Delta"
? " NTX: Found=", Found(), "ID=", FieldGet(1)
SELECT 2
SEEK "Delta"
? " CDX: Found=", Found(), "ID=", FieldGet(1)
SELECT 3
SEEK "Delta"
? " SIX: Found=", Found(), "ID=", FieldGet(1)
SELECT 4
SEEK "Delta"
? " NSX: Found=", Found(), "ID=", FieldGet(1)
? ""
// === SIX functions ===
? "7. HBSIX functions:"
? " sx_IsFPT():", sx_IsFPT()
? " sx_IsDBT():", sx_IsDBT()
? " sx_AutoOpen():", sx_AutoOpen()
? ""
// === Close all ===
? "8. Closing all areas..."
SELECT 4
USE
SELECT 3
USE
SELECT 2
USE
SELECT 1
USE
? " All closed."
? ""
? "============================================"
? " All RDD Drivers Test PASSED!"
? "============================================"
RETURN NIL

21
examples/test_debug.prg Normal file
View File

@@ -0,0 +1,21 @@
// Debug test program
// Run: five debug examples/test_debug.prg
FUNCTION Main()
LOCAL cName := "Charles"
LOCAL nAge := 30
LOCAL nResult
? "Starting..."
nResult := Calculate(10, 20)
? "Result:", nResult
? "Name:", cName
? "Age:", nAge
? "Done."
RETURN NIL
FUNCTION Calculate(a, b)
LOCAL nSum
nSum := a + b
nSum := nSum * 2
RETURN nSum

37
examples/test_frb.prg Normal file
View File

@@ -0,0 +1,37 @@
// FRB runtime loading test
// Usage: ./test_frb (after: five frb examples/frb_module.prg -o mylib.frb)
FUNCTION Main()
LOCAL pMod
? "=== FRB Runtime Module Test ==="
? ""
// Load FRB module at runtime
? "Loading mylib.frb..."
pMod := FrbLoad("mylib.frb")
IF pMod = NIL
? "ERROR: Cannot load mylib.frb"
RETURN NIL
ENDIF
? "Loaded!"
? ""
// Call functions from loaded module
? "Calling Hello('Five'):"
? " ", FrbDo(pMod, "HELLO", "Five")
? "Calling Add(100, 200):"
? " ", FrbDo(pMod, "ADD", 100, 200)
? "Calling Factorial(10):"
? " ", FrbDo(pMod, "FACTORIAL", 10)
? ""
// Unload
FrbUnload(pMod)
? "Module unloaded."
? "=== Done ==="
RETURN NIL

42
examples/test_frb_mem.prg Normal file
View File

@@ -0,0 +1,42 @@
// FRB in-memory compilation test
// Compiles PRG source at runtime and executes it
FUNCTION Main()
LOCAL pMod, cSource
? "=== FRB In-Memory Compilation Test ==="
? ""
// 1. Compile PRG source string at runtime
cSource := 'FUNCTION Double(n)' + Chr(10) + ;
' RETURN n * 2' + Chr(10) + ;
'FUNCTION Greet(cName)' + Chr(10) + ;
' RETURN "Hi " + cName + "!"' + Chr(10)
? "Compiling PRG source at runtime..."
pMod := FrbCompile(cSource)
IF pMod = NIL
? "ERROR: Compile failed"
RETURN NIL
ENDIF
? "Compiled!"
? ""
// 2. Call dynamically compiled functions
? "Double(21):", FrbDo(pMod, "DOUBLE", 21)
? "Greet('Charles'):", FrbDo(pMod, "GREET", "Charles")
? ""
FrbUnload(pMod)
// 3. One-shot: compile + run + unload
? "FrbExec one-shot:"
cSource := 'FUNCTION Main()' + Chr(10) + ;
' ? " Hello from dynamic PRG!"' + Chr(10) + ;
' RETURN 42' + Chr(10)
? "Result:", FrbExec(cSource)
? ""
? "=== Done ==="
RETURN NIL

View File

@@ -0,0 +1,22 @@
// Test pcode FRB — loads pcode module (no Go compiler needed)
FUNCTION Main()
LOCAL pMod
? "=== FRB Pcode Mode Test ==="
? ""
pMod := FrbLoad("mylib_pc.frb")
IF pMod = NIL
? "ERROR: Cannot load mylib_pc.frb"
RETURN NIL
ENDIF
? "Hello:", FrbDo(pMod, "HELLO", "World")
? "Add:", FrbDo(pMod, "ADD", 100, 200)
? "Factorial:", FrbDo(pMod, "FACTORIAL", 10)
FrbUnload(pMod)
? ""
? "=== Done (pcode mode, 175 bytes, no Go needed!) ==="
RETURN NIL

View File

@@ -0,0 +1,36 @@
// Test FRB symbol scoping — load/unload isolation
FUNCTION Main()
LOCAL pMod
? "=== FRB Symbol Scope Test ==="
? ""
// Host program has its own Add function
? "Host Add(1,2):", Add(1, 2)
? ""
// Load FRB module that also has Add
? "Loading pcode module..."
pMod := FrbLoad("mylib_pc.frb")
// FrbDo uses MODULE scope — calls module's Add (string concat)
? "Module Hello:", FrbDo(pMod, "HELLO", "Test")
? "Module Add:", FrbDo(pMod, "ADD", 100, 200)
? ""
// Host Add still works (not overwritten by module)
? "Host Add(1,2) after load:", Add(1, 2)
? ""
// Unload — module symbols removed
FrbUnload(pMod)
? "After unload:"
? "Host Add(1,2):", Add(1, 2)
? ""
? "=== Done ==="
RETURN NIL
// Host's Add function — returns sum * 10 (different from module's Add)
FUNCTION Add(a, b)
RETURN (a + b) * 10

46
examples/test_get.prg Normal file
View File

@@ -0,0 +1,46 @@
// GET System Test — interactive form entry
// Run: ./gettest
// Navigation: Up/Down/Tab/Enter = move fields, ESC = cancel
// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
FUNCTION Main()
LOCAL cName, cCity, cPhone, nAge, nSalary, lMarried
LOCAL GetList := {}
cName := Space(20)
cCity := Space(15)
cPhone := Space(13)
nAge := 0
nSalary := 0
lMarried := .F.
CLS
SetCursor(1)
@ 1, 20 SAY "=== Customer Entry Form ==="
@ 2, 20 SAY "Up/Down/Tab=Move Enter=Next ESC=Quit"
@ 4, 5 SAY "Name...:" GET cName
@ 6, 5 SAY "City...:" GET cCity
@ 8, 5 SAY "Phone..:" GET cPhone
@ 10, 5 SAY "Age....:" GET nAge PICTURE "999"
@ 12, 5 SAY "Salary.:" GET nSalary PICTURE "999999.99"
@ 14, 5 SAY "Married:" GET lMarried
READ
// Show results
CLS
? "=== Entered Data ==="
? ""
? " Name...:", AllTrim(cName)
? " City...:", AllTrim(cCity)
? " Phone..:", AllTrim(cPhone)
? " Age....:", nAge
? " Salary.:", nSalary
? " Married:", lMarried
? ""
? "Press any key to exit..."
Inkey(0)
RETURN NIL

View File

@@ -0,0 +1,45 @@
// Minimal GET typing test
FUNCTION Main()
LOCAL cName
LOCAL GetList := {}
cName := Space(10)
CLS
@ 2, 5 SAY "Type and press ESC:" GET cName
? ""
? "GetList len:", Len(GetList)
// Manual read loop for debugging
LOCAL oGet, nKey
oGet := GetList[1]
? "Buffer:[" + oGet:cBuffer + "]"
? "DispLen:", oGet:nDispLen
? "Type:", oGet:cType
? "Press key to start edit..."
Inkey(0)
oGet:setFocus()
DO WHILE .T.
SetPos(oGet:nRow, oGet:nCol + oGet:nPos - 1)
nKey := Inkey(0)
? "Key:", nKey
IF nKey = 27
EXIT
ENDIF
IF nKey >= 32 .AND. nKey <= 126
oGet:overStrike(Chr(nKey))
oGet:display()
? "Buf:[" + oGet:cBuffer + "] Pos:", oGet:nPos
ENDIF
ENDDO
oGet:killFocus()
? ""
? "Result:[" + cName + "]"
Inkey(0)
RETURN NIL

View File

@@ -0,0 +1,64 @@
// Advanced INDEX ON test — FOR condition + function expressions
FUNCTION Main()
LOCAL i, nCount
? "=== Index Advanced Test ==="
dbCreate("idxadv", {{"ID","N",6,0},{"FIRST","C",10,0},{"LAST","C",10,0},{"CITY","C",10,0}})
USE "idxadv"
FOR i := 1 TO 30
APPEND BLANK
FieldPut(1, i)
FieldPut(2, {"John","Jane","Bob","Alice","Tom","Mary"}[Int(Mod(i-1,6))+1])
FieldPut(3, {"Kim","Lee","Park","Choi","Yoon"}[Int(Mod(i-1,5))+1])
FieldPut(4, {"Seoul","Tokyo","NYC"}[Int(Mod(i-1,3))+1])
NEXT
? "Records:", RecCount()
// 1. INDEX ON with UPPER()
? ""
? "--- 1. INDEX ON UPPER(LAST) ---"
INDEX ON UPPER(LAST) TO idxadv_upper
GO TOP
? " First:", AllTrim(FieldGet(3))
GO BOTTOM
? " Last:", AllTrim(FieldGet(3))
SEEK "KIM"
? " SEEK KIM: Found=", Found()
// 2. INDEX ON with concatenation
? ""
? "--- 2. INDEX ON LAST+FIRST ---"
INDEX ON LAST+FIRST TO idxadv_combo
GO TOP
? " First:", AllTrim(FieldGet(3)), AllTrim(FieldGet(2))
SEEK "Choi"
? " SEEK Choi: Found=", Found(), "Name:", AllTrim(FieldGet(3)), AllTrim(FieldGet(2))
// 3. INDEX ON with FOR condition
? ""
? "--- 3. INDEX ON LAST FOR CITY = Seoul ---"
INDEX ON LAST TO idxadv_seoul FOR CITY = "Seoul"
GO TOP
nCount := 0
DO WHILE !Eof()
nCount++
SKIP
ENDDO
? " Records in Seoul index:", nCount, "(expected 10 of 30)"
GO TOP
? " First Seoul:", AllTrim(FieldGet(3)), "City:", AllTrim(FieldGet(4))
// 4. INDEX ON UPPER(LAST+FIRST) — nested function + concat
? ""
? "--- 4. INDEX ON UPPER(LAST+FIRST) ---"
INDEX ON UPPER(LAST+FIRST) TO idxadv_full
GO TOP
? " First:", AllTrim(FieldGet(3)), AllTrim(FieldGet(2))
SEEK "KIMJOHN"
? " SEEK KIMJOHN: Found=", Found()
USE
? ""
? "=== Done ==="
RETURN NIL

16
examples/test_keycode.prg Normal file
View File

@@ -0,0 +1,16 @@
// Key code test — shows what Inkey returns for each key press
FUNCTION Main()
LOCAL nKey
CLS
? "Press keys to see codes. ESC to quit."
? ""
DO WHILE .T.
nKey := Inkey(0)
?? " [" + Str(nKey) + "]"
IF nKey = 27
EXIT
ENDIF
ENDDO
? ""
? "Done."
RETURN NIL

30
examples/test_menu.prg Normal file
View File

@@ -0,0 +1,30 @@
// Test program for MENU TO / AChoice functions
// Calls __AtPrompt and __MenuTo directly (pp #command will add @ PROMPT syntax later)
FUNCTION Main()
LOCAL nChoice
CLS
SetCursor(0)
// Simple menu using __AtPrompt / __MenuTo
__AtPrompt(5, 10, "[ 1. Open File ]", "Open an existing file")
__AtPrompt(7, 10, "[ 2. Save File ]", "Save current file")
__AtPrompt(9, 10, "[ 3. Print ]", "Print document")
__AtPrompt(11, 10, "[ 4. Exit ]", "Exit the program")
SetPos(3, 10)
DevOut("Select an option:")
nChoice := __MenuTo(1)
CLS
SetCursor(1)
IF nChoice = 0
? "Cancelled (ESC)"
ELSE
? "You selected option:", nChoice
ENDIF
RETURN NIL

130
examples/test_multi_rdd.prg Normal file
View File

@@ -0,0 +1,130 @@
// Multi-RDD Test — DBFNTX and DBFCDX work areas simultaneously
// Tests: multiple USE VIA, SELECT, alias access, cross-area operations
// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
FUNCTION Main()
LOCAL i
? "================================================"
? " Multi-RDD Test (NTX + CDX simultaneous)"
? "================================================"
? ""
// === Create two databases ===
? "1. Creating databases..."
dbCreate("customers", {{"ID","N",6,0},{"NAME","C",20,0},{"CITY","C",15,0}})
dbCreate("orders", {{"ORDID","N",6,0},{"CUSTID","N",6,0},{"AMOUNT","N",10,2},{"PRODUCT","C",20,0}})
? " customers.dbf created"
? " orders.dbf created"
? ""
// === Open both with different drivers ===
? "2. Opening with different RDD drivers..."
SELECT 1
USE "customers" ALIAS CUST
? " Area 1: customers (CUST)"
SELECT 2
USE "orders" ALIAS ORD
? " Area 2: orders (ORD)"
? ""
// === Populate customers ===
? "3. Populating customers..."
SELECT 1
FOR i := 1 TO 20
APPEND BLANK
FieldPut(1, i)
FieldPut(2, {"Kim","Lee","Park","Choi","Jung","Kang","Cho","Yoon","Jang","Lim"}[Int(Mod(i-1,10))+1])
FieldPut(3, {"Seoul","Tokyo","NYC","London","Paris"}[Int(Mod(i-1,5))+1])
NEXT
? " Customers:", RecCount()
// === Populate orders ===
? "4. Populating orders..."
SELECT 2
FOR i := 1 TO 50
APPEND BLANK
FieldPut(1, i)
FieldPut(2, Int(Mod(i-1, 20)) + 1)
FieldPut(3, 100 + i * 10)
FieldPut(4, {"Laptop","Phone","Tablet","Monitor","Mouse"}[Int(Mod(i-1,5))+1])
NEXT
? " Orders:", RecCount()
? ""
// === Create indexes ===
? "5. Creating indexes..."
SELECT 1
INDEX ON NAME TO cust_name
? " customers INDEX ON NAME created"
SELECT 2
INDEX ON CUSTID TO ord_cust
? " orders INDEX ON CUSTID created"
? ""
// === Switch between areas ===
? "6. Cross-area navigation..."
SELECT 1
GO TOP
? " CUST first (by name):", AllTrim(FieldGet(2)), "City:", AllTrim(FieldGet(3))
SELECT 2
GO TOP
? " ORD first (by custid):", FieldGet(1), "CustID:", FieldGet(2), "Product:", AllTrim(FieldGet(4))
? ""
// === SEEK in each area ===
? "7. SEEK tests..."
SELECT 1
SEEK "Park"
? " CUST SEEK Park: Found=", Found(), "ID=", FieldGet(1), "Name=", AllTrim(FieldGet(2))
SELECT 2
SEEK 5
? " ORD SEEK CustID=5: Found=", Found(), "OrdID=", FieldGet(1), "Amount=", FieldGet(3)
? ""
// === Navigate with index in area 1, natural in area 2 ===
? "8. Simultaneous navigation..."
SELECT 1
GO TOP
? " CUST Top 5 by name:"
FOR i := 1 TO 5
IF !Eof()
? " ", FieldGet(1), AllTrim(FieldGet(2)), AllTrim(FieldGet(3))
SKIP
ENDIF
NEXT
SELECT 2
GO TOP
? " ORD first 5 by custid:"
FOR i := 1 TO 5
IF !Eof()
? " ", FieldGet(1), FieldGet(2), FieldGet(3), AllTrim(FieldGet(4))
SKIP
ENDIF
NEXT
? ""
// === Close both ===
? "9. Closing..."
SELECT 1
USE
SELECT 2
USE
? " All closed."
? ""
? "================================================"
? " Multi-RDD Test PASSED!"
? "================================================"
RETURN NIL

56
examples/test_rdd.prg Normal file
View File

@@ -0,0 +1,56 @@
// RDD test — simple CREATE + USE + APPEND + INDEX ON + SEEK
FUNCTION Main()
LOCAL i, aStruct
? "=== RDD Test ==="
// 1. Create
? "1. dbCreate..."
aStruct := {{"ID","N",6,0}, {"NAME","C",20,0}, {"CITY","C",15,0}}
dbCreate("rddtest", aStruct)
? " Created."
// 2. Open and append
? "2. USE + APPEND..."
USE "rddtest"
FOR i := 1 TO 20
APPEND BLANK
FieldPut(1, i)
FieldPut(2, "Name_" + Str(i))
FieldPut(3, "City_" + Str(i % 5))
NEXT
? " RecCount:", RecCount()
// 3. Index
? "3. INDEX ON NAME..."
INDEX ON NAME TO rddtest_idx
? " Index created."
// 4. Navigate
? "4. First 5 in index order:"
GO TOP
FOR i := 1 TO 5
IF !Eof()
? " ", FieldGet(1), FieldGet(2)
SKIP
ENDIF
NEXT
// 5. Seek — partial key match (Harbour compatible)
? "5. SEEK 'Name_1' (partial)..."
SEEK "Name_1"
? " Found:", Found(), "RecNo:", RecNo()
IF Found()
? " Name:", FieldGet(2)
ENDIF
? "6. SEEK 'Name_5' (partial)..."
SEEK "Name_5"
? " Found:", Found(), "RecNo:", RecNo()
IF Found()
? " Name:", FieldGet(2)
ENDIF
USE
? "=== Done ==="
RETURN NIL

View File

@@ -0,0 +1,87 @@
// RDD Full Test Suite
FUNCTION Main()
LOCAL i, nCount
? "=== Five RDD Full Test ==="
? ""
// Phase 1: Create + Append
? "Phase 1: Create/Append..."
dbCreate("rddtest", {{"ID","N",6,0},{"NAME","C",20,0},{"CITY","C",15,0}})
USE "rddtest"
FOR i := 1 TO 100
APPEND BLANK
FieldPut(1, i)
FieldPut(2, "Name_" + PadL(AllTrim(Str(i)), 3, "0"))
FieldPut(3, "City_" + AllTrim(Str(Mod(i, 5))))
NEXT
? " RecCount:", RecCount()
GO TOP
? " First ID:", FieldGet(1), "Name:", AllTrim(FieldGet(2))
GO BOTTOM
? " Last ID:", FieldGet(1), "Name:", AllTrim(FieldGet(2))
// Phase 2: Navigation
? "Phase 2: Navigation..."
GO TOP
nCount := 0
DO WHILE !Eof()
nCount++
SKIP
ENDDO
? " Forward count:", nCount
GO BOTTOM
SKIP -1
? " Skip -1 from bottom: ID =", FieldGet(1)
// Phase 3: Delete/Recall/Pack
? "Phase 3: Delete/Pack..."
FOR i := 10 TO 20
GO i
DELETE
NEXT
GO 15
? " Rec 15 deleted:", Deleted()
RECALL
? " Rec 15 after recall:", Deleted()
PACK
? " After Pack:", RecCount(), "records"
// Phase 4: Zap
? "Phase 4: Zap..."
ZAP
? " After Zap:", RecCount(), "records"
// Phase 5: Index
? "Phase 5: INDEX ON..."
FOR i := 1 TO 50
APPEND BLANK
FieldPut(1, i)
FieldPut(2, {"Kim","Lee","Park","Choi","Jung","Kang","Cho","Yoon","Jang","Lim"}[Int(Mod(i-1,10))+1])
NEXT
INDEX ON NAME TO rddtest_name
GO TOP
? " First (indexed):", AllTrim(FieldGet(2))
GO BOTTOM
? " Last (indexed):", AllTrim(FieldGet(2))
GO TOP
nCount := 0
DO WHILE !Eof()
nCount++
SKIP
ENDDO
? " Traversal:", nCount
// Phase 6: SEEK
? "Phase 6: SEEK..."
SEEK "Park"
? " SEEK Park: Found=", Found(), "RecNo=", RecNo()
SEEK "Kim"
? " SEEK Kim: Found=", Found(), "RecNo=", RecNo()
SEEK "ZZZZZ"
? " SEEK ZZZZZ: Found=", Found()
USE
? ""
? "=== DONE ==="
RETURN NIL

12
examples/test_say.prg Normal file
View File

@@ -0,0 +1,12 @@
// Simple @ SAY test (no GET)
FUNCTION Main()
CLS
@ 2, 5 SAY "Hello from @ SAY!"
@ 4, 5 SAY "Number:"
@ 4, 13 SAY 42
@ 6, 5 SAY "Pi:"
@ 6, 9 SAY 3.14159
@ 8, 5 SAY "Done. Press any key..."
Inkey(0)
CLS
RETURN NIL