# Five Language Syntax Reference Five = 100% Harbour compatible + Go extended syntax. Existing PRG code runs without modification, and Go's powerful features are available in PRG syntax. ## Harbour Compatible Syntax (98% parsing) Full support for all Harbour/Clipper/xBase syntax: ```prg FUNCTION, PROCEDURE, RETURN, LOCAL, STATIC, PRIVATE, PUBLIC IF/ELSEIF/ELSE/ENDIF, DO CASE/CASE/OTHERWISE/ENDCASE FOR/NEXT, FOR EACH/NEXT, DO WHILE/ENDDO BEGIN SEQUENCE/RECOVER/END, SWITCH/CASE/ENDSWITCH CLASS/DATA/METHOD/ACCESS/ASSIGN/ENDCLASS USE, SELECT, SEEK, SKIP, GO, APPEND, REPLACE, DELETE, PACK @ SAY/GET/READ, MENU TO, SET, INDEX ON ``` ## Five Go Extensions ### 1. IMPORT — Direct Go Package Access ```prg IMPORT "strings" // Go standard library IMPORT "database/sql" // SQL database IMPORT _ "modernc.org/sqlite" // blank import (driver registration) IMPORT myhttp "net/http" // aliased import ``` After IMPORT, use directly from PRG: ```prg IMPORT "strings" PROCEDURE Main() LOCAL cResult cResult := strings.ToUpper("hello five") // Direct Go function call ? strings.Contains(cResult, "FIVE") // .T. ? strings.Split("a,b,c", ",") // {"a","b","c"} RETURN ``` **No #pragma BEGINDUMP needed. IMPORT gives access to Go's entire ecosystem.** ### 2. Multi-Return — Multiple Return Values ```prg // Return multiple values from a function FUNCTION GetUserInfo() RETURN "Charles", 30, "Seoul" // Receive multiple values cName, nAge, cCity := GetUserInfo() // Discard unwanted values with blank identifier _, nAge, _ := GetUserInfo() ``` Natural support for Go's `(val, error)` pattern: ```prg IMPORT "database/sql" db, err := sql.Open("sqlite", ":memory:") IF err != NIL ? "Error:", err ENDIF ``` ### 3. DEFER — Automatic Cleanup Executes when the function returns. Guaranteed even on errors. ```prg PROCEDURE ProcessFile(cPath) LOCAL db db := sql.Open("sqlite", cPath) DEFER db:Close() // Auto-Close when function ends db:Exec("INSERT ...") // Even if error occurs here db:Exec("UPDATE ...") // db:Close() will always execute RETURN // ← DEFER executes here ``` More concise than Harbour's `BEGIN SEQUENCE/RECOVER`: ``` Before (Harbour): After (Five): ─────────────────────────────── ───────────────────── BEGIN SEQUENCE db := SqlOpen(...) db := SqlOpen(...) DEFER db:Close() db:Exec(...) db:Exec(...) RECOVER RETURN db:Close() END SEQUENCE db:Close() ``` ### 4. Slice — Sub-array / Sub-string ```prg LOCAL aData := {"a", "b", "c", "d", "e"} aSub := aData[2:4] // {"b", "c", "d"} aSub := aData[3:] // {"c", "d", "e"} (from 3 to end) aSub := aData[:2] // {"a", "b"} (from start to 2) ``` Replaces verbose Harbour loops: ``` Before: After: ─────────────────────────────── ───────────────────── LOCAL aSub := {} aSub := aData[3:7] FOR i := 3 TO 7 AAdd(aSub, aData[i]) NEXT ``` ### 5. Parallel Assignment — Simultaneous Assign ```prg // Swap values (no temp variable needed!) a, b := b, a // Simultaneous initialization x, y, z := 1, 2, 3 ``` ### 6. Nil-Safe Operator — `?:` ```prg // Before: repeated NIL checks IF oCustomer != NIL IF oCustomer:Address != NIL ? oCustomer:Address:City ENDIF ENDIF // Five: one line ? oCustomer?:Address?:City // Returns NIL if any part is NIL ``` ### 7. String Interpolation — `f"..."` ```prg LOCAL cName := "Charles", nAge := 30 // Before ? "Name: " + cName + " Age: " + Str(nAge) // Five ? f"Name: {cName}, Age: {nAge}" // With format specifiers ? f"Price: {nPrice:.2f}, Count: {nCount:05d}" ``` ### 8. CONST Block — Constants / Enums ```prg CONST STATUS_ACTIVE := 1 STATUS_CLOSED := 2 STATUS_PENDING := 3 END CONST ``` ### 9. SWITCH (Harbour compatible + extended) ```prg // Standard Harbour syntax works as-is SWITCH nStatus CASE 1 ? "Active" CASE 2 ? "Closed" OTHERWISE ? "Unknown" ENDSWITCH ``` ## Five Concurrency Syntax ### 10. Channel Operators — `<-` ```prg ch := Channel() ch <- "hello" // Send to channel msg := <- ch // Receive from channel ``` Harbour functions vs Five operators: ``` Harbour functions: Five operators: ─────────────────────────────── ───────────────────── ChSend(ch, "hello") ch <- "hello" msg := ChReceive(ch) msg := <- ch ChSend(chOut, nResult) chOut <- nResult ``` ### 11. SPAWN / LAUNCH / GOROUTINE — Inline Goroutine Three keywords, same behavior — choose your preference: ```prg SPAWN {|| DoHeavyWork() } LAUNCH {|| ProcessData() } GOROUTINE {|| SendNotification() } ``` ### 12. WATCH — Channel Multiplexing (Go select) Monitor multiple channels simultaneously, process the first one ready: ```prg WATCH CASE msg := <- chMessages // Message arrived ? "Message:", msg CASE result := <- chResults // Result arrived ? "Result:", result CASE <- chTimeout // Timeout ? "Timeout!" OTHERWISE // No channel ready ? "No channel ready" END WATCH ``` **Real-world pattern: Select fastest server response** ```prg SPAWN {|| DelayAndSend(0.1, chFast, "Fast Server") } SPAWN {|| DelayAndSend(2.0, chSlow, "Slow Server") } SPAWN {|| DelayAndSend(3.0, chTimeout, "TIMEOUT") } WATCH CASE cResult := <- chFast ? "Winner:", cResult // ← Selected (fastest at 100ms) CASE cResult := <- chSlow ? "Winner:", cResult CASE <- chTimeout ? "Timeout!" END WATCH ``` ### 13. PARALLEL FOR — Parallel Loop ```prg // Process 100K items across all CPU cores PARALLEL FOR i := 1 TO 100000 aResult[i] := ProcessItem(aData[i]) NEXT // Automatically waits for all goroutines to complete ``` ### 14. ASYNC / AWAIT — Asynchronous Execution ```prg // Start heavy work in background future := ASYNC HeavyQuery("SELECT * FROM big_table") // Do other work (non-blocking) ? "Loading..." PrepareUI() // Wait for result aRows := AWAIT future ? "Got", Len(aRows), "rows" ``` ### 15. WITH TIMEOUT — Timeout Context ```prg // Auto-cancel if not completed within 3 seconds WITH TIMEOUT 3 result := SlowNetworkCall() END IF result == NIL ? "Timeout!" ENDIF ``` ## Direct Go Object Manipulation ### `pkg.Func()` — Package Function Calls ```prg IMPORT "strings" IMPORT "math" IMPORT "fmt" ? strings.ToUpper("hello") // "HELLO" ? math.Sqrt(144) // 12 ? fmt.Sprintf("%.2f", 3.14159) // "3.14" ``` ### `obj:Method()` — Go Object Method Calls ```prg IMPORT "database/sql" db := sql.Open("sqlite", ":memory:") db:Exec("CREATE TABLE test (id INTEGER)") rows := db:Query("SELECT * FROM test") DO WHILE rows:Next() ? rows:Column(1) END rows:Close() db:Close() ``` ### Multiple Go Objects Simultaneously ```prg dbSource := sql.Open("sqlite", "source.db") dbTarget := sql.Open("sqlite", "target.db") aRows := SqlScan(dbSource, "SELECT * FROM products") FOR i := 1 TO Len(aRows) dbTarget:Exec("INSERT INTO inventory VALUES (...)") NEXT dbSource:Close() dbTarget:Close() ``` ## Five vs Competitors ### xBase Family Comparison | Feature | Harbour | xHarbour | FiveWin | **Five** | |---------|---------|----------|---------|----------| | DBF/NTX/CDX | Yes | Yes | Yes | **Yes** | | SQL Database | No | Limited | ODBC | **All Go DBs** | | HTTP Server | No | No | No | **net/http** | | WebSocket | No | No | No | **Yes** | | Goroutine | No | No | No | **Native** | | Channel `<-` | No | No | No | **Yes** | | JSON | Limited | Limited | Limited | **Go encoding/json** | | Cross-platform | Partial | Partial | Windows | **Linux/Mac/Windows** | | Package ecosystem | C libs | C libs | C libs | **All Go packages** | ### Transpiler Comparison | Feature | TypeScript→JS | Kotlin→JVM | **Five (PRG→Go)** | |---------|---------------|------------|---------------------| | Type system | Static→Dynamic | Static→Static | Dynamic→Static | | Concurrency | async/await | coroutine | **goroutine+channel** | | External packages | npm | Maven | **Go modules** | | Build output | JS code | bytecode | **Native binary** | | Interop | Direct JS | Direct Java | **Direct Go (IMPORT)** | | Performance | V8 runtime | JVM runtime | **Native speed** | ### What Makes Five Unique 1. **IMPORT gives access to all of Go** — No #pragma BEGINDUMP needed 2. **Native binary output** — Single executable, no JVM or V8 required 3. **goroutine + channel + WATCH** — Full Go concurrency in PRG syntax 4. **100% xBase compatible** — Existing DBF/NTX/CDX code runs as-is 5. **FastPath optimization** — Go function calls within 2x of native performance 6. **DEFER** — Safe resource management, cleaner than BEGIN SEQUENCE 7. **Multi-Return** — `a, b := Func()`, natural Go (val, error) pattern 8. **f-string** — String interpolation, `f"Hello {name}"` 9. **PARALLEL FOR** — Automatic parallel processing of large datasets 10. **Nil-safe `?:`** — Safe chaining, no runtime errors from NIL ## Performance ``` ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Call Type Direct Go Reflect FastPath ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ strings.ToUpper 34ns 242ns 66ns strings.Contains 3ns 218ns 19ns math.Sqrt 0.1ns 173ns 16ns obj:Method() — 412ns 235ns ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Throughput: 15M calls/sec (FastPath), 4.3M calls/sec (Method) Stress tested: 40K calls, 1MB strings, 10K arrays, 20K concurrent goroutines, 5K random fuzz ``` ## Math: Harbour RTL + Go math Together Five gives you two math systems in one file: ```prg IMPORT "math" PROCEDURE Main() LOCAL nVal // Harbour RTL — simple, no IMPORT needed ? Abs(-42.5) // 42.5 ? Sqrt(144) // 12 ? Round(3.14159, 2) // 3.14 ? Max(10, 20) // 20 // Go math — complete, 60+ functions ? math.Sin(math.Pi / 6) // 0.5 ? math.Pow(2, 10) // 1024 ? math.Hypot(3, 4) // 5 ? math.Log2(1024) // 10 // Combined — use both freely nVal := (1 / Sqrt(2 * math.Pi)) * Exp(-0.5 * math.Pow(0, 2)) ? "Normal PDF at 0:", Round(nVal, 6) RETURN ``` | | Harbour RTL | Go math | |---|---|---| | Functions | 9 (Abs, Sqrt, Round, Int, Max, Min, Log, Exp, Mod) | 60+ | | IMPORT needed | No | `IMPORT "math"` | | Best for | Business calculations | Scientific/financial | | Precision | Standard | IEEE 754 | | Trig/Hyperbolic | No | Sin, Cos, Tan, Sinh, Cosh, ... | | Constants | No | Pi, E, Phi, Ln2, Sqrt2 | | Special values | No | NaN, Inf, MaxFloat64 | ## Value Type Methods (Five Extension) Five provides 52 built-in methods on basic types. All support chaining: ### String Methods (20) ```prg LOCAL cStr := " Hello World " ? cStr:Trim() // "Hello World" ? cStr:Upper() // " HELLO WORLD " ? cStr:Lower() // " hello world " ? cStr:Left(7) // " Hello" ? cStr:Right(7) // "orld " ? cStr:SubStr(3, 5) // "Hello" ? cStr:At("World") // 9 ? cStr:Len() // 15 ? cStr:Replicate(2) // " Hello World Hello World " ? cStr:Reverse() // " dlroW olleH " ? cStr:IsAlpha() // .F. (starts with space) ? cStr:IsDigit() // .F. ? cStr:IsEmpty() // .F. ? cStr:Trim():Upper():Left(5) // "HELLO" — chaining ``` ### Array Methods (14) ```prg LOCAL aList := {3, 1, 4, 1, 5} ? aList:Len() // 5 ? aList:Sort() // {1, 1, 3, 4, 5} ? aList:Find(4) // 3 (1-based) ? aList:Push(9) // {1,1,3,4,5,9} ? aList:Pop() // 9 ? aList:First() // 1 ? aList:Last() // 5 ? aList:Join(",") // "1,1,3,4,5" ? aList:Reverse() // {5,4,3,1,1} ? aList:Unique() // {5,4,3,1} ? aList:Slice(2, 4) // {4,3} // Map/Filter/Each with code blocks LOCAL aDoubled := {1,2,3}:Map({|x| x * 2}) // {2,4,6} LOCAL aEven := {1,2,3,4}:Filter({|x| x % 2 == 0}) // {2,4} {1,2,3}:Each({|x| QOut(x)}) // prints each element ``` ### Numeric Methods (6) ```prg LOCAL nVal := 3.14159 ? nVal:Round(2) // 3.14 ? nVal:Abs() // 3.14159 ? nVal:Int() // 3 ? nVal:Str(10, 4) // " 3.1416" ? nVal:IsZero() // .F. ? (-5):Abs() // 5 ``` ### Hash Methods (7) ```prg LOCAL hData := {"name" => "Charles", "age" => 30} ? hData:Keys() // {"name","age"} ? hData:Values() // {"Charles",30} ? hData:Len() // 2 ? hData:HasKey("name") // .T. ? hData:Remove("age") // {"name" => "Charles"} ? hData:Merge({"city" => "Seoul"}) ``` ### Any Type Methods (5) ```prg LOCAL xVal := "hello" ? xVal:Type() // "C" ? xVal:Clone() // deep copy ? xVal:IsNil() // .F. ? xVal:ToString() // "hello" ? xVal:ValType() // "C" ``` ## MEMVAR — PUBLIC/PRIVATE Variables Harbour-compatible memory variable system. PUBLIC is global, PRIVATE is function-scoped with shadowing: ```prg // PUBLIC — accessible throughout the entire program PUBLIC gAppName gAppName := "Five Application" PROCEDURE Main() LOCAL cLocal := "local only" // PRIVATE — accessible in current function + callees, restored on return PRIVATE nTemp := 100 SubFunc() ? nTemp // 100 (SubFunc's PRIVATE restored) ? gAppName // "Five Application" (PUBLIC) RETURN PROCEDURE SubFunc() PRIVATE nTemp := 999 // shadows caller's nTemp ? nTemp // 999 RETURN // nTemp restored to 100 ``` ### MEMVAR Scope Rules | Type | Lifetime | Visibility | Shadowing | |------|----------|------------|-----------| | PUBLIC | Until program exit | Everywhere | Can be shadowed by PRIVATE | | PRIVATE | Until declaring function returns | Declaring function + callees | Nested PRIVATE supported | | LOCAL | Until declaring function returns | Declaring function only | Independent of MEMVAR | | STATIC | Until program exit | Declaring function only | Independent of MEMVAR | ### MEMVAR Access via Macro ```prg PUBLIC cName := "Charles" LOCAL cVar := "cName" ? &cVar // "Charles" — macro searches MEMVAR ``` ## SET Command System Harbour-compatible SET settings. 47+ settings supported: ```prg // Boolean toggles SET EXACT ON // exact string comparison SET DELETED ON // hide deleted records SET SOFTSEEK ON // nearest record on failed SEEK SET EXCLUSIVE OFF // shared mode SET CONFIRM ON // require confirmation on GET // Value settings SET DATE FORMAT "yyyy-mm-dd" // date format SET DECIMALS TO 4 // decimal places SET EPOCH TO 2000 // 2-digit year interpretation base // Programmatic access via SET() function LOCAL lOld := SET(_SET_EXACT, .T.) // set and return previous value ? SET(_SET_EXACT) // .T. ``` ### SET Constants ```prg _SET_EXACT // 1 exact string comparison _SET_FIXED // 2 fixed decimal point _SET_DECIMALS // 3 decimal places _SET_DATEFORMAT // 4 date format _SET_EPOCH // 5 epoch year _SET_DELETED // 8 deleted record filter _SET_EXCLUSIVE // 11 exclusive mode _SET_SOFTSEEK // 12 soft seek ``` ## ErrorBlock / Break — Error Handling Harbour-compatible structured error handling: ### BEGIN SEQUENCE / RECOVER ```prg LOCAL bOldError LOCAL oErr // Set error handler bOldError := ErrorBlock({|e| Break(e)}) BEGIN SEQUENCE // Code that may generate an error USE "nonexistent.dbf" RECOVER USING oErr // oErr is an error object (Hash) ? oErr["DESCRIPTION"] // error description ? oErr["OPERATION"] // failed operation ? oErr["SUBSYSTEM"] // subsystem name ? oErr["GENCODE"] // generic error code END SEQUENCE // Restore previous handler ErrorBlock(bOldError) ``` ### ErrorBlock ```prg // Get current error handler LOCAL bHandler := ErrorBlock() // Set new handler (returns previous) LOCAL bOld := ErrorBlock({|e| MyErrorHandler(e)}) FUNCTION MyErrorHandler(oErr) ? "Error:", oErr["DESCRIPTION"] ? "Operation:", oErr["OPERATION"] BREAK oErr // pass to RECOVER in BEGIN SEQUENCE RETURN NIL ``` ### ErrorNew ```prg LOCAL oErr := ErrorNew() oErr["SUBSYSTEM"] := "MYAPP" oErr["DESCRIPTION"] := "Custom error" oErr["OPERATION"] := "MyFunc" oErr["GENCODE"] := 1001 oErr["SEVERITY"] := 2 // ES_ERROR ``` ## MEMO Fields — Transparent Read/Write Five handles DBF MEMO fields transparently. FPT files are automatically created and opened: ```prg // Create table with MEMO field — FPT auto-created USE "notes" NEW APPEND BLANK REPLACE NAME WITH "Charles" REPLACE NOTES WITH "This is a long memo text..." // auto-writes to FPT ? NOTES // "This is a long memo text..." — auto-reads from FPT // Large memos work seamlessly REPLACE NOTES WITH REPLICATE("Large data. ", 1000) // ~12KB ? LEN(NOTES) // 12000 ``` ### MEMO Internal Behavior | Action | Automatic Handling | |--------|-------------------| | Create DBF (with M field) | FPT file auto-created | | Open DBF (with M field) | FPT file auto-opened | | REPLACE memo WITH text | Write to FPT, store block number in DBF | | ? memo | Read FPT by block number, return string | | Close DBF | FPT auto-closed | ## Example Files | File | Description | |------|-------------| | `examples/go_native.prg` | Direct Go package usage with IMPORT only | | `examples/go_strings.prg` | Full strings package utilization | | `examples/go_typetest.prg` | 18 type conversion tests | | `examples/go_dual_db.prg` | Two SQLite databases simultaneously | | `examples/go_channel.prg` | Channel operators + WATCH + Pipeline | | `examples/go_httpserver.prg` | REST API server | | `examples/go_concurrent.prg` | Parallel data pipeline | | `examples/go_websocket.prg` | WebSocket chat server | | `examples/go_extensions.prg` | All 9 extension syntax demo | | `examples/go_math_compare.prg` | Harbour RTL vs Go math side-by-side | | `examples/godump_demo.prg` | HB_FUNC Go API |