# Five Language Syntax Reference Five = Harbour 100% 호환 + Go 확장 문법. 기존 PRG 코드 수정 없이 실행되고, Go의 강력한 기능을 PRG 문법으로 사용. ## Harbour 호환 문법 (98% 파싱) 기존 Harbour/Clipper/xBase 문법 전체 지원: ```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 확장 문법 ### 1. IMPORT — Go 패키지 직접 사용 ```prg IMPORT "strings" // Go 표준 라이브러리 IMPORT "database/sql" // SQL 데이터베이스 IMPORT _ "modernc.org/sqlite" // blank import (드라이버) IMPORT myhttp "net/http" // 별칭 import ``` IMPORT하면 PRG에서 바로 사용: ```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 — 다중 반환값 ```prg // 함수에서 여러 값 반환 FUNCTION GetUserInfo() RETURN "Charles", 30, "Seoul" // 받는 쪽 cName, nAge, cCity := GetUserInfo() // 불필요한 값 무시 _, nAge, _ := GetUserInfo() ``` Go의 `(val, error)` 패턴 자연스럽게 지원: ```prg IMPORT "database/sql" db, err := sql.Open("sqlite", ":memory:") IF err != NIL ? "Error:", err ENDIF ``` ### 3. DEFER — 자동 정리 함수가 끝날 때 자동 실행. 에러가 나도 보장. ```prg 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 — 부분 배열/문자열 ```prg 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 — 동시 할당 ```prg // 값 교환 (swap) a, b := b, a // temp 변수 불필요! // 동시 초기화 x, y, z := 1, 2, 3 ``` ### 6. Nil-Safe Operator — `?:` ```prg // 기존: NIL 체크 반복 IF oCustomer != NIL IF oCustomer:Address != NIL ? oCustomer:Address:City ENDIF ENDIF // Five: 한 줄로 ? oCustomer?:Address?:City // NIL이면 NIL 반환, 에러 없음 ``` ### 7. String Interpolation — `f"..."` ```prg 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 — 상수/열거형 ```prg CONST STATUS_ACTIVE := 1 STATUS_CLOSED := 2 STATUS_PENDING := 3 END CONST ``` ### 9. SWITCH (Harbour 호환 + 확장) ```prg // 기존 Harbour 문법 그대로 동작 SWITCH nStatus CASE 1 ? "Active" CASE 2 ? "Closed" OTHERWISE ? "Unknown" ENDSWITCH ``` ## Five 동시성 문법 ### 10. 채널 연산자 — `<-` ```prg 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 ```prg // 3가지 키워드, 같은 동작 SPAWN {|| DoHeavyWork() } LAUNCH {|| ProcessData() } GOROUTINE {|| SendNotification() } ``` ### 12. WATCH — 채널 멀티플렉싱 (Go select) 여러 채널을 동시 감시, 먼저 준비된 채널 처리: ```prg WATCH CASE msg := <- chMessages // 메시지 도착 ? "Message:", msg CASE result := <- chResults // 결과 도착 ? "Result:", result CASE <- chTimeout // 타임아웃 ? "Timeout!" OTHERWISE // 아무 채널도 준비 안 됨 ? "No channel ready" END WATCH ``` **실전 패턴: 가장 빠른 서버 응답 선택** ```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 // ← 100ms로 가장 빨라서 선택됨 CASE cResult := <- chSlow ? "Winner:", cResult CASE <- chTimeout ? "Timeout!" END WATCH ``` ### 13. PARALLEL FOR — 병렬 루프 ```prg // 10만 건을 CPU 코어 수만큼 병렬 처리 PARALLEL FOR i := 1 TO 100000 aResult[i] := ProcessItem(aData[i]) NEXT // 자동으로 모든 goroutine 완료 대기 ``` ### 14. ASYNC / AWAIT — 비동기 실행 ```prg // 무거운 작업을 백그라운드에서 시작 future := ASYNC HeavyQuery("SELECT * FROM big_table") // 다른 작업 수행 (비동기) ? "Loading..." PrepareUI() // 결과 대기 aRows := AWAIT future ? "Got", Len(aRows), "rows" ``` ### 15. WITH TIMEOUT — 타임아웃 컨텍스트 ```prg // 3초 안에 완료되지 않으면 자동 취소 WITH TIMEOUT 3 result := SlowNetworkCall() END IF result == NIL ? "Timeout!" ENDIF ``` ## Go 객체 직접 조작 ### `pkg.Func()` — 패키지 함수 호출 ```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 객체 메서드 호출 ```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() ``` ### 여러 Go 객체 동시 사용 ```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 경쟁 언어 ### 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-Return** — `a, b := Func()`, Go의 (val, error) 패턴 8. **f-string** — 문자열 보간, `f"Hello {name}"` 9. **PARALLEL FOR** — 대량 데이터 자동 병렬 처리 10. **Nil-safe `?:`** — 안전한 체이닝, 런타임 에러 방지 ## 수학: Harbour RTL + Go math 동시 사용 ```prg 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 | 불���요 | `IMPORT "math"` | | 삼각함수 | 없음 | Sin, Cos, Tan, ... | | 상수 | 없음 | Pi, E, Phi, ... | ## Value 타입 메서드 (Five 확장) Five는 기본 타입에 52개의 내장 메서드를 지원합니다. 체이닝 가능: ### String 메서드 (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 메서드 (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 ``` ### Numeric 메서드 (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 메서드 (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 타입 메서드 (5개) ```prg 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은 함수 스코프. ```prg // 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 접근 ```prg PUBLIC cName := "Charles" LOCAL cVar := "cName" ? &cVar // "Charles" — 매크로가 MEMVAR 검색 ``` ## SET 명령어 시스템 Harbour 호환 SET 설정. 47+ 설정 지원: ```prg // 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 상수 ```prg _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 ```prg 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 ```prg // 현재 에러 핸들러 가져오기 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 ```prg 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 파일이 자동으로 생성/열림: ```prg // 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 |