CFG는 Context free language를 나타내기 위한 notation이다.
재귀적 형태를 나타내는데 용이하다.
CFG의 구성
- Terminal: 대체될 수 없는 기본 symbol들을 의미한다. 보통, 토큰 이름 == terminal이 된다.
- Non-terminal: 다른 non-terminal이나 terminal로 대체될 수 있는 syntactic variable이다.
- Start symbol: 하나의 non-terminal로, 대개 첫번째 rule의 non-terminal이다.
- Production(→): Non-terminal이 대체될 때의 규칙이다.
CFG 규칙
(통상)
- Non-terminal은 대문자로, terminal은 소문자로 작성한다.
- Non-terminal, terminal, 𝜖을 포함한 일련의 문자열은 α,β,ω로 작성한다.
e.g.
Terminal = {+, id, *, (, )} 일 때,
e.g., id+id,id∗id,(id),id+id∗id,(id+id)∗id 등을 나타내는 CFG는 다음과 같이 나타내질 수 있다.
E→E+E∣E∗E∣(E)∣id
Derivation
- derivation(⇒)은 replacement를 나타낸다.
- ⇒∗는 0번 이상의 derivation을 나타낸다.
- ⇒lm 은 가장 왼쪽의 non-terminal부터 대체한다.
- ⇒rm 은 가장 오른쪽의 non-terminal부터 대체한다.
예를 들어, E→E+E∣E∗E∣(E)∣id 에 대해 id∗id+id 를 만들어보자
- left-most derive
E⇒lmE+E⇒lmE∗E+E⇒lmid∗E+E⇒lmid∗id+E⇒lmid∗id+id
E⇒lm∗id∗id+id
- right-most derive
E⇒rmE+E⇒rmE+id⇒rmE∗E+id⇒rmE∗id+id⇒rmid∗id+id
E⇒rm∗id∗id+id
Token validation test
- CFG G의 start symbol A에 대해, A⇒∗α라면 α는 G의 sentinel form이다.
- sentinel form α가 terminal으로만 이루어졌다면 α는 G의 sentence form이다.
- L(G)는 CFG G의 language이다. (L(G)={α∣α is sentence of G})
(token 집합 등의) 입력 문자열이 L(G)에 포함된다면, 입력이 G에 대해 valid하다고 할 수 있다.
좋은 CFG의 조건
- 모호성이 없다
- left recursion이 없다
- 각각의 non-terminal에 대해, 특정 input symbol로 부터 단 하나의 production만 선택 가능하다.
모호성 제거
E→E+E∣E∗E∣id 에 대해, id*id+id를 나타내는데 다음 두가지 모두 가능하다.
위와 같이 여러 tree로 나타낼 수 있는 CFG는 비효율적이다.
*가 +보다 더 높은 우선순위를 가진다는 것을 명시하면, 모호성이 제거된다.
E→T+E∣T
T→F∗T∣F
F→(E)∣id
Left-recursion 제거
A⇒+Aα (A: nonterminal, α: non-terminal&terminal로 이루어진 모둔 sequence) 를 포함한 문법을 left recursive grammar라고 한다.
e.g. S→Aa∣b,A→Sb
S⇒Aa⇒Sba
S=A, ba=α 로 보면, left recursion 형태임을 알 수 있다.
이를 해결하기 위해, left-recursion을 S→bA,A→aA∣ϵ 형태의 right recursion으로 변환할 수 있다.
e.g. S→Sα1∣Sα2∣…∣Sαm∣β1∣β2∣…∣βn
- 새로운 non-terminal A를 만들고, 모든 αi에 대해 production rule αiA를, 그리고 ϵ을 추가한다.
A→α1A∣α2A∣…∣αnA∣ϵ
- non-terminal S에 대해, 모든 βi에 대한 βiA production rule을 추가한다.
S→β1A∣β2A∣…∣βnA
단일 선택 보장: Left factoring
E→T+E∣T,T→F∗T∣F,F→(E)∣id
의 경우, 첫번째에서 T가 중복, 두번째에서 F가 중복되는 것을 볼 수 있다.
이 모호성을 제거하기 위해, left factoring을 적용할 수 있다.
- non-terminal A에 대해, 가장 긴 공통 접두사 α를 찾는다.
E에 대해, α=T
- A→αβ 형태의 모든 production을 A→αA′으로 대체한다.
E→TE′
- 2에서 대체된 production에 대해, A′→β 형태의 새로운 production을 추가한다.
E′=+E∣ϵ
- 모든 non-terminal에 대해 더이상 공통 접두사가 없을때까지 1-3을 반복한다.
E→TE′,E′=+E∣ϵ
T→FT′,T′→∗T∣ϵ
F→(E)∣id
AST(Abstract Syntax Tree)

다음 parse tree에서, 할 수 있는 추상화 작업은 다음과 같다
-
Single-successor node

자식을 하나만 가지는 노드를 자식 노드 하나로 합친다.
-
문법적 디테일 (괄호, 콤마 등)

parse tree가 이미 문법적 디테일을 설명해주기 때문에, 제거해도 된다.
-
연산자를 자식으로 가지는 non-terminal

AST Construction
문법적 production과 관련된 액션인 Semantic action을 만든다.
e.g. CFG G : E→E+E∣E∗E∣(E)∣id
| Production | Semantic action |
|---|
| E→E1+E2 | E.node = new Node('+', E1.node, E2.node) |
| E→E1∗E2 | E.node = new Node('*', E1.node, E2.node) |
| E→(E1) | E.node = E1.node |
| E→id | E.node = new Leaf(id, id.value) |