Files
five/docs/harbour-prg-to-go-transpiler.md
Charles KWON OhJun 59568f3301 Five v0.9 — Harbour + Go fusion language
- 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>
2026-03-31 09:41:50 +09:00

31 KiB

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
  2. 목표 파이프라인: PRG → Go
  3. gencc.c 패턴 분석
  4. Go 코드 생성 설계
  5. Go 런타임 라이브러리 설계
  6. 변환 예시: PRG → C → Go 비교
  7. 심볼 테이블 생성
  8. 스코프와 변수 매핑
  9. 제어 흐름 변환
  10. OOP 변환
  11. 매크로와 코드 블록
  12. 빌드 파이프라인
  13. 스레딩 문제 해결
  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

// 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: 산술/비교 연산

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

// 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: 함수 호출

// 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 코드의 함수 전체 구조:
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 함수 호출을 생성한다:

// 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 코드 구조

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

FUNCTION Main()
   LOCAL n := 10
   ? n + 5
   RETURN n

C 출력 (gencc.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):

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* 함수 대응

// 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에서는 모든 연산이 에러 시 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에서는 런타임 에러를 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)
}
// 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:

FUNCTION Greet(cName)
   LOCAL cMsg
   cMsg := "Hello, " + cName + "!"
   ? cMsg
   RETURN cMsg

C (gencc.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 스타일):

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:

FUNCTION SumTo(nMax)
   LOCAL nSum := 0, i := 1
   DO WHILE i <= nMax
      nSum += i
      i++
   ENDDO
   RETURN nSum

C (gencc.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 버전:

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 스타일) — 구조적 버전 (최적화 시):

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:

FUNCTION SafeOpen(cFile)
   LOCAL lOk := .F.
   BEGIN SEQUENCE
      USE (cFile) ALIAS data
      lOk := .T.
   RECOVER
      ? "Error opening: " + cFile
   END SEQUENCE
   RETURN lOk

Go (gengo 스타일):

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)

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 생성

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 변수

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

변수 접근 코드 생성

// 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의 패턴을 거의 그대로 변환 가능:

// 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로 변환:

// 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 클래스 정의

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 생성 코드

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 런타임에 미니 컴파일러를 포함해야 한다:

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

코드 블록 ({|| ...})

LOCAL bBlock := {|x| x * 2}
? Eval(bBlock, 5)  // 10
// 코드 블록: 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
}

디태치된 로컬이 있는 코드 블록

FUNCTION MakeCounter()
   LOCAL nCount := 0
   RETURN {|| nCount++, nCount}
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 사용법

# 단일 파일 컴파일 + 실행
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 (자동 생성)

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.SymbolTablesync.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 변환 후 스레드 모델

// 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 트랜스파일러 설계