실용적인 내용에서 잠시 벗어나, Ruby의 흥미로운 트릭 하나를 소개해 보려고 합니다.
Ruby는 프로그램을 실행하기 전에 반드시 코드를 파싱(parse)해야 합니다. 이 파서는 일종의 상태 머신(state machine)으로 동작하는데, 잘 알려지지 않은 명령줄 옵션 하나를 사용하면 상태 머신이 수행하는 모든 작업을 로그로 확인할 수 있습니다.
-y 플래그로 파서 동작 들여다보기
다음과 같은 간단한 예제 코드가 있다고 가정해 보겠습니다:
a = 1 + 2
이 코드를 -y 플래그와 함께 실행하면 다음과 같은 출력 결과를 얻을 수 있습니다:
$ ruby -y sample.rb
Starting parse
Entering state 0
Reducing stack by rule 1 (line 903):
-> $$ = nterm $@1 ()
Stack now 0
Entering state 2
Reading a token: Next token is token tIDENTIFIER ()
Shifting token tIDENTIFIER ()
Entering state 35
Reading a token: Next token is token '=' ()
Reducing stack by rule 509 (line 4417):
$1 = token tIDENTIFIER ()
-> $$ = nterm user_variable ()
Stack now 0 2
Entering state 113
Next token is token '=' ()
Reducing stack by rule 100 (line 1764):
$1 = nterm user_variable ()
-> $$ = nterm lhs ()
Stack now 0 2
...
140 more lines
파서는 어떤 방식으로 동작할까?
위 로그에서 우리가 보게 되는 것은 Ruby 파서가 파일 내의 각 토큰(token)을 순회하며 다음과 같은 작업을 반복적으로 수행하는 모습입니다:
- 토큰을 스택(stack)에 추가합니다.
- 스택의 내용을 규칙(rule) 목록과 비교합니다.
- 토큰이 규칙과 일치하면 상태 전이(state transition)를 수행합니다.
- 일치하지 않으면 스택에 토큰을 하나 더 추가한 뒤 다시 시도합니다.
이러한 모든 상태와 규칙은 parse.y 파일에 정의되어 있습니다. 이 파일은 bison이라는 파서 생성기(parser generator)에 의해 처리되어, 실제 파서인 C 코드를 생성하게 됩니다.