Skip to content

7. 확장 시스템: '달빛 약속'의 유연한 확장

'달빛 약속'은 자체적인 문법과 실행 환경을 가지고 있지만, 외부 세계, 특히 JavaScript/TypeScript의 풍부한 생태계와 상호작용할 수 있도록 설계되었습니다. 이러한 상호작용을 가능하게 하는 것이 바로 **확장 시스템(Extensions System)**입니다. 이 시스템은 '달빛 약속'의 기능을 유연하게 확장할 수 있는 강력한 도구입니다. 이번 장에서는 확장 시스템이 어떻게 동작하는지 자세히 알아보겠습니다.

확장 시스템의 목적: FFI(Foreign Function Interface) 및 미래

확장 시스템의 주된 목적은 현재 **FFI(Foreign Function Interface)**를 제공하는 것입니다. FFI는 한 프로그래밍 언어로 작성된 코드가 다른 프로그래밍 언어로 작성된 코드를 호출할 수 있도록 하는 메커니즘을 의미합니다. '달빛 약속'에서는 이를 통해 외부 JavaScript/TypeScript 코드를 '달빛 약속' 코드 내에서 실행하고, 그 결과를 다시 '달빛 약속'의 값으로 받아올 수 있습니다.

하지만 확장 시스템의 잠재력은 FFI에만 국한되지 않습니다. 미래에는 새로운 문법 요소 추가, 내장 함수 확장, 심지어 언어의 핵심 동작 방식 변경 등 더욱 다양한 형태의 확장을 지원할 수 있도록 설계될 예정입니다.

Extension 인터페이스 분석

확장 시스템의 핵심은 /core/extension/extension.ts에 정의된 Extension 인터페이스입니다. 새로운 확장을 만들려면 이 Extension 인터페이스를 구현하는 클래스를 작성해야 합니다.

Extension 인터페이스는 다음과 같은 주요 멤버를 가집니다.

  • manifest: ExtensionManifest: 확장의 메타데이터를 정의합니다. 특히 ffiRunner 속성을 통해 이 확장이 어떤 FFI 런타임(runtimeName)을 제공하는지 명시합니다. 예를 들어, QuickJS 확장은 runtimeName: 'QuickJS'를 가질 수 있습니다.
  • init?(): Promise<void> | void: 확장이 초기화될 때 호출되는 선택적 메소드입니다. 비동기 작업(예: 외부 라이브러리 로드, 환경 설정)이 필요한 경우 여기에 구현합니다.
  • executeFFI(code: string, args: FunctionInvokingParams): ValueType | Promise<ValueType>: 이 메소드가 바로 FFI의 핵심입니다. '달빛 약속' 코드에서 외부 함수 호출이 발생했을 때, 이 메소드가 호출되어 실제 외부 코드를 실행합니다.
    • code: 외부 런타임에서 실행할 코드 문자열입니다.
    • args: '달빛 약속' 코드에서 외부 함수로 전달된 인자들입니다. 이 인자들은 이미 '달빛 약속'의 ValueType 형태로 변환되어 전달됩니다.
    • 반환 값: executeFFI 메소드는 외부 코드의 실행 결과를 반드시 '달빛 약속'의 ValueType 형태로 반환해야 합니다. 이를 통해 외부에서 계산된 값이 '달빛 약속' 환경으로 자연스럽게 통합될 수 있습니다.

YaksokSession과의 연동

Extension 인스턴스는 YaksokSession과 긴밀하게 연동됩니다.

  1. 확장 등록: YaksokSessionextend(extension: Extension) 메소드를 통해 Extension 인스턴스를 세션에 추가합니다. 세션은 등록된 확장들을 내부적으로 관리합니다.

    typescript
    const session = new YaksokSession()
    await session.extend(new QuickJS()) // QuickJS 확장을 세션에 등록
  2. FFI 호출 처리: '달빛 약속' 코드 내에서 @확장명 문법을 사용하여 외부 함수를 호출하면, 실행기(Executer)는 YaksokSessionrunFFI 메소드를 호출합니다.

    YaksokSessionrunFFI(runtime: string, code: string, args: Record<string, any>): Promise<ValueType> 메소드는 다음과 같은 역할을 합니다.

    • runtime 이름(manifest.ffiRunner.runtimeName)을 기반으로 등록된 Extension을 찾습니다.
    • 찾은 ExtensionexecuteFFI 메소드를 호출하여 실제 외부 코드를 실행합니다.
    • executeFFI의 반환 값을 받아 '달빛 약속'의 ValueType으로 변환하여 '달빛 약속' 코드에 전달합니다.

FFI의 실제 용례: QuickJS 확장

'달빛 약속' 프로젝트에는 /quickjs 디렉터리에 QuickJS 런타임을 활용한 FFI 확장이 구현되어 있습니다. 이 확장은 Extension 인터페이스를 구현하여 '달빛 약속' 코드 내에서 JavaScript 코드를 실행할 수 있도록 합니다.

typescript
const session = new YaksokSession()
await session.extend(new QuickJS())

session.addModule(
    'main',
    `
번역(QuickJS), (배열) 중 최대값
***
return Math.max(...배열)
***

번역(QuickJS), (배열)에서 가장 큰 값 제거하기
***
const n = 배열.indexOf(Math.max(...배열))
return [...배열.slice(0, n), ...배열.slice(n + 1)]
***

내_점수 = [80, 90, 100]
내_점수 중 최대값 보여주기

내_점수 = 내_점수 에서 가장 큰 값 제거하기
내_점수 보여주기
내_점수 중 최대값 보여주기
`,
)

const result = await session.runModule('main')

이처럼 확장 시스템은 '달빛 약속'이 특정 환경이나 기능에 갇히지 않고, 외부의 풍부한 생태계를 활용할 수 있도록 하는 강력한 도구입니다.