- Compiler: PP → Lexer → Parser → Analyzer → Gengo pipeline - Parser: 232/236 (98%) Harbour compatibility, registry-based dispatch - RTL: 351 Harbour-compatible functions - RDD: DBF/NTX/CDX engines with Rushmore bitmap optimization - Go Interop: IMPORT + pkg.Func() + obj:Method() with FastPath (15M calls/sec) - HB_FUNC API: Full Harbour C API compatible Go bridge - Concurrency: SPAWN/LAUNCH/GOROUTINE, <-, WATCH, PARALLEL FOR, ASYNC/AWAIT - Extensions: Multi-return, DEFER, Slice, f-string, Nil-safe ?:, CONST - Macro Compiler: Runtime AST parsing and evaluation - Debugger: TUI debugger with source display, breakpoints, stepping - FRB: Native + Pcode dual mode runtime binary - Tests: 13 packages ALL PASS Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
262 lines
7.8 KiB
Markdown
262 lines
7.8 KiB
Markdown
# Five Go Interop — PRG에서 Go 패키지 직접 사용
|
|
|
|
Five의 핵심 차별점: **PRG 코드에서 Go의 전체 패키지 생태계를 직접 사용**.
|
|
|
|
## 1. IMPORT — Go 패키지 가져오기
|
|
|
|
```prg
|
|
IMPORT "strings" // Go 표준 라이브러리
|
|
IMPORT "database/sql" // 데이터베이스
|
|
IMPORT "net/http" // HTTP 서버/클라이언트
|
|
IMPORT "encoding/json" // JSON
|
|
IMPORT _ "modernc.org/sqlite" // blank import (드라이버 등록용)
|
|
IMPORT myhttp "net/http" // 별칭 import
|
|
```
|
|
|
|
PRG 파일 최상단에 선언. gengo가 Go import로 직접 변환.
|
|
|
|
## 2. 패키지 함수 호출 — `pkg.Func()`
|
|
|
|
```prg
|
|
IMPORT "strings"
|
|
IMPORT "strconv"
|
|
IMPORT "fmt"
|
|
|
|
PROCEDURE Main()
|
|
LOCAL cResult, nVal, cFormatted
|
|
|
|
cResult := strings.ToUpper("hello five!") // → "HELLO FIVE!"
|
|
cResult := strings.ReplaceAll("a-b-c", "-", "_") // → "a_b_c"
|
|
|
|
nVal := strconv.Atoi("42") // → 42
|
|
cFormatted := fmt.Sprintf("Name: %s, Age: %d", "Charles", 30)
|
|
|
|
IF strings.HasPrefix(cResult, "HELLO")
|
|
? "starts with HELLO"
|
|
ENDIF
|
|
|
|
RETURN
|
|
```
|
|
|
|
### 동작 원리
|
|
```
|
|
PRG: strings.ToUpper("hello")
|
|
↓ gengo
|
|
Go: hbrt.GoCallFunc(strings.ToUpper, _arg0)
|
|
```
|
|
|
|
- gengo가 IMPORT된 패키지 이름을 인식
|
|
- `pkg.Func(args)` → `hbrt.GoCallFunc()` reflect 호출로 변환
|
|
- 반환값은 자동으로 Harbour Value로 변환
|
|
|
|
### 자동 타입 변환
|
|
|
|
| Go 타입 | → Harbour 타입 |
|
|
|---------|---------------|
|
|
| `string` | String |
|
|
| `int`, `int64` | Numeric (Integer/Long) |
|
|
| `float64` | Numeric (Double) |
|
|
| `bool` | Logical |
|
|
| `[]string`, `[]int` 등 | Array |
|
|
| `map[string]interface{}` | Hash |
|
|
| `error` (nil) | NIL |
|
|
| `error` (non-nil) | String (에러 메시지) |
|
|
| `*sql.DB` 등 포인터 | Go Object (Value로 래핑) |
|
|
|
|
## 3. Go 객체 메서드 호출 — `obj:Method()`
|
|
|
|
Go 함수가 반환한 객체(포인터)는 Harbour의 `:` 문법으로 메서드 호출:
|
|
|
|
```prg
|
|
IMPORT "database/sql"
|
|
IMPORT _ "modernc.org/sqlite"
|
|
|
|
PROCEDURE Main()
|
|
LOCAL db, rows
|
|
|
|
db := sql.Open("sqlite", ":memory:") // *sql.DB 반환
|
|
db:Exec("CREATE TABLE test (id INTEGER)") // *sql.DB.Exec() 호출
|
|
db:Exec("INSERT INTO test VALUES (1)")
|
|
|
|
rows := db:Query("SELECT * FROM test") // *sql.Rows 반환
|
|
DO WHILE rows:Next() // *sql.Rows.Next()
|
|
? rows:Column(1) // 컬럼 값 읽기
|
|
ENDDO
|
|
rows:Close() // *sql.Rows.Close()
|
|
|
|
db:Close() // *sql.DB.Close()
|
|
RETURN
|
|
```
|
|
|
|
### 동작 원리
|
|
```
|
|
PRG: db:Exec("CREATE TABLE ...")
|
|
↓ gengo
|
|
Go: if hbrt.IsGoObject(_obj) {
|
|
hbrt.GoCall(_obj, "Exec", _args...) // reflect 호출
|
|
} else {
|
|
t.Send("Exec", 1) // Harbour 객체 호출
|
|
}
|
|
```
|
|
|
|
- 런타임에 Go 객체 vs Harbour 객체 자동 판별
|
|
- Go 객체: `reflect.MethodByName()` 으로 호출
|
|
- Harbour 객체: 기존 `Send()` 메커니즘
|
|
|
|
## 4. 여러 Go 객체 동시 사용
|
|
|
|
```prg
|
|
IMPORT "database/sql"
|
|
IMPORT _ "modernc.org/sqlite"
|
|
|
|
PROCEDURE Main()
|
|
LOCAL dbSource, dbTarget, aRows, i
|
|
|
|
// 두 데이터베이스 동시 오픈
|
|
dbSource := sql.Open("sqlite", "source.db")
|
|
dbTarget := sql.Open("sqlite", "target.db")
|
|
|
|
// Source에서 읽어서 Target에 쓰기
|
|
aRows := SqlScan(dbSource, "SELECT * FROM products")
|
|
FOR i := 1 TO Len(aRows)
|
|
dbTarget:Exec("INSERT INTO inventory VALUES (...)")
|
|
NEXT
|
|
|
|
dbSource:Close()
|
|
dbTarget:Close()
|
|
RETURN
|
|
|
|
// PRG 함수에서 Go 객체를 파라미터로 받아 사용
|
|
FUNCTION SqlScan(db, cSQL)
|
|
LOCAL rows, cols, aResult, aRow, i, nCols
|
|
aResult := {}
|
|
rows := db:Query(cSQL) // Go *sql.Rows 반환
|
|
cols := rows:Columns() // 컬럼 이름 배열
|
|
nCols := Len(cols)
|
|
DO WHILE rows:Next()
|
|
aRow := {=>}
|
|
FOR i := 1 TO nCols
|
|
aRow[cols[i]] := rows:Column(i)
|
|
NEXT
|
|
AAdd(aResult, aRow)
|
|
ENDDO
|
|
rows:Close()
|
|
RETURN aResult
|
|
```
|
|
|
|
## 5. 배열 반환 처리
|
|
|
|
Go 함수가 슬라이스를 반환하면 자동으로 Harbour 배열로 변환:
|
|
|
|
```prg
|
|
IMPORT "strings"
|
|
|
|
PROCEDURE Main()
|
|
LOCAL aParts, i
|
|
|
|
aParts := strings.Split("one,two,three", ",")
|
|
|
|
? Len(aParts) // 3
|
|
? aParts[1] // "one"
|
|
? aParts[2] // "two"
|
|
? aParts[3] // "three"
|
|
|
|
FOR i := 1 TO Len(aParts)
|
|
? " [" + Str(i, 1) + "]", aParts[i]
|
|
NEXT
|
|
RETURN
|
|
```
|
|
|
|
## 6. #pragma BEGINDUMP — 고급 사용 (선택)
|
|
|
|
복잡한 Go 로직이 필요한 경우에만 사용:
|
|
|
|
```prg
|
|
PROCEDURE Main()
|
|
? MyGoFunc("hello")
|
|
RETURN
|
|
|
|
#pragma BEGINDUMP
|
|
import "five/hbrt"
|
|
|
|
func init() {
|
|
hbrt.HB_FUNC("MYGOFUNC", func(ctx *hbrt.HBContext) {
|
|
// 복잡한 Go 로직
|
|
s := ctx.ParC(1)
|
|
ctx.RetC(strings.ToUpper(s) + "!!!")
|
|
})
|
|
}
|
|
#pragma ENDDUMP
|
|
```
|
|
|
|
### HB_FUNC API (Harbour C API 호환)
|
|
|
|
| Harbour C | Five Go | 설명 |
|
|
|-----------|---------|------|
|
|
| `HB_FUNC(NAME)` | `hbrt.HB_FUNC("NAME", fn)` | 함수 등록 |
|
|
| `hb_pcount()` | `ctx.PCount()` | 파라미터 수 |
|
|
| `hb_parc(n)` | `ctx.ParC(n)` | 문자열 파라미터 |
|
|
| `hb_parni(n)` | `ctx.ParNI(n)` | 정수 파라미터 |
|
|
| `hb_parnl(n)` | `ctx.ParNL(n)` | Long 파라미터 |
|
|
| `hb_parnd(n)` | `ctx.ParND(n)` | Double 파라미터 |
|
|
| `hb_parl(n)` | `ctx.ParL(n)` | 논리값 |
|
|
| `hb_pards(n)` | `ctx.ParDS(n)` | 날짜 (YYYYMMDD) |
|
|
| `hb_pardl(n)` | `ctx.ParDL(n)` | 날짜 (Julian) |
|
|
| `HB_ISCHAR(n)` | `ctx.IsChar(n)` | 타입 체크 |
|
|
| `HB_ISNUM(n)` | `ctx.IsNum(n)` | 타입 체크 |
|
|
| `HB_ISLOG(n)` | `ctx.IsLog(n)` | 타입 체크 |
|
|
| `HB_ISARRAY(n)` | `ctx.IsArray(n)` | 타입 체크 |
|
|
| `HB_ISNIL(n)` | `ctx.IsNil(n)` | 타입 체크 |
|
|
| `hb_retc(s)` | `ctx.RetC(s)` | 문자열 반환 |
|
|
| `hb_retni(n)` | `ctx.RetNI(n)` | 정수 반환 |
|
|
| `hb_retnl(n)` | `ctx.RetNL(n)` | Long 반환 |
|
|
| `hb_retnd(d)` | `ctx.RetND(d)` | Double 반환 |
|
|
| `hb_retl(b)` | `ctx.RetL(b)` | 논리값 반환 |
|
|
| `hb_retds(s)` | `ctx.RetDS(s)` | 날짜 반환 |
|
|
| `hb_storc(s,n)` | `ctx.StorC(s,n)` | By-ref 저장 |
|
|
| `hb_storni(v,n)` | `ctx.StorNI(v,n)` | By-ref 저장 |
|
|
| `hb_arrayNew()` | `ctx.ArrayNew(n)` | 배열 생성 |
|
|
| `hb_arrayGet()` | `ctx.ArrayGet(v,i)` | 배열 읽기 |
|
|
| `hb_arraySet()` | `ctx.ArraySet(v,i,x)` | 배열 쓰기 |
|
|
| `hb_hashNew()` | `ctx.HashNew()` | 해시 생성 |
|
|
|
|
### Five 확장 API (Harbour에 없는 Go 전용)
|
|
|
|
| API | 설명 |
|
|
|-----|------|
|
|
| `ctx.ParDate(n)` | `time.Time` 반환 |
|
|
| `ctx.ParArray(n)` | `[]Value` 반환 |
|
|
| `ctx.ParHash(n)` | `*HbHash` 반환 |
|
|
| `ctx.RetArray(items)` | 배열 반환 |
|
|
| `ctx.RetHash(h)` | 해시 반환 |
|
|
| `ctx.RetVal(v)` | 임의 Value 반환 |
|
|
| `hbrt.WrapGo(obj)` | Go 객체 → Value |
|
|
| `hbrt.UnwrapGo(v)` | Value → Go 객체 |
|
|
| `hbrt.GoCall(v, method, args...)` | reflect 메서드 호출 |
|
|
|
|
## 7. 사용 가능한 Go 패키지 예시
|
|
|
|
| 패키지 | PRG 사용 예 |
|
|
|--------|-------------|
|
|
| `strings` | `strings.ToUpper()`, `strings.Split()`, `strings.Contains()` |
|
|
| `strconv` | `strconv.Atoi()`, `strconv.FormatFloat()` |
|
|
| `fmt` | `fmt.Sprintf()` |
|
|
| `database/sql` | `sql.Open()` → `db:Exec()`, `db:Query()` |
|
|
| `net/http` | HTTP 서버, REST API |
|
|
| `encoding/json` | JSON encode/decode |
|
|
| `os` | `os.ReadFile()`, `os.Stat()` |
|
|
| `path/filepath` | `filepath.Join()`, `filepath.Glob()` |
|
|
| `time` | `time.Now()`, `time.Since()` |
|
|
| `crypto/sha256` | 해시 함수 |
|
|
| `regexp` | 정규식 |
|
|
| `sort` | 정렬 |
|
|
| 외부 패키지 | `modernc.org/sqlite`, `github.com/...` 등 |
|
|
|
|
## 8. 핵심 원칙
|
|
|
|
1. **IMPORT만으로 사용** — `#pragma BEGINDUMP` 불필요
|
|
2. **PRG 코드 100%** — Go 코드 0줄로 Go 기능 사용
|
|
3. **자동 타입 변환** — string/int/bool/array/hash 양방향
|
|
4. **Go 객체 투명 전달** — LOCAL 변수에 저장, `:` 로 메서드 호출
|
|
5. **Harbour 호환** — 기존 xBase 문법 그대로, Go는 백엔드
|