3. 실행기: 코드 실행의 프로젝트 매니저
지금까지 우리는 코드를 잘게 쪼개고(토크나이저), 구조적인 나무로 엮었습니다(파서). 이제 여정의 마지막 단계, 바로 **실행기(Executer)**입니다.
실행기의 역할
- 실행 컨텍스트(
Scope
) 관리: 코드가 실행되는 동안 변수와 함수가 어디에 저장되고, 어떻게 찾아지는지를 결정하는Scope
를 생성하고 관리합니다. 이는 코드의 생명 주기와 유효 범위를 결정하는 가장 중요한 요소입니다. - AST 노드 실행 위임: 실행기 자체가 AST 노드를 순회하며 모든 로직을 처리하는 것이 아닙니다. 대신, 각 AST 노드(
Executable
인터페이스를 구현한)에게 자신의execute
메소드를 호출하여 실제 실행 로직을 위임합니다. 즉, 각 노드가 스스로를 어떻게 실행할지 알고 있는 구조입니다. - 제어 흐름 시그널 처리: 함수 반환(
return
), 반복문 탈출(break
)과 같이 일반적인 코드 흐름을 벗어나는 특별한 상황(Signal
)을 감지하고, 이를 적절한 에러로 변환하거나 상위 호출자에게 전달하여 프로그램의 제어 흐름을 관리합니다.
executer 함수 들여다보기
'달빛 약속'의 실행 로직은 주로 /core/executer/index.ts
파일의 executer
함수에서 시작됩니다. 이 함수는 다음과 같은 중요한 일을 합니다.
export async function executer<NodeType extends Executable>(
node: NodeType,
codeFile?: CodeFile,
): Promise<Scope> {
const scope =
codeFile?.ranScope ||
new Scope({
codeFile,
})
try {
await node.execute(scope) // 핵심: 노드에게 실행을 위임
return scope
} catch (e) {
if (e instanceof ReturnSignal) {
throw new CannotReturnOutsideFunctionError({
tokens: e.tokens,
})
}
if (e instanceof BreakSignal) {
throw new BreakNotInLoopError({
tokens: e.tokens,
})
}
throw e
}
}
위 코드에서 볼 수 있듯이, executer
함수는 특정 node
의 execute
메소드를 호출하는 것이 핵심입니다. 그리고 이 execute
메소드가 실행되는 동안 발생하는 ReturnSignal
이나 BreakSignal
같은 특별한 시그널(예외)을 캐치하여 적절한 에러로 변환합니다.
Scope: 실행 컨텍스트의 핵심
실행기의 가장 중요한 파트너는 바로 /core/executer/scope.ts
에 정의된 Scope
클래스입니다. Scope
는 변수와 함수를 저장하고 관리하는 공간이며, 다음과 같은 특징을 가집니다.
- 계층적 구조: 각
Scope
는 부모Scope
를 가질 수 있으며, 이를 통해 **스코프 체인(Scope Chain)**을 형성합니다. 변수나 함수를 찾을 때 현재 스코프에 없으면 부모 스코프로 거슬러 올라가며 재귀적으로 탐색합니다. 이는 '달빛 약속' 언어의 클로저와 변수 유효 범위를 결정하는 핵심 메커니즘입니다. - 변수 및 함수 관리:
setVariable
,getVariable
,addFunctionObject
,getFunctionObject
와 같은 메소드를 통해 변수와 함수를 안전하게 관리합니다. CodeFile
및Session
연결:Scope
는 자신이 속한CodeFile
과Session
에 대한 참조를 가질 수 있어, 실행 중인 코드의 위치와 전반적인 실행 환경에 대한 정보를 제공합니다.
Signals: 특별한 제어 흐름
/core/executer/signals.ts
에 정의된 Signal
클래스들은 '달빛 약속'의 특별한 제어 흐름을 처리하기 위한 메커니즘입니다. 예를 들어, 함수 내에서 돌려주기
(return) 명령을 만나면 ReturnSignal
이 발생하고, 반복문 내에서 탈출
(break) 명령을 만나면 BreakSignal
이 발생합니다. 이 시그널들은 JavaScript/TypeScript의 throw
문을 통해 예외(Exception)처럼 발생하며, executer
함수나 상위 호출자에서 try...catch
블록을 통해 이를 처리합니다. 이를 통해 일반적인 코드 흐름을 벗어나 프로그램의 흐름을 변경합니다.