Files
five/docs/five-syntax-en.md
Charles KWON OhJun 08c0ef13d4 feat: transparent MEMO read/write + documentation update
DBFArea auto-manages FPT memo files:
- Create/Open: auto-creates/opens FPT when memo fields exist
- PutValue: string on MEMO field auto-writes to FPT
- GetValue: MEMO field auto-reads from FPT, returns string
- Close: auto-closes FPT

Documentation: Value methods, MEMVAR, SET, ErrorBlock, MEMO
added to five-syntax-ko.md and five-syntax-en.md (+480 lines)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 17:32:07 +09:00

683 lines
19 KiB
Markdown

# 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 |