Skip to content

번역: 약속을 외부 환경과 연결하기 ​

다른 언어와 런타임의 도움을 받는다면 약속 언어에서 제공하지 않는 기능도 사용할 수 있습니다. 이 때 사용하는 방법이 번역입니다. 예를 들어, 사용자 브라우저에서 제공하는 alert 함수를 약속에서 호출할 때 사용합니다. 다른 언어에서는 FFI, Interoperability 등으로 불립니다.

번역 문법의 활용도 ​

번역 문법의 활용도는 크게 두가지로 나뉩니다.

  • 약속이 아닌 언어의 코드를 호출해 값을 가져오기
  • 다른 언어 / 런타임에 값을 전달하고 가져오기

두 경우 모두 번역 문법을 사용해 구현되지만, 구현 과정 및 목적이 현저히 다르기에 구분과 명확한 이해가 필요합니다.

약속이 아닌 언어의 코드를 호출해 값을 가져오기 ​

파이썬, 자바스크립트 등의 코드를 약속 코드 내에서 직접 작성해 실행하는 방법입니다.

보안에 주의해서 구현해주세요

약속 코드에서 외부 런타임을 호출할 때 보안에 주의해주세요. 의도치 않게 악의적인 코드가 실행된다면 개인정보 유출, 시스템 손상 등의 심각한 문제가 발생할 수 있습니다. 보안 샌드박스, 컨테이너 등을 사용해 외부 런타임의 영향을 최소화해주세요.

Vyper
번역(Python), (핀_번호)번 핀을 디지털로 설정하기
***
    import RPi.GPIO as GPIO
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(핀_번호, GPIO.OUT)
***

(10)번 핀을 디지털로 설정하기

다른 언어 / 런타임에 값을 전달하고 가져오기 ​

약속 코드에서 다른 언어나 런타임에 값을 전달하고 가져오는 방법입니다.

Vyper
번역(Arduino), (핀_번호)번 핀을 디지털로 설정하기
***
{
    "action": "setPinDigital"
}
***

(10)번 핀을 디지털로 설정하기

외부 런타임을 연결하기 ​

외부 런타임은 확장(Extension) 을 통해 약속 세션에 연결할 수 있습니다. 확장은 Extension 인터페이스를 구현하는 클래스입니다.

typescript
import {
    YaksokSession,
    Extension,
    ExtensionManifest,
    ValueType,
    FunctionInvokingParams,
} from '@dalbit-yaksok/core'

class MyExtension implements Extension {
    manifest: ExtensionManifest = {
        ffiRunner: {
            runtimeName: 'MyRuntime',
        },
    }

    async init() {
        // 필요한 초기화 로직
    }

    async executeFFI(
        code: string,
        args: FunctionInvokingParams,
    ): Promise<ValueType> {
        // 외부 코드 실행 로직
        // ...
        // 약속 런타임이 이해할 수 있는 ValueType 값을 반환해야 합니다.
    }
}

const session = new YaksokSession()
const myExtension = new MyExtension()

await session.extend(myExtension)

Extension 인터페이스 ​

  • manifest: 확장의 정보를 담는 객체입니다. ffiRunner.runtimeName은 약속 코드의 번역(런타임이름)에서 사용될 이름을 지정합니다.
  • init(): 확장이 세션에 등록될 때 호출되는 비동기 초기화 함수입니다.
  • executeFFI(code, args): 약속 코드에서 번역 문법이 사용될 때 호출되는 함수입니다.
    • code: *** 사이에 작성된 코드 본문입니다.
    • args: 약속에서 전달된 인자입니다. ValueType 객체의 Record입니다.
    • 반환값: executeFFI의 반환값은 다시 약속 런타임으로 돌아갑니다. 반환값은 약속 런타임이 이해할 수 있는 값(ValueType)이여야 합니다. 반환값이 약속 런타임의 값이 아니거나 undefined일 경우 오류가 발생합니다. 비동기 작업을 위해 Promise<ValueType>을 반환할 수도 있습니다.

예제 ​

약속이 아닌 언어의 코드를 호출해 값을 가져오기 ​

약속에서 지원하지 않는 랜덤 숫자 구하기 기능을 자바스크립트 eval을 이용해 구현해보겠습니다.

typescript
import {
    YaksokSession,
    Extension,
    ExtensionManifest,
    ValueType,
    FunctionInvokingParams,
    NumberValue,
} from '@dalbit-yaksok/core'

class EvalExtension implements Extension {
    manifest: ExtensionManifest = { ffiRunner: { runtimeName: 'JavaScript' } }
    async init() {}
    executeFFI(code: string, args: FunctionInvokingParams): ValueType {
        const runnableCode = this.buildCode(code, args)
        const resultInJS = eval(runnableCode)
        return new NumberValue(resultInJS)
    }
    private buildCode(code: string, params: FunctionInvokingParams) {
        const paramNames = Object.keys(params)
        const paramValues = Object.values(params).map((p) => p.value)
        return `((${paramNames.join(', ')}) => {${code}})(${paramValues.join(
            ', ',
        )})`
    }
}

const session = new YaksokSession()
await session.extend(new EvalExtension())

session.addModule(
    'main',
    `
번역(JavaScript), (A)와 (B) 사이 랜덤 수
***
    return Math.floor(Math.random() * (B - A) + A)
***

(1)와 (10) 사이 랜덤 수 보여주기
`,
)

await session.runModule('main')

다른 언어 / 런타임에 값을 전달하고 가져오기 ​

약속 코드에서 브라우저의 UserAgent를 가져오는 기능을 구현해보겠습니다.

typescript
import {
    YaksokSession,
    Extension,
    ExtensionManifest,
    ValueType,
    FunctionInvokingParams,
    StringValue,
} from '@dalbit-yaksok/core'

class BrowserExtension implements Extension {
    manifest: ExtensionManifest = { ffiRunner: { runtimeName: 'Browser' } }
    async init() {}
    executeFFI(code: string, args: FunctionInvokingParams): ValueType {
        const payload = JSON.parse(code)
        if (payload.action === 'getUserAgent') {
            return new StringValue(navigator.userAgent)
        }
        throw new Error('Unknown action')
    }
}

const session = new YaksokSession()
await session.extend(new BrowserExtension())

session.addModule(
    'main',
    `
    번역(Browser), 브라우저의 UserAgent 가져오기
    ***
    {
        "action": "getUserAgent"
    }
    ***

    "현재 실행중인 환경은 " + (브라우저의 UserAgent 가져오기) 보여주기
`,
)

await session.runModule('main')