번역: 약속을 외부 환경과 연결하기
다른 언어와 런타임의 도움을 받는다면 약속 언어에서 제공하지 않는 기능도 사용할 수 있습니다. 이 때 사용하는 방법이 번역입니다. 예를 들어, 사용자 브라우저에서 제공하는 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')