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

19 KiB

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:

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

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:

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

// 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:

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.

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

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

// Swap values (no temp variable needed!)
a, b := b, a

// Simultaneous initialization
x, y, z := 1, 2, 3

6. Nil-Safe Operator — ?:

// 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"..."

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

CONST
   STATUS_ACTIVE  := 1
   STATUS_CLOSED  := 2
   STATUS_PENDING := 3
END CONST

9. SWITCH (Harbour compatible + extended)

// Standard Harbour syntax works as-is
SWITCH nStatus
CASE 1
   ? "Active"
CASE 2
   ? "Closed"
OTHERWISE
   ? "Unknown"
ENDSWITCH

Five Concurrency Syntax

10. Channel Operators — <-

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:

SPAWN     {|| DoHeavyWork() }
LAUNCH    {|| ProcessData() }
GOROUTINE {|| SendNotification() }

12. WATCH — Channel Multiplexing (Go select)

Monitor multiple channels simultaneously, process the first one ready:

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

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

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

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

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

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

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

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-Returna, 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:

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)

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)

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)

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)

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)

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:

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

PUBLIC cName := "Charles"
LOCAL cVar := "cName"
? &cVar                          // "Charles" — macro searches MEMVAR

SET Command System

Harbour-compatible SET settings. 47+ settings supported:

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

_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

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

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

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:

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