Files
five/docs/five-syntax-ko.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

18 KiB
Raw Permalink Blame History

Five Language Syntax Reference

Five = Harbour 100% 호환 + Go 확장 문법. 기존 PRG 코드 수정 없이 실행되고, Go의 강력한 기능을 PRG 문법으로 사용.

Harbour 호환 문법 (98% 파싱)

기존 Harbour/Clipper/xBase 문법 전체 지원:

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 확장 문법

1. IMPORT — Go 패키지 직접 사용

IMPORT "strings"                    // Go 표준 라이브러리
IMPORT "database/sql"               // SQL 데이터베이스
IMPORT _ "modernc.org/sqlite"       // blank import (드라이버)
IMPORT myhttp "net/http"            // 별칭 import

IMPORT하면 PRG에서 바로 사용:

IMPORT "strings"

PROCEDURE Main()
   LOCAL cResult
   cResult := strings.ToUpper("hello five")     // Go 함수 직접 호출
   ? strings.Contains(cResult, "FIVE")           // .T.
   ? strings.Split("a,b,c", ",")                 // {"a","b","c"}
   RETURN

#pragma BEGINDUMP 불필요. IMPORT만으로 Go 전체 생태계 접근.

2. Multi-Return — 다중 반환값

// 함수에서 여러 값 반환
FUNCTION GetUserInfo()
   RETURN "Charles", 30, "Seoul"

// 받는 쪽
cName, nAge, cCity := GetUserInfo()

// 불필요한 값 무시
_, nAge, _ := GetUserInfo()

Go의 (val, error) 패턴 자연스럽게 지원:

IMPORT "database/sql"
db, err := sql.Open("sqlite", ":memory:")
IF err != NIL
   ? "Error:", err
ENDIF

3. DEFER — 자동 정리

함수가 끝날 때 자동 실행. 에러가 나도 보장.

PROCEDURE ProcessFile(cPath)
   LOCAL db
   db := sql.Open("sqlite", cPath)
   DEFER db:Close()                  // 함수 끝나면 자동 Close

   db:Exec("INSERT ...")             // 여기서 에러 나도
   db:Exec("UPDATE ...")             // db:Close()는 반드시 실행
   RETURN                            // ← 여기서 DEFER 실행

Harbour의 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 — 부분 배열/문자열

LOCAL aData := {"a", "b", "c", "d", "e"}

aSub := aData[2:4]          // {"b", "c", "d"}
aSub := aData[3:]           // {"c", "d", "e"}  (3번부터 끝까지)
aSub := aData[:2]           // {"a", "b"}        (처음부터 2번까지)

Harbour의 반복 루프 대체:

Before:                              After:
───────────────────────────────      ─────────────────────
LOCAL aSub := {}                     aSub := aData[3:7]
FOR i := 3 TO 7
   AAdd(aSub, aData[i])
NEXT

5. Parallel Assignment — 동시 할당

// 값 교환 (swap)
a, b := b, a                // temp 변수 불필요!

// 동시 초기화
x, y, z := 1, 2, 3

6. Nil-Safe Operator — ?:

// 기존: NIL 체크 반복
IF oCustomer != NIL
   IF oCustomer:Address != NIL
      ? oCustomer:Address:City
   ENDIF
ENDIF

// Five: 한 줄로
? oCustomer?:Address?:City           // NIL이면 NIL 반환, 에러 없음

7. String Interpolation — f"..."

LOCAL cName := "Charles", nAge := 30

// 기존
? "Name: " + cName + " Age: " + Str(nAge)

// Five
? f"Name: {cName}, Age: {nAge}"

// 포맷 지정
? f"Price: {nPrice:.2f}, Count: {nCount:05d}"

8. CONST Block — 상수/열거형

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

9. SWITCH (Harbour 호환 + 확장)

// 기존 Harbour 문법 그대로 동작
SWITCH nStatus
CASE 1
   ? "Active"
CASE 2
   ? "Closed"
OTHERWISE
   ? "Unknown"
ENDSWITCH

Five 동시성 문법

10. 채널 연산자 — <-

ch := Channel()

ch <- "hello"                // 채널로 전송 (send)
msg := <- ch                 // 채널에서 수신 (receive)

Harbour 함수 vs Five 연산자:

Harbour 함수:                        Five 연산자:
───────────────────────────────      ─────────────────────
ChSend(ch, "hello")                  ch <- "hello"
msg := ChReceive(ch)                 msg := <- ch
ChSend(chOut, nResult)               chOut <- nResult

11. SPAWN / LAUNCH / GOROUTINE — 인라인 goroutine

// 3가지 키워드, 같은 동작
SPAWN     {|| DoHeavyWork() }
LAUNCH    {|| ProcessData() }
GOROUTINE {|| SendNotification() }

12. WATCH — 채널 멀티플렉싱 (Go select)

여러 채널을 동시 감시, 먼저 준비된 채널 처리:

WATCH
CASE msg := <- chMessages            // 메시지 도착
   ? "Message:", msg
CASE result := <- chResults          // 결과 도착
   ? "Result:", result
CASE <- chTimeout                    // 타임아웃
   ? "Timeout!"
OTHERWISE                            // 아무 채널도 준비 안 됨
   ? "No channel ready"
END WATCH

실전 패턴: 가장 빠른 서버 응답 선택

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              // ← 100ms로 가장 빨라서 선택됨
CASE cResult := <- chSlow
   ? "Winner:", cResult
CASE <- chTimeout
   ? "Timeout!"
END WATCH

13. PARALLEL FOR — 병렬 루프

// 10만 건을 CPU 코어 수만큼 병렬 처리
PARALLEL FOR i := 1 TO 100000
   aResult[i] := ProcessItem(aData[i])
NEXT
// 자동으로 모든 goroutine 완료 대기

14. ASYNC / AWAIT — 비동기 실행

// 무거운 작업을 백그라운드에서 시작
future := ASYNC HeavyQuery("SELECT * FROM big_table")

// 다른 작업 수행 (비동기)
? "Loading..."
PrepareUI()

// 결과 대기
aRows := AWAIT future
? "Got", Len(aRows), "rows"

15. WITH TIMEOUT — 타임아웃 컨텍스트

// 3초 안에 완료되지 않으면 자동 취소
WITH TIMEOUT 3
   result := SlowNetworkCall()
END

IF result == NIL
   ? "Timeout!"
ENDIF

Go 객체 직접 조작

pkg.Func() — 패키지 함수 호출

IMPORT "strings"
IMPORT "math"
IMPORT "fmt"

? strings.ToUpper("hello")          // "HELLO"
? math.Sqrt(144)                     // 12
? fmt.Sprintf("%.2f", 3.14159)       // "3.14"

obj:Method() — Go 객체 메서드 호출

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()

여러 Go 객체 동시 사용

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 경쟁 언어

xBase 계열 비교

기능 Harbour xHarbour FiveWin Five
DBF/NTX/CDX
SQL Database 제한적 ODBC 모든 Go DB
HTTP Server net/http
WebSocket
Goroutine 네이티브
Channel <- 연산자
JSON 제한적 제한적 제한적 Go encoding/json
크로스플랫폼 Windows Linux/Mac/Windows
패키지 생태계 C lib C lib C lib Go 전체

다른 트랜스파일러 비교

기능 TypeScript→JS Kotlin→JVM Five (PRG→Go)
타입 시스템 정적→동적 정적→정적 동적→정적
동시성 async/await coroutine goroutine+channel
외부 패키지 npm Maven Go modules
컴파일 결과 JS 코드 bytecode 네이티브 바이너리
인터롭 JS 직접 Java 직접 Go 직접 (IMPORT)
성능 V8 런타임 JVM 런타임 네이티브 속도

Five만의 차별점

  1. IMPORT만으로 Go 생태계 전체 접근 — #pragma BEGINDUMP 불필요
  2. 네이티브 바이너리 — JVM, V8 없이 단일 실행 파일
  3. goroutine + channel + WATCH — PRG 문법으로 Go 동시성 100%
  4. xBase 100% 호환 — 기존 DBF/NTX/CDX 코드 그대로 실행
  5. FastPath 최적화 — Go 함수 호출이 native의 2x 이내 성능
  6. DEFER — 리소스 안전 관리, BEGIN SEQUENCE보다 간결
  7. Multi-Returna, b := Func(), Go의 (val, error) 패턴
  8. f-string — 문자열 보간, f"Hello {name}"
  9. PARALLEL FOR — 대량 데이터 자동 병렬 처리
  10. Nil-safe ?: — 안전한 체이닝, 런타임 에러 방지

수학: Harbour RTL + Go math 동시 사용

IMPORT "math"

PROCEDURE Main()
   // Harbour RTL — 간단, IMPORT 불필요
   ? Abs(-42.5)              // 42.5
   ? Sqrt(144)               // 12
   ? Round(3.14159, 2)       // 3.14

   // Go math — 완전, 60개+ 함수
   ? math.Sin(math.Pi / 6)  // 0.5
   ? math.Pow(2, 10)         // 1024
   ? math.Hypot(3, 4)        // 5

   // 조합 사용
   ? (1 / Sqrt(2 * math.Pi)) * Exp(-0.5 * math.Pow(0, 2))
   RETURN
Harbour RTL Go math
함수 수 9개 60개+
IMPORT <EFBFBD><EFBFBD><EFBFBD> IMPORT "math"
삼각함수 없음 Sin, Cos, Tan, ...
상수 없음 Pi, E, Phi, ...

Value 타입 메서드 (Five 확장)

Five는 기본 타입에 52개의 내장 메서드를 지원합니다. 체이닝 가능:

String 메서드 (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 메서드 (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

Numeric 메서드 (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 메서드 (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 타입 메서드 (5개)

LOCAL xVal := "hello"
? xVal:Type()                    // "C"
? xVal:Clone()                   // deep copy
? xVal:IsNil()                   // .F.
? xVal:ToString()                // "hello"
? xVal:ValType()                 // "C"

MEMVAR — PUBLIC/PRIVATE 변수

Harbour 호환 메모리 변수 시스템. PUBLIC은 전역, PRIVATE은 함수 스코프.

// PUBLIC — 프로그램 전체에서 접근
PUBLIC gAppName
gAppName := "Five Application"

PROCEDURE Main()
   LOCAL cLocal := "local only"

   // PRIVATE — 현재 함수 + 하위 함수에서 접근, 리턴 시 복원
   PRIVATE nTemp := 100
   SubFunc()
   ? nTemp                       // 100 (SubFunc의 PRIVATE이 복원됨)

   ? gAppName                    // "Five Application" (PUBLIC)
RETURN

PROCEDURE SubFunc()
   PRIVATE nTemp := 999          // shadows caller's nTemp
   ? nTemp                       // 999
RETURN                            // nTemp restored to 100

MEMVAR 스코프 규칙

종류 수명 가시성 섀도잉
PUBLIC 프로그램 종료까지 전체 PRIVATE이 숨길 수 있음
PRIVATE 선언 함수 리턴까지 선언 함수 + 하위 중첩 PRIVATE 가능
LOCAL 선언 함수 리턴까지 선언 함수만 MEMVAR와 독립
STATIC 프로그램 종료까지 선언 함수만 MEMVAR와 독립

매크로에서 MEMVAR 접근

PUBLIC cName := "Charles"
LOCAL cVar := "cName"
? &cVar                          // "Charles" — 매크로가 MEMVAR 검색

SET 명령어 시스템

Harbour 호환 SET 설정. 47+ 설정 지원:

// Boolean 토글
SET EXACT ON                     // 문자열 완전 일치 비교
SET DELETED ON                   // 삭제 레코드 숨김
SET SOFTSEEK ON                  // SEEK 실패 시 가장 가까운 레코드
SET EXCLUSIVE OFF                // 공유 모드
SET CONFIRM ON                   // GET 입력 시 확인 필요

// 값 설정
SET DATE FORMAT "yyyy-mm-dd"     // 날짜 형식
SET DECIMALS TO 4                // 소수점 자릿수
SET EPOCH TO 2000                // 2자리 년도 해석 기준

// SET() 함수로 프로그래밍 방식 접근
LOCAL lOld := SET(_SET_EXACT, .T.)    // 설정하고 이전 값 반환
? SET(_SET_EXACT)                      // .T.

SET 상수

_SET_EXACT      // 1   문자열 정확 비교
_SET_FIXED      // 2   고정 소수점
_SET_DECIMALS   // 3   소수점 자릿수
_SET_DATEFORMAT // 4   날짜 형식
_SET_EPOCH      // 5   년도 기준
_SET_DELETED    // 8   삭제 레코드 필터
_SET_EXCLUSIVE  // 11  독점 모드
_SET_SOFTSEEK   // 12  소프트 검색

ErrorBlock / Break — 에러 처리

Harbour 호환 구조적 에러 처리:

BEGIN SEQUENCE / RECOVER

LOCAL bOldError
LOCAL oErr

// 에러 핸들러 설정
bOldError := ErrorBlock({|e| Break(e)})

BEGIN SEQUENCE
   // 에러가 발생할 수 있는 코드
   USE "nonexistent.dbf"
RECOVER USING oErr
   // oErr는 에러 객체 (Hash)
   ? oErr["DESCRIPTION"]         // 에러 설명
   ? oErr["OPERATION"]           // 실패한 연산
   ? oErr["SUBSYSTEM"]           // 서브시스템 이름
   ? oErr["GENCODE"]             // 일반 에러 코드
END SEQUENCE

// 이전 핸들러 복원
ErrorBlock(bOldError)

ErrorBlock

// 현재 에러 핸들러 가져오기
LOCAL bHandler := ErrorBlock()

// 새 핸들러 설정 (이전 핸들러 반환)
LOCAL bOld := ErrorBlock({|e| MyErrorHandler(e)})

FUNCTION MyErrorHandler(oErr)
   ? "Error:", oErr["DESCRIPTION"]
   ? "Operation:", oErr["OPERATION"]
   BREAK oErr                    // BEGIN SEQUENCE의 RECOVER로 전달
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 필드 — 투명한 읽기/쓰기

Five는 DBF의 MEMO 필드를 투명하게 처리합니다. FPT 파일이 자동으로 생성/열림:

// MEMO 필드가 있는 테이블 생성 — FPT 자동 생성
USE "notes" NEW
APPEND BLANK
REPLACE NAME WITH "Charles"
REPLACE NOTES WITH "This is a long memo text..."    // FPT에 자동 저장
? NOTES                          // "This is a long memo text..." — FPT에서 자동 읽기

// 큰 메모도 문제없음
REPLACE NOTES WITH REPLICATE("Large data. ", 1000)  // ~12KB
? LEN(NOTES)                     // 12000

MEMO 내부 동작

동작 자동 처리
DBF 생성 (M 필드 포함) FPT 파일 자동 생성
DBF 열기 (M 필드 포함) FPT 파일 자동 열기
REPLACE memo WITH text FPT에 쓰기 → 블록 번호 DBF에 저장
? memo 블록 번호로 FPT 읽기 → 문자열 반환
DBF 닫기 FPT 자동 닫기

예제 파일

파일 설명
examples/go_native.prg IMPORT만으로 Go 패키지 직접 사용
examples/go_strings.prg strings 패키지 전체 활용
examples/go_typetest.prg 18가지 타입 변환 테스트
examples/go_dual_db.prg 두 SQLite DB 동시 사용
examples/go_channel.prg 채널 연산자 + WATCH + Pipeline
examples/go_httpserver.prg REST API 서버
examples/go_concurrent.prg 병렬 데이터 파이프라인
examples/go_websocket.prg WebSocket 채팅 서버
examples/go_extensions.prg 9가지 확장 문법 데모
examples/go_math_compare.prg Harbour RTL vs Go math 비교
examples/godump_demo.prg HB_FUNC Go API