프로그래밍 언어의 핵심 개념
- 언어(Language): 특정 규칙을 따르는 문자열의 집합으로, 루비 코드를 정의합니다.
- 문법(Grammar): 언어를 정의하기 위한 유한한 규칙의 집합으로, 코드에 구조를 부여합니다.
if
문이나1+2*3
과 같은 표현식의 구조를 정의합니다. - 오토마톤(Automaton): 언어의 규칙을 인식하는 추상 기계입니다. 유한 오토마톤(Finite Automaton)은 자판기 예시처럼 상태 전이를 통해 입력을 처리하며, 스택을 가진 푸시다운 오토마톤(Pushdown Automaton)은 중첩된 구조(예: 클래스 정의)를 처리하는 데 사용됩니다.
- 파서(Parser): 토큰을 입력으로 받아 문법 규칙에 따라 구조를 분석하는 오토마톤입니다. LR 파서는 시프트(shift)와 리듀스(reduce) 동작을 통해 문법 트리를 구성하며, 다음 토큰(룩어헤드 세트)을 통해 적절한 규칙을 선택합니다.
루비 파서의 개행 처리 내부 구현
- 렉서(Lexer): 입력 문자열을 의미 있는 토큰으로 분할하는 컴포넌트입니다. 루비의 렉서는
lex_state
라는 내부 상태에 따라 개행을 토큰으로 반환하거나 무시합니다. lex_state
의 역할:EXPR_CLASS
,EXPR_DOT
,EXPR_BEG
상태에서는 개행을 무시하여 코드가 한 줄처럼 처리됩니다 (예:class A
,obj. method
,1+ 2
).EXPR_END
상태에서는 개행을 토큰으로 반환하여 문을 분리합니다.- 특정 상황(예: 메서드 인자 목록의 마지막 필수 키워드 인자)에서는 예외적으로 개행 토큰을 강제 반환하여 의도치 않은 파싱을 방지합니다.
lex_state
의 복잡성:lex_state
는 13가지 플래그와 그 조합으로 이루어진 복잡한 구조로, 루비 커미터들조차 “개발자의 이성을 좀먹는 상태 전이”, “파서의 심연에 숨어 있는 혼돈의 문”이라고 표현할 정도로 이해하고 관리하기 어렵습니다. 이는 파서와 렉서가 독립적이지 않고 상호작용하며, 하나의lex_state
가 여러 문법적 맥락에서 사용될 수 있기 때문입니다.
가설 검증 및 예외 사례
- 모델링 접근 방식:
lex_state
의 혼돈을 해결하기 위해 파서와lex_state
를 하나의 통합된 오토마톤으로 모델링하는 시도가 이루어졌습니다. Yacc 문법을 확장하여lex_state
의 전이 규칙을 명시적으로 기술함으로써, 기계적으로 각 토큰 및 문법 규칙에 따른lex_state
의 동작을 파악할 수 있게 되었습니다. - 가설에 대한 반례:
- 문법적으로 개행이 허용되나 렉서가 무시하는 경우:
- 엔드리스 레인지 (
1.. 2
):..
뒤EXPR_BEG
상태로 전환되어 개행이 무시되고1..2
로 파싱됩니다. - 익명 인자 (
foo(* args)
):*
뒤EXPR_BEG
상태로 전환되어 개행이 무시되고foo(*args)
로 파싱됩니다.**
,&
도 동일합니다.
- 엔드리스 레인지 (
- 문법적으로 개행이 허용되지 않으나 렉서가 개행 토큰을 반환하는 경우:
- 전역 변수 별칭 (
alias $foo $bar
):alias $foo
뒤EXPR_END
상태에서 개행 토큰이 반환되나 문법이 이를 허용하지 않아 구문 오류가 발생합니다. BEGIN { ... }
,END { ... }
블록 (BEGIN {
): 키워드와{
사이에 개행이 있을 경우 구문 오류가 발생합니다.
- 전역 변수 별칭 (
- 문법적으로 개행이 허용되나 렉서가 무시하는 경우:
- 이러한 예외 사례들은 AKR의 가설이 대부분의 경우에 유효하지만, 특정 엣지 케이스에서는 복잡한
lex_state
동작으로 인해 원칙이 깨질 수 있음을 보여줍니다.