# Harbour PRG → Go Transpiler Strategy > PRG 소스를 Go 소스 코드로 변환하는 트랜스파일러 설계 문서 > 기존 PRG → C (gencc.c/genc.c) 패턴을 분석하고 Go 대응 설계를 제시 > > Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com) > All rights reserved. --- ## 목차 1. [현재 파이프라인: PRG → C](#1-현재-파이프라인-prg--c) 2. [목표 파이프라인: PRG → Go](#2-목표-파이프라인-prg--go) 3. [gencc.c 패턴 분석](#3-genccc-패턴-분석) 4. [Go 코드 생성 설계](#4-go-코드-생성-설계) 5. [Go 런타임 라이브러리 설계](#5-go-런타임-라이브러리-설계) 6. [변환 예시: PRG → C → Go 비교](#6-변환-예시-prg--c--go-비교) 7. [심볼 테이블 생성](#7-심볼-테이블-생성) 8. [스코프와 변수 매핑](#8-스코프와-변수-매핑) 9. [제어 흐름 변환](#9-제어-흐름-변환) 10. [OOP 변환](#10-oop-변환) 11. [매크로와 코드 블록](#11-매크로와-코드-블록) 12. [빌드 파이프라인](#12-빌드-파이프라인) 13. [스레딩 문제 해결](#13-스레딩-문제-해결) 14. [구현 순서](#14-구현-순서) --- ## 1. 현재 파이프라인: PRG → C ### 전체 흐름 ``` test.prg │ ▼ ┌──────────────────┐ │ hb_comp_yyparse │ Bison 파서 (harbour.y) │ (파싱) │ └────────┬─────────┘ ▼ ┌──────────────────┐ │ pcode 생성 │ AST → 바이트코드 (hbpcode.c) └────────┬─────────┘ ▼ ┌──────────────────┐ │ 최적화 │ hb_compOptimizePCode() └────────┬─────────┘ ▼ ┌──────────────────┐ │ genc.c │ pcode → C 소스 (바이트 배열) │ 또는 │ 또는 │ gencc.c │ pcode → C 함수 호출 (실제 코드) └────────┬─────────┘ ▼ test.c 생성된 C 파일 │ ▼ ┌──────────────────┐ │ C 컴파일러 │ gcc / msvc / clang │ + Harbour 런타임 │ libharbour.a 링크 └────────┬─────────┘ ▼ test.exe 네이티브 바이너리 ``` ### 두 가지 C 생성 모드 | 모드 | 파일 | 출력 형태 | 용도 | |------|------|----------|------| | **Compact** | `genc.c` | pcode를 바이트 배열로 임베드 | 기본 모드, VM이 해석 실행 | | **RealCode** | `gencc.c` | pcode를 `hb_xvm*()` 함수 호출로 변환 | 최적화 모드, 직접 실행 | **Go 변환은 RealCode 모드(gencc.c)를 기반으로 설계한다.** pcode 바이트 배열을 임베드하는 것이 아니라, 각 opcode를 Go 런타임 함수 호출로 변환한다. --- ## 2. 목표 파이프라인: PRG → Go ### 전체 흐름 ``` test.prg │ ▼ ┌──────────────────┐ │ Parser │ hand-written recursive descent (Go) │ (파싱) │ └────────┬─────────┘ ▼ ┌──────────────────┐ │ AST │ typed AST nodes └────────┬─────────┘ ▼ ┌──────────────────┐ │ Analyzer │ 타입 추론, 스코프 해석, 상수 폴딩 └────────┬─────────┘ ▼ ┌──────────────────┐ │ gengo.go │ AST → Go 소스 코드 │ (Go 코드 생성) │ └────────┬─────────┘ ▼ test_hb.go 생성된 Go 파일 │ ▼ ┌──────────────────┐ │ go build │ Go 컴파일러 │ + hbrt 패키지 │ harbour-go 런타임 라이브러리 └────────┬─────────┘ ▼ test 네이티브 바이너리 (단일 파일) ``` ### C 모드 vs Go 모드 비교 | 항목 | PRG → C (gencc.c) | PRG → Go (gengo.go) | |------|-------------------|---------------------| | 파서 | Bison (harbour.y) | hand-written recursive descent | | 중간 표현 | pcode 바이트코드 | typed AST | | 코드 생성 | `fprintf(yyc, "hb_xvm*(...)")` | Go AST → `go/format` | | 런타임 | libharbour.a (C) | `hbrt` 패키지 (Go) | | 컴파일러 | gcc/msvc | `go build` | | 실행 파일 | 동적 링크 가능 | 단일 정적 바이너리 | | 스레딩 | pthread + 수동 mutex | goroutine + channel | | GC | 자체 mark-sweep | Go 런타임 GC | --- ## 3. gencc.c 패턴 분석 ### 핵심 패턴: pcode opcode → C 런타임 함수 호출 gencc.c는 각 pcode opcode를 대응하는 `hb_xvm*()` C 함수 호출로 변환한다. #### 패턴 1: 값 Push ```c // pcode: HB_P_PUSHINT (정수 push) // 생성되는 C 코드: fprintf(yyc, "\tif( hb_xvmPushInt( %dL ) ) break;\n", value); // pcode: HB_P_PUSHSTRCONST (문자열 상수 push) // 생성되는 C 코드: fprintf(yyc, "\thb_xvmPushStringConst( \"hello\", 5 );\n"); // pcode: HB_P_PUSHLOCAL (로컬 변수 push) // 생성되는 C 코드: fprintf(yyc, "\tif( hb_xvmPushLocal( %d ) ) break;\n", localIndex); ``` #### 패턴 2: 산술/비교 연산 ```c // pcode: HB_P_PLUS fprintf(yyc, "\tif( hb_xvmPlus() ) break;\n"); // 최적화: PUSH + 연산 결합 // pcode: HB_P_PUSHINT(5) + HB_P_PLUS fprintf(yyc, "\tif( hb_xvmAddInt( 5L ) ) break;\n"); ``` #### 패턴 3: 제어 흐름 (goto + label) ```c // pcode: HB_P_JUMP → C goto fprintf(yyc, "\tgoto lab%05u;\n", labelNumber); // pcode: HB_P_JUMPFALSE → C conditional goto fprintf(yyc, "\tif( hb_xvmPopLogical(&fValue) ) break;\n"); fprintf(yyc, "\tif( !fValue )\n\t\tgoto lab%05u;\n", labelNumber); // 점프 대상 → C label fprintf(yyc, "lab%05u: ;\n", labelNumber); ``` #### 패턴 4: 함수 호출 ```c // pcode: HB_P_FUNCTION → C 호출 fprintf(yyc, "\tif( hb_xvmFunction( %hu ) ) break;\n", paramCount); // pcode: HB_P_SEND → 메서드 호출 fprintf(yyc, "\tif( hb_xvmSend( %hu ) ) break;\n", paramCount); ``` #### 패턴 5: 에러 처리 (do-break 패턴) ```c // 생성되는 C 코드의 함수 전체 구조: HB_FUNC( MYFUNC ) { do { // ... 모든 코드가 do { } while(0) 안에 ... // hb_xvm*() 함수가 TRUE 반환 = 에러 발생 // → break로 do 블록 탈출 if( hb_xvmPlus() ) break; if( hb_xvmPopLocal(1) ) break; } while( 0 ); hb_xvmExitProc(); } ``` ### gencc.c의 최적화 패턴 gencc.c는 연속된 pcode를 분석하여 융합된 C 함수 호출을 생성한다: ```c // PUSHINT + POPLOCAL → hb_xvmLocalSetInt() // PUSHINT + PLUS → hb_xvmAddInt() // PUSHINT + EQUAL + JUMPFALSE → hb_xvmEqualIntIs() + goto // PUSHINT + RETVALUE → hb_xvmRetInt() // PUSHLOCAL + PLUS + POPLOCAL → hb_xvmLocalAdd() ``` --- ## 4. Go 코드 생성 설계 ### 핵심 원칙 ``` 1. gencc.c가 hb_xvm*() C 함수를 호출하듯, gengo는 hbrt.*() Go 함수를 호출하는 Go 코드를 생성한다. 2. C의 goto+label → Go의 구조적 제어 흐름으로 변환한다. (Go에는 goto가 있으나, 구조적 변환이 가능하면 우선 사용) 3. C의 do{...break...}while(0) 에러 패턴 → Go의 error 반환 또는 panic/recover로 변환한다. ``` ### 생성되는 Go 코드 구조 ```go // test.prg에서 생성된 test_hb.go package main import "harbour-go/hbrt" // 심볼 테이블 var symbols = hbrt.NewSymbolTable( hbrt.Symbol{Name: "MAIN", Scope: hbrt.FsPublic | hbrt.FsLocal, Func: HB_MAIN}, hbrt.Symbol{Name: "HELPER", Scope: hbrt.FsStatic | hbrt.FsLocal, Func: HB_HELPER}, hbrt.Symbol{Name: "QOUT", Scope: hbrt.FsPublic, Func: nil}, // 외부 ) func HB_MAIN(t *hbrt.Thread) { t.Frame(0, 2) // 파라미터 0개, 로컬 2개 defer t.EndProc() // LOCAL nCount := 10 t.PushInt(10) t.PopLocal(1) // LOCAL cName := "World" t.PushString("World") t.PopLocal(2) // ? "Hello " + cName t.PushSymbol(symbols.Find("QOUT")) t.PushNil() t.PushString("Hello ") t.PushLocal(2) t.Plus() t.Function(1) // RETURN nCount t.PushLocal(1) t.RetValue() } func HB_HELPER(t *hbrt.Thread) { t.Frame(1, 0) // 파라미터 1개, 로컬 0개 defer t.EndProc() // RETURN param1 * 2 t.PushLocal(1) t.PushInt(2) t.Mult() t.RetValue() } func main() { hbrt.Init(symbols) hbrt.Run("MAIN") } ``` ### C gencc.c 출력 vs Go gengo 출력 비교 **동일한 PRG:** ```harbour FUNCTION Main() LOCAL n := 10 ? n + 5 RETURN n ``` **C 출력 (gencc.c):** ```c HB_FUNC( MAIN ) { do { static const HB_BYTE pcode[] = { ... }; hb_xvmFrame( 0, 1 ); hb_xvmLocalSetInt( 1, 10L ); hb_xvmPushSymbol( symbols + 1 ); // QOUT hb_xvmPushNil(); if( hb_xvmPushLocal( 1 ) ) break; if( hb_xvmAddInt( 5L ) ) break; if( hb_xvmFunction( 1 ) ) break; if( hb_xvmPushLocal( 1 ) ) break; hb_xvmRetValue(); } while( 0 ); hb_xvmExitProc(); } ``` **Go 출력 (gengo):** ```go func HB_MAIN(t *hbrt.Thread) { t.Frame(0, 1) defer t.EndProc() t.LocalSetInt(1, 10) t.PushSymbol(symbols.At(1)) // QOUT t.PushNil() t.PushLocal(1) t.AddInt(5) t.Function(1) t.PushLocal(1) t.RetValue() } ``` --- ## 5. Go 런타임 라이브러리 설계 ### 패키지 구조 ``` harbour-go/ ├── cmd/ │ └── harbour/ ← CLI: harbour build, harbour run │ └── main.go ├── hbrt/ ← 핵심 런타임 │ ├── value.go ← Tagged Value 16B │ ├── thread.go ← goroutine별 실행 컨텍스트 │ ├── stack.go ← eval 스택 │ ├── symbol.go ← 심볼 테이블 │ ├── class.go ← OOP 클래스 시스템 │ ├── error.go ← 에러 처리 (BEGIN SEQUENCE) │ ├── ops_arith.go ← 산술 연산 (Plus, Minus, ...) │ ├── ops_compare.go ← 비교 연산 │ ├── ops_string.go ← 문자열 연산 │ ├── macro.go ← 런타임 매크로 컴파일 │ └── init.go ← 초기화 ├── hbrtl/ ← 표준 라이브러리 (RTL) │ ├── strings.go ← SUBSTR, ALLTRIM, UPPER, ... │ ├── numeric.go ← INT, VAL, STR, ROUND, ... │ ├── datetime.go ← DATE, TIME, CTOD, DTOC, ... │ ├── file.go ← FOPEN, FCLOSE, FREAD, ... │ ├── console.go ← QOUT, ACCEPT, INKEY, ... │ └── ... ├── hbrdd/ ← RDD (데이터베이스) │ ├── workarea.go │ ├── dbf.go │ ├── ntx.go │ ├── cdx.go │ └── ... └── compiler/ ← 컴파일러 (PRG → Go) ├── lexer.go ├── parser.go ├── ast.go ├── analyzer.go └── gengo.go ← Go 코드 생성기 ``` ### hbrt.Thread — gencc.c의 hb_xvm* 함수 대응 ```go // hbrt/thread.go type Thread struct { stack []Value // eval 스택 sp int // 스택 포인터 calls []CallFrame // 호출 스택 locals []Value // 현재 함수의 로컬 변수 memvars map[string]*Value // PRIVATE 변수 statics *[]Value // STATIC 변수 (모듈별) vm *VM // 공유 VM 상태 } ``` ### hb_xvm* → Thread 메서드 매핑 gencc.c가 생성하는 C 함수 호출과 Go 런타임 메서드의 1:1 매핑: ``` C (gencc.c 출력) Go (Thread 메서드) ───────────────────────────────── ────────────────────────────── hb_xvmFrame(params, locals) t.Frame(params, locals) hb_xvmExitProc() t.EndProc() hb_xvmPushNil() t.PushNil() hb_xvmPushInt(n) t.PushInt(n) hb_xvmPushLong(n) t.PushLong(n) hb_xvmPushDouble(v, w, d) t.PushDouble(v, w, d) hb_xvmPushStringConst(s, len) t.PushString(s) hb_xvmPushLogical(b) t.PushBool(b) hb_xvmPushDate(julian) t.PushDate(julian) hb_xvmPushLocal(n) t.PushLocal(n) hb_xvmPushLocalByRef(n) t.PushLocalRef(n) hb_xvmPushStatic(n) t.PushStatic(n) hb_xvmPushStaticByRef(n) t.PushStaticRef(n) hb_xvmPushMemvar(sym) t.PushMemvar(sym) hb_xvmPushField(sym) t.PushField(sym) hb_xvmPushSymbol(sym) t.PushSymbol(sym) hb_xvmPopLocal(n) t.PopLocal(n) hb_xvmPopStatic(n) t.PopStatic(n) hb_xvmPopMemvar(sym) t.PopMemvar(sym) hb_xvmPopField(sym) t.PopField(sym) hb_xvmPop() t.Pop() hb_xvmDuplicate() t.Dup() hb_xvmPlus() t.Plus() hb_xvmMinus() t.Minus() hb_xvmMult() t.Mult() hb_xvmDivide() t.Divide() hb_xvmModulus() t.Modulus() hb_xvmPower() t.Power() hb_xvmNegate() t.Negate() hb_xvmInc() t.Inc() hb_xvmDec() t.Dec() hb_xvmEqual() t.Equal() hb_xvmExactlyEqual() t.ExactEqual() hb_xvmNotEqual() t.NotEqual() hb_xvmLess() t.Less() hb_xvmLessEqual() t.LessEqual() hb_xvmGreater() t.Greater() hb_xvmGreaterEqual() t.GreaterEqual() hb_xvmNot() t.Not() hb_xvmAnd() t.And() hb_xvmOr() t.Or() hb_xvmFunction(n) t.Function(n) hb_xvmDo(n) t.Do(n) hb_xvmSend(n) t.Send(n) hb_xvmRetValue() t.RetValue() hb_xvmArrayGen(n) t.ArrayGen(n) hb_xvmArrayPush() t.ArrayPush() hb_xvmArrayPop() t.ArrayPop() hb_xvmSeqBegin(offset) t.SeqBegin(recoverFunc) hb_xvmSeqEnd() t.SeqEnd() hb_xvmSeqRecover() t.SeqRecover() hb_xvmPopLogical(&fValue) t.PopLogical() bool hb_xvmLocalSetInt(n, val) t.LocalSetInt(n, val) hb_xvmAddInt(n) t.AddInt(n) hb_xvmRetInt(n) t.RetInt(n) hb_xvmLocalAdd(n) t.LocalAdd(n) hb_xvmEqualIntIs(n, &fValue) t.EqualIntIs(n) bool ``` ### 에러 처리 패턴 변환 **C (gencc.c): `if(hb_xvm*()) break` 패턴** ```c // C에서는 모든 연산이 에러 시 TRUE 반환 // break로 do{}while(0) 탈출 후 hb_xvmExitProc() do { if( hb_xvmPlus() ) break; if( hb_xvmPopLocal(1) ) break; } while( 0 ); hb_xvmExitProc(); ``` **Go: panic/recover 패턴** ```go // Go에서는 런타임 에러를 panic으로 전파 // defer t.EndProc()가 recover() 포함 func HB_MAIN(t *hbrt.Thread) { t.Frame(0, 1) defer t.EndProc() // recover() + 스택 정리 // hb_xvm*()의 break 패턴이 불필요 // 에러 시 Plus() 내부에서 panic t.Plus() t.PopLocal(1) } ``` ```go // hbrt/thread.go func (t *Thread) EndProc() { if r := recover(); r != nil { if hbErr, ok := r.(*HbError); ok { t.handleError(hbErr) // BEGIN SEQUENCE 처리 } else { panic(r) // 예상치 못한 panic은 전파 } } t.restoreFrame() } func (t *Thread) Plus() { b := t.pop() a := t.peek() // top을 교체할 것이므로 peek result, err := valueAdd(a, b) if err != nil { panic(t.runtimeError("EG_ARG", "+", a, b)) } t.setTop(result) } ``` --- ## 6. 변환 예시: PRG → C → Go 비교 ### 예시 1: 기본 함수 **PRG:** ```harbour FUNCTION Greet(cName) LOCAL cMsg cMsg := "Hello, " + cName + "!" ? cMsg RETURN cMsg ``` **C (gencc.c 스타일):** ```c HB_FUNC( GREET ) { do { hb_xvmFrame( 1, 1 ); // 파라미터 1, 로컬 1 hb_xvmPushStringConst("Hello, ", 7); if( hb_xvmPushLocal(1) ) break; // cName if( hb_xvmPlus() ) break; hb_xvmPushStringConst("!", 1); if( hb_xvmPlus() ) break; if( hb_xvmPopLocal(2) ) break; // cMsg hb_xvmPushSymbol(symbols + 1); // QOUT hb_xvmPushNil(); if( hb_xvmPushLocal(2) ) break; // cMsg if( hb_xvmFunction(1) ) break; if( hb_xvmPushLocal(2) ) break; // cMsg hb_xvmRetValue(); } while(0); hb_xvmExitProc(); } ``` **Go (gengo 스타일):** ```go func HB_GREET(t *hbrt.Thread) { t.Frame(1, 1) defer t.EndProc() t.PushString("Hello, ") t.PushLocal(1) // cName t.Plus() t.PushString("!") t.Plus() t.PopLocal(2) // cMsg t.PushSymbol(sym_QOUT) t.PushNil() t.PushLocal(2) // cMsg t.Function(1) t.PushLocal(2) // cMsg t.RetValue() } ``` ### 예시 2: DO WHILE 루프 **PRG:** ```harbour FUNCTION SumTo(nMax) LOCAL nSum := 0, i := 1 DO WHILE i <= nMax nSum += i i++ ENDDO RETURN nSum ``` **C (gencc.c 스타일):** ```c HB_FUNC( SUMTO ) { do { HB_BOOL fValue; hb_xvmFrame( 1, 2 ); hb_xvmLocalSetInt( 2, 0L ); // nSum := 0 hb_xvmLocalSetInt( 3, 1L ); // i := 1 lab00001: ; if( hb_xvmPushLocal( 3 ) ) break; // i if( hb_xvmPushLocal( 1 ) ) break; // nMax if( hb_xvmLessEqual() ) break; if( hb_xvmPopLogical( &fValue ) ) break; if( !fValue ) goto lab00002; if( hb_xvmPushLocal( 3 ) ) break; // i hb_xvmLocalAdd( 2 ); // nSum += top hb_xvmLocalAddInt( 3, 1 ); // i++ goto lab00001; lab00002: ; if( hb_xvmPushLocal( 2 ) ) break; // nSum hb_xvmRetValue(); } while(0); hb_xvmExitProc(); } ``` **Go (gengo 스타일) — goto 버전:** ```go func HB_SUMTO(t *hbrt.Thread) { t.Frame(1, 2) defer t.EndProc() t.LocalSetInt(2, 0) // nSum := 0 t.LocalSetInt(3, 1) // i := 1 lab00001: t.PushLocal(3) // i t.PushLocal(1) // nMax t.LessEqual() if !t.PopLogical() { goto lab00002 } t.PushLocal(3) // i t.LocalAdd(2) // nSum += top t.LocalAddInt(3, 1) // i++ goto lab00001 lab00002: t.PushLocal(2) // nSum t.RetValue() } ``` **Go (gengo 스타일) — 구조적 버전 (최적화 시):** ```go func HB_SUMTO(t *hbrt.Thread) { t.Frame(1, 2) defer t.EndProc() t.LocalSetInt(2, 0) // nSum := 0 t.LocalSetInt(3, 1) // i := 1 for { t.PushLocal(3) // i t.PushLocal(1) // nMax t.LessEqual() if !t.PopLogical() { break } t.PushLocal(3) // i t.LocalAdd(2) // nSum += top t.LocalAddInt(3, 1) // i++ } t.PushLocal(2) // nSum t.RetValue() } ``` ### 예시 3: BEGIN SEQUENCE **PRG:** ```harbour FUNCTION SafeOpen(cFile) LOCAL lOk := .F. BEGIN SEQUENCE USE (cFile) ALIAS data lOk := .T. RECOVER ? "Error opening: " + cFile END SEQUENCE RETURN lOk ``` **Go (gengo 스타일):** ```go func HB_SAFEOPEN(t *hbrt.Thread) { t.Frame(1, 1) defer t.EndProc() t.PushBool(false) t.PopLocal(2) // lOk := .F. t.SeqBegin(func(t *hbrt.Thread) { // RECOVER 블록 t.PushSymbol(sym_QOUT) t.PushNil() t.PushString("Error opening: ") t.PushLocal(1) // cFile t.Plus() t.Function(1) }) // BEGIN SEQUENCE 블록 t.PushSymbol(sym_USE) t.PushNil() t.PushLocal(1) // cFile t.Do(1) t.PushBool(true) t.PopLocal(2) // lOk := .T. t.SeqEnd() t.PushLocal(2) // lOk t.RetValue() } ``` --- ## 7. 심볼 테이블 생성 ### C 생성 (genc.c) ```c HB_INIT_SYMBOLS_BEGIN( hb_vm_SymbolInit_TEST ) { "MAIN", {HB_FS_PUBLIC | HB_FS_LOCAL | HB_FS_FIRST}, {HB_FUNCNAME( MAIN )}, NULL }, { "QOUT", {HB_FS_PUBLIC}, {HB_FUNCNAME( QOUT )}, NULL }, { "HELPER", {HB_FS_STATIC | HB_FS_LOCAL}, {HB_FUNCNAME( HELPER )}, NULL }, { "USE", {HB_FS_PUBLIC}, {NULL}, NULL } HB_INIT_SYMBOLS_END( hb_vm_SymbolInit_TEST ) ``` ### Go 생성 ```go package main import "harbour-go/hbrt" import "harbour-go/hbrtl" var symbols = hbrt.NewModule("TEST", hbrt.Sym("MAIN", hbrt.FsPublic|hbrt.FsLocal|hbrt.FsFirst, HB_MAIN), hbrt.Sym("QOUT", hbrt.FsPublic, hbrtl.QOUT), hbrt.Sym("HELPER", hbrt.FsStatic|hbrt.FsLocal, HB_HELPER), hbrt.Sym("USE", hbrt.FsPublic, nil), // RDD 명령 ) // 컴파일 타임 심볼 참조 (인덱스 캐싱) var ( sym_MAIN = symbols.At(0) sym_QOUT = symbols.At(1) sym_HELPER = symbols.At(2) sym_USE = symbols.At(3) ) ``` ### STATIC 변수 ```go // C: static 배열로 생성 // HB_FUNC_INITSTATICS() { ... } // Go: 모듈 레벨 변수로 생성 var statics_TEST = hbrt.NewStatics(3) // 3개의 STATIC 변수 // STATIC 초기화 함수 func init() { hbrt.RegisterStatics("TEST", statics_TEST) // STATIC nCounter := 0 statics_TEST.Set(0, hbrt.MakeInt(0)) // STATIC cPrefix := "LOG_" statics_TEST.Set(1, hbrt.MakeString("LOG_")) // STATIC aCache := {} statics_TEST.Set(2, hbrt.MakeArray(0)) } ``` --- ## 8. 스코프와 변수 매핑 ### Harbour 변수 스코프 → Go 매핑 | Harbour 스코프 | C 매핑 | Go 매핑 | |---------------|--------|---------| | LOCAL | 스택의 HB_ITEM | `t.locals[]` (Thread-local) | | STATIC | 모듈 static 배열 | `statics_MODULE[]` (패키지 변수) | | PRIVATE | 동적 심볼의 memvar | `t.memvars[name]` (Thread-local map) | | PUBLIC | 동적 심볼의 memvar | `vm.publics[name]` (sync.RWMutex 보호) | | FIELD | WorkArea 필드 | `t.currentWA().Field(name)` | | PARAMETER | 스택의 HB_ITEM (음수 오프셋) | `t.locals[1..paramCount]` | ### 변수 접근 코드 생성 ```go // LOCAL nX → t.PushLocal(n) / t.PopLocal(n) // STATIC nY → t.PushStatic(n) / t.PopStatic(n) // PRIVATE cZ → t.PushMemvar(sym) / t.PopMemvar(sym) // PUBLIC cW → t.PushMemvar(sym) / t.PopMemvar(sym) (PUBLIC도 memvar) // FIELD->Name → t.PushField(sym) / t.PopField(sym) // alias->Name → t.PushAliasedField(alias, sym) ``` --- ## 9. 제어 흐름 변환 ### C의 goto/label → Go 변환 전략 **전략 1: 직접 goto (단순, 기본)** Go도 goto를 지원한다. gencc.c의 패턴을 거의 그대로 변환 가능: ```go // DO WHILE → goto loop lab_1: t.PushLocal(1) t.PushInt(10) t.LessEqual() if !t.PopLogical() { goto lab_2 } // body goto lab_1 lab_2: ``` **전략 2: 구조적 변환 (최적화)** 컴파일러가 제어 흐름 그래프를 분석하여 for/if/switch로 변환: ```go // DO WHILE → for loop (분석 후 변환) for { t.PushLocal(1) t.PushInt(10) t.LessEqual() if !t.PopLogical() { break } // body } ``` **권장: Phase 1에서는 goto, Phase 2에서 구조적 변환 추가** ### 제어 흐름 매핑 | Harbour | C (gencc.c) | Go (gengo) | |---------|------------|------------| | `DO WHILE ... ENDDO` | label + goto | `for { if !cond { break } }` | | `FOR i := 1 TO n` | label + goto + addint | `for { ... t.LocalAddInt() }` | | `FOR EACH x IN arr` | label + goto + enum | `for { ... t.EnumNext() }` | | `IF ... ELSEIF ... ENDIF` | jumpfalse + label | `if ... { } else if ... { }` | | `DO CASE ... ENDCASE` | jumpfalse chain | `switch { case: ... }` 또는 if chain | | `BEGIN SEQUENCE` | seqbegin + recover addr | `t.SeqBegin(recoverFunc)` | | `SWITCH ... END` | jump table | `switch t.PopInt() { case: }` | --- ## 10. OOP 변환 ### PRG 클래스 정의 ```harbour CLASS Person DATA cName INIT "" DATA nAge INIT 0 METHOD New(cName, nAge) METHOD Greet() ENDCLASS METHOD New(cName, nAge) CLASS Person ::cName := cName ::nAge := nAge RETURN Self METHOD Greet() CLASS Person ? "Hi, I'm " + ::cName RETURN Self ``` ### Go 생성 코드 ```go func init() { hbrt.RegisterClass("PERSON", func(cls *hbrt.ClassDef) { cls.Data("CNAME", hbrt.MakeString("")) cls.Data("NAGE", hbrt.MakeInt(0)) cls.Method("NEW", HB_PERSON_NEW) cls.Method("GREET", HB_PERSON_GREET) }) } func HB_PERSON_NEW(t *hbrt.Thread) { t.Frame(2, 0) defer t.EndProc() // ::cName := cName t.PushLocal(1) // cName 파라미터 t.PushSelf() t.SendAssign("CNAME") // ::nAge := nAge t.PushLocal(2) // nAge 파라미터 t.PushSelf() t.SendAssign("NAGE") // RETURN Self t.PushSelf() t.RetValue() } func HB_PERSON_GREET(t *hbrt.Thread) { t.Frame(0, 0) defer t.EndProc() // ? "Hi, I'm " + ::cName t.PushSymbol(sym_QOUT) t.PushNil() t.PushString("Hi, I'm ") t.PushSelf() t.Send0("CNAME") // ::cName 읽기 t.Plus() t.Function(1) // RETURN Self t.PushSelf() t.RetValue() } ``` --- ## 11. 매크로와 코드 블록 ### 매크로 (`&variable`) 매크로는 **런타임 컴파일**이 필요하다. Go 런타임에 미니 컴파일러를 포함해야 한다: ```go // PRG: USE &cFile // Go 생성 코드: t.PushLocal(1) // cFile 값을 스택에 t.MacroPush() // 런타임 컴파일 + 실행 // hbrt/macro.go func (t *Thread) MacroPush() { str := t.pop().AsString() // 미니 파서 + 컴파일러로 str을 파싱 // 결과 코드 실행하여 값을 스택에 push code := t.vm.macroCompiler.Compile(str) code.Execute(t) } ``` ### 코드 블록 (`{|| ...}`) ```harbour LOCAL bBlock := {|x| x * 2} ? Eval(bBlock, 5) // 10 ``` ```go // 코드 블록: Go 클로저로 변환 // 캡처하는 로컬이 없으면 → 순수 함수 // 캡처하는 로컬이 있으면 → 클로저 + detached locals func HB_MAIN(t *hbrt.Thread) { t.Frame(0, 1) defer t.EndProc() // LOCAL bBlock := {|x| x * 2} t.PushBlock(func(bt *hbrt.Thread) { bt.Frame(1, 0) defer bt.EndProc() bt.PushLocal(1) // x bt.PushInt(2) bt.Mult() bt.RetValue() }, 0) // 0 = 캡처하는 로컬 없음 t.PopLocal(1) // ? Eval(bBlock, 5) t.PushSymbol(sym_QOUT) t.PushNil() t.PushLocal(1) // bBlock t.PushInt(5) t.EvalBlock(1) // Eval(block, 1 param) t.Function(1) // QOut } ``` ### 디태치된 로컬이 있는 코드 블록 ```harbour FUNCTION MakeCounter() LOCAL nCount := 0 RETURN {|| nCount++, nCount} ``` ```go func HB_MAKECOUNTER(t *hbrt.Thread) { t.Frame(0, 1) defer t.EndProc() t.PushInt(0) t.PopLocal(1) // nCount := 0 // 코드 블록 + 로컬 캡처 t.PushBlockWithLocals(func(bt *hbrt.Thread) { bt.Frame(0, 0) defer bt.EndProc() bt.PushDetachedLocal(0) // nCount (detached) bt.Inc() bt.PopDetachedLocal(0) bt.PushDetachedLocal(0) bt.RetValue() }, []int{1}) // 로컬 인덱스 1 (nCount)을 캡처 t.RetValue() } ``` --- ## 12. 빌드 파이프라인 ### CLI 사용법 ```bash # 단일 파일 컴파일 + 실행 harbour run test.prg # 컴파일만 (Go 소스 생성) harbour build test.prg -o test_hb.go # 프로젝트 빌드 (여러 PRG → Go → 바이너리) harbour build ./src/*.prg -o myapp # 내부 동작: # 1. *.prg → parser → AST # 2. AST → analyzer # 3. analyzer → gengo → *_hb.go # 4. go build -o myapp ./_harbour_build/ ``` ### 생성되는 프로젝트 구조 ``` _harbour_build/ (임시 빌드 디렉토리) ├── go.mod ├── main.go (엔트리포인트) ├── test_hb.go (test.prg에서 생성) ├── utils_hb.go (utils.prg에서 생성) └── module_init.go (모듈 초기화, 심볼 테이블 결합) ``` ### main.go (자동 생성) ```go package main import "harbour-go/hbrt" func main() { vm := hbrt.NewVM() vm.RegisterModule(symbols_TEST) vm.RegisterModule(symbols_UTILS) vm.RegisterRTL() // 표준 라이브러리 vm.RegisterRDD() // RDD 드라이버 vm.Run("MAIN") } ``` --- ## 13. 스레딩 문제 해결 ### PRG → Go 변환이 스레딩 문제를 해결하는 방법 | 기존 문제 (C) | Go 변환에서의 해결 | |--------------|-------------------| | 글로벌 GC suspend 경합 | Go GC에 위임. HB_GARBAGE 계층 완전 제거 | | 심볼 테이블 수동 락 | `hbrt.SymbolTable`에 `sync.RWMutex` 캡슐화 | | 정적 버퍼 공유 | `Thread` 구조체에 모든 상태 캡슐화 | | WorkArea 크로스 스레드 | `Thread.currentWA()` = goroutine-local | | STRING 참조 카운트 레이스 | Go string = immutable, 또는 `atomic.Int32` refcount | | 클래스 풀 lazy init | `init()` 함수에서 완전 초기화, `sync.Once` 불필요 | | `hb_vmThreadRequest` volatile | `context.Context` 취소 신호 | ### Go 변환 후 스레드 모델 ```go // Harbour의 hb_threadStart() → Go의 goroutine func HB_MYTHREADSTART(t *hbrt.Thread) { t.Frame(1, 0) defer t.EndProc() // hb_threadStart(@WorkerFunc()) t.PushLocal(1) // 함수 심볼 // Go에서는 goroutine으로 실행 workerSym := t.pop() go func() { newThread := t.vm.NewThread() defer newThread.Destroy() newThread.PushSymbol(workerSym) newThread.PushNil() newThread.Function(0) }() } ``` ``` 각 goroutine은 독립된 Thread를 소유: - stack, locals, memvars: goroutine-local (락 불필요) - symbols, classes: 공유 읽기 전용 (RWMutex) - publics: sync.RWMutex 보호 - WorkArea: goroutine별 소유권 ``` --- ## 14. 구현 순서 ### Phase 0: 기반 타입 (1주) ``` 구현: value.go (Tagged Value 16B) 검증: 모든 타입의 생성, 접근, 변환 단위 테스트 ``` ### Phase 1: 최소 런타임 (2주) ``` 구현: thread.go, stack.go, symbol.go, ops_arith.go, ops_compare.go 검증: 수동으로 작성한 Go 코드로 "? 1 + 2" 실행 ``` ### Phase 2: 파서 (3주) ``` 구현: lexer.go, parser.go, ast.go 검증: test.prg → AST 덤프 출력 ``` ### Phase 3: 코드 생성기 (3주) ``` 구현: analyzer.go, gengo.go 검증: test.prg → test_hb.go 생성 → go build → 실행 대상: LOCAL, IF, DO WHILE, FUNCTION, RETURN ``` ### Phase 4: RTL 기본 (2주) ``` 구현: hbrtl/ (QOUT, STR, VAL, SUBSTR, DATE 등 핵심 50개 함수) 검증: 기본 Harbour 프로그램 실행 ``` ### Phase 5: OOP + 코드 블록 (2주) ``` 구현: class.go, 코드 블록 변환, Eval() 검증: CLASS 정의 + 메서드 호출 동작 ``` ### Phase 6: 매크로 + PP (2주) ``` 구현: macro.go (미니 컴파일러), 전처리기 검증: &variable, #command 동작 ``` ### Phase 7: RDD (4주) ``` 구현: hbrdd/ (DBF 읽기/쓰기, NTX/CDX 인덱스) 검증: USE, APPEND, SEEK, REPLACE, INDEX ON ``` ### Phase 8: 도구 체인 (2주) ``` 구현: CLI (harbour build/run/fmt), LSP 기본 검증: VSCode에서 .prg 편집, 빌드, 실행 ``` --- ## 변경 이력 | 날짜 | 변경 내용 | |------|----------| | 2026-03-27 | 초기 작성. PRG→C 패턴 분석 및 PRG→Go 트랜스파일러 설계 |