티스토리 뷰

"컴파일러는 우리가 작성한 코드를 어떻게 '언어'로 인식하고, 문법이 맞았는지 틀렸는지 판단할 수 있을까?"

 

지난 주에는 컴파일러의 전체적인 구조를 훑어봤다. 이번에는 구문 분석(Parsing)의 이론적 뼈대가 되는 형식 문법(Formal Grammar)에 대해 알아볼 것이다.

 

 

왜 갑자기 '수학'부터 시작할까?

컴파일러가 다루는 프로그래밍 언어는 우리가 사용하는 자연어와 근본적인 차이가 있다. 그것은 바로 모호성(Ambiguity)이 없다는 점이다. 모든 코드는 단 하나의 의미로만 해석되어야 한다. 이러한 엄밀함과 명확성을 보장하는 도구가 수학이며, 컴퓨터 과학자들은 프로그래밍 언어를 정의하기 위해 수학적 도구들을 도입했다. 그 첫 번째가 바로 집합(Set)이다.

 
  • 집합 (Set): 원소들의 모임이며, 소속성 외에는 어떤 구조도 가지지 않는다. 언어를 구성하는 가장 기본적인 재료들의 모음으로, 특정 언어가 사용할 기호의 범위를 약속하는 것이다. 이를 알파벳(Alphabet)이라 칭한다.
  • 문자열 (String): 알파벳을 구성하는 기호가 0개 또는 1개 이상 유한하게 나열된 것이다. ab와 ba는 순서가 다르므로 다른 문자열로 취급된다.
  • 언어 (Language): 형식 언어란 특정 알파벳에서 얻은 기호들로 구성된 문자열의 집합이다. 수많은 문자열 조합 중에서, 특정 문법(Grammar) 규칙을 만족하는 유효한 문자열들의 집합을 의미한다.

 

문법(Grammar)의 해부: 약속은 어떻게 만들어지는가?

컴파일러가 코드를 이해하는 규칙, 즉 문법은 4가지 요소로 구성된 시스템으로 정의된다. 이를  G = (V, Σ, P, S)로 표현한다.

 

  • V (Non-terminals, 비단말 기호): 문법 구조를 나타내는 기호이다. <expression>, <statement> 와 같이 다른 것으로 대체되어야 할 중간 단계의 개념을 나타낸다.
  • Σ (Terminals, 단말 기호): 실제 언어의 기본 기호들이다. 키워드, 연산자, 식별자 등이 여기에 속하며, 더 이상 다른 것으로 바뀔 수 없는 최종 단위이다.
  • P (Production Rules, 생성 규칙): 비단말 기호를 단말 기호나 다른 비단말 기호로 치환하는 규칙이다.
    ::= 또는 → 기호를 사용하여 "왼쪽은 오른쪽으로 바뀔 수 있다"고 정의한다.
    • 예: <expr> → <expr> + <term>
    • 해석: "하나의 <expr>는, 또 다른 <expr>와 + 기호, 그리고 하나의 <term>의 조합으로 만들어질 수 있다."
  • S (Start Symbol, 시작 기호): 파싱을 시작하는 최상위 비단말 기호이다. 모든 유효한 프로그램은 이 시작 기호로부터 생성 규칙을 통해 파생되어야 한다.

 

문장 생성 과정: 유도(Derive)와 문장(Sentence)

문법의 네 가지 요소를 가지고 실제 문장을 만드는 과정을 유도(Derive)라고 한다. 이는 시작 기호(S)에서 출발하여 생성 규칙(P)을 반복적으로 적용하며 비단말 기호(V)를 모두 단말 기호(Σ)로 바꾸는 과정이다.

 
 

 

<규칙 (P)>

 
  • <expr> → <expr> + <term> | <term>
  • <term> → <term> * <factor> | <factor>
  • <factor> → (<expr>) | id

<id + id * id 유도 과정>

  1. <expr>
  2. => <expr> + <term>
  3. => <term> + <term>
  4. => <factor> + <term>
  5. => id + <term>
  6. => id + <term> * <factor>
  7. => id + <factor> * <factor>
  8. => id + id * <factor>
  9. => id + id * id

최종적으로 만들어진 단말 기호의 나열을 문장(Sentence)이라고 한다. 유도 과정 중 비단말 기호가 남아있는 중간 결과물은 문장 형태(Sentential Form)라고 한다.

 
 

촘스키 계층: 문법의 '파워 레벨'을 나누다 (Chomsky Hierarchy)

언어학자 노엄 촘스키는 생성 규칙(P)의 제약 조건에 따라 문법을 4개의 계층으로 분류했다. 이것이 촘스키 계층(Chomsky Hierarchy)**이다. 규칙에 제약이 많을수록 표현력은 약하지만 기계가 해석하기는 더 쉽다.

 
 
 

Type 3: 정규 문법 (Regular Grammar)

  • 규칙 형태: A → a 또는 A → aB 와 같이 오른쪽 선형이거나, A → Ba 와 같이 왼쪽 선형 중 한 가지 형태만 사용한다.
  • 특징: 가장 제약이 강하며 단순한 패턴을 정의한다.
  • 예시 언어: 정규 표현식 (a)*b 와 동일한 S → aS | b 문법은 b, ab, aab 등의 문자열을 생성한다.
  • 해석 기계: 유한 오토마타 (Finite Automata, FA).

 

Type 2: 문맥 자유 문법 (Context-Free Grammar, CFG)

  • 규칙 형태: A → α (규칙의 왼쪽에 단 하나의 비단말 기호만 와야 한다).
  • 특징: 규칙 적용 시 주변 문맥을 고려하지 않기 때문에 '문맥 자유'라고 부른다. 대부분의 프로그래밍 언어 문법이 여기에 속한다.
  • 예시 언어: { aⁿbⁿ | n ≥ 1 } (a와 b의 개수가 같은 언어), 괄호 짝 맞추기, 중첩된 구문 등을 표현할 수 있다.
  • 해석 기계: 푸시다운 오토마타 (Pushdown Automata, PDA).

 

Type 1: 문맥 의존 문법 (Context-Sensitive Grammar, CSG)

  • 규칙 형태: αAβ → αγβ (A가 γ로 치환되려면 α와 β라는 주변 문맥이 필요하다). 또한, 생성 규칙 적용 후 문자열의 길이가 줄어들지 않는다 (비축약성).
  • 특징: 문맥에 따라 규칙 적용이 달라져 CFG보다 강력한 표현력을 가진다.
  • 예시 언어: { aⁿbⁿcⁿ | n ≥ 1 } (a, b, c 세 기호의 개수가 모두 같은 언어). CFG는 두 종류의 기호 개수까지만 맞출 수 있다.
  • 해석 기계: 선형 유계 오토마타 (Linear Bounded Automata, LBA).

Type 0: 무제약 문법 (Unrestricted Grammar)

  • 규칙 형태: α → β (규칙의 좌변에 최소 한 개의 비단말이 포함되기만 하면 다른 제약이 거의 없다). 문자열 길이가 줄어드는 축약 규칙도 허용된다.
  • 특징: 가장 표현력이 강하며, 튜링 기계가 인식 가능한 모든 언어와 일치한다.
  • 해석 기계: 튜링 머신 (Turing Machine).
 

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2026/07   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함