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)번 핀을 디지털로 설정하기

외부 런타임을 연결하기

runFFI 함수

약속 코드에서 외부 런타임을 호출하고 값을 전달하기 위해서는 RuntimeConfigrunFFI 함수를 사용합니다.

typescript
import { yaksok } from '@yaksok-ts/core'

yaksok(code, {
    runFFI: (runtime, code, params) => {
        // Do something: 번역할 코드를 실행합니다
    },
})

파라미터

runFFI의 인자는 다음과 같습니다:

  • <string> runtime: FFI에서 사용할 런타임입니다. 번역(Python)으로 호출했다면 Python(string)이 됩니다.
  • <string> code: 번역할 본문입니다. *** 사이의 문자열이 전달됩니다.
  • <FunctionParams> params: 번역을 호출할 때 전달된 인자입니다. FunctionParams를 참조하세요.

params는 다음과 같이 Record<string, ValueTypes> 타입으로 정의됩니다.

typescript
{
    핀_번호: NumberValue { value: 10 }
}

FunctionParams 객체의 값은 자바스크립트에서 사용하는 데이터타입이 아닙니다

FunctionParams 객체의 값은 약속 런타임이 이해할 수 있는 값(ValueTypes)입니다. 자바스크립트 데이터타입으로 오인하지 않도록 주의하세요.

반환값

runFFI 함수의 반환값은 다시 약속 런타임으로 돌아갑니다. 반환값은 약속 런타임이 이해할 수 있는 값(ValueTypes)이여야 합니다. 반환값이 약속 런타임의 값이 아니거나 undefined일 경우 오류가 발생합니다.

예제

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

약속에서 지원하지 않는 랜덤 숫자 구하기 기능을 자바스크립트에서 빌려와 사용해보겠습니다. Eval과 FFI를 사용해 구현합니다.

typescript
import { yaksok, FunctionParams, NumberValue } from '@yaksok-ts/core'

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

(1)와 (10) 사이 랜덤 수 보여주기
`,
    {
        runFFI: (runtime, code, params) => {
            const runnableCode = buildCodeFromCodeAndParams(code, params)
            const resultInJS = eval(runnableCode)

            return new NumberValue(resultInJS)
        },
    },
)

function buildCodeFromCodeAndParams(code: string, params: FunctionParams) {
    const paramNames = Object.keys(params)
    const paramsInJS = Object.fromEntries(
        Object.entries(params).map(([key, value]) => [key, value.value]),
    )

    return `((${paramNames.join(', ')}) => {${code}})(${Object.values(
        paramsInJS,
    ).join(', ')})`
}

쉬운 이해를 위해 일부 코드가 생략되었습니다

runFFI 함수에서 runtime, buildCodeFromCodeAndPrams의 반환값 타입에 따른 예외처리가 생략되었습니다. 실제 구현에서는 이를 반드시 추가해주세요.

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

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

typescript
import { yaksok, FunctionParams, StringValue } from '@yaksok-ts/core'

function runFFI(runtime: string, code: string, params: FunctionParams) {
    const payload = JSON.parse(code)

    if (payload.action === 'getUserAgent') {
        return new StringValue(navigator.userAgent)
    }
}

const code = `
번역(JavaScript), 브라우저의 UserAgent 가져오기
***
{
    "action": "getUserAgent"
}
***

브라우저의 UserAgent 가져오기 보여주기
`

yaksok(code, { runFFI })

쉬운 이해를 위해 일부 코드가 생략되었습니다

runFFI 함수에서 payload.action, runtime에 따른 예외처리가 생략되었습니다. 실제 구현에서는 이를 반드시 추가해주세요.