940 likes | 1.35k Views
COMPILER. Chapter 7. LL 구문 분석. 김 영 균 ygkim@cespc1.kumoh.ac.kr. Contents. 1. 결정적 구문 분석 2. Recursive-descent 파서 3. Predictive 파서 4. Predictive 파싱 테이블의 구성 5. Strong LL(k) 문법과 LL(k) 문법. 7. 구문 분석. - Top-down 구문분석이란 , 시작 심벌로부터 유도과정을 거쳐 주어진 스트링과 같은 문장을 생성하기 위한 방법으로 생각 .
E N D
COMPILER Chapter 7. LL구문 분석 김 영 균 ygkim@cespc1.kumoh.ac.kr
Contents 1. 결정적 구문 분석 2. Recursive-descent 파서 3. Predictive 파서 4. Predictive 파싱 테이블의 구성 5. Strong LL(k)문법과 LL(k)문법
7. 구문 분석 - Top-down 구문분석이란, 시작 심벌로부터 유도과정을 거쳐 주어진 스트링과 같은 문장을 생성하기 위한 방법으로 생각. - 입력 심벌을 반복해서 검조(scanning)하면서, 구문 분석을 하는 일반적인 top-down방식은 6장에서 이미 설명. - 일반적인 top-down방식은 주어진 스트링을 파싱하는데 많은 시간을 필요로 함. - 실제적인 컴파일러의 파싱 알고리즘으로 사용하기에는 부적당. 따라서, backtracking을 하지 않고 결정적으로 파싱할 수 있는 방법이 요구됨. 이와 같은 결정적 파싱 방법을 LL파싱이라 부름.
7. 구문 분석 - LL은 입력 스트링을 왼쪽에서 오른쪽으로 읽어 가며 (left to right scanning) 좌파스(left parse)를 생성하기 때문에 붙여진 이름. - LL방법은 주어진 스트링과 같은 문장을 생성하기 위하여 현재의 입력 심벌을 보고,적용될 생성 규칙을 결정적으로 선택하여 유도 한다. - 현재의 입력 심벌과 생성된 terminal심벌이 같지 않으면, 주어진 스트링을 틀린 문장으로 간주하는 방법. 이와 같이 결정적으로 파싱하기 위해서는 문법이 어떤 특정한 조건을 만족해야 하는데 이 조건을 LL 조건(LL condition)이라고 부름.
7. 구문 분석 - LL 조건을 형식적으로 정의하고, LL조건을 만족하는 문법을 결정적으로 파싱할 수 있는 recursive-descent파서와predictive 파서에 관한 내용을 다룸. - LL은 이제까지 본 심벌과현재 보고 있는 심벌에 따라 파싱 행동을 결정하고 strong LL은 현재 보고 있는 심벌에 따라, 파싱 행동을 결정한다. - 그런데 현재 보고 있는 심벌이 한 개인 경우, 즉 LL(1)과 strong LL(1)이 같기 때문에 LL조건이라 부름.
7.1 결정적 구문 분석 7.1 결정적 구문 분석 - Top-down방법으로 구문 분석을 할 때, backtracking을 하지 않기 위해서는6장에서 설명한 대로 문법이 left-recursion을 갖지 않아야 하며, left-factoring이 되어야 하는 것 이외에 몇가지 조건을 더 만족해야 한다. - 왜냐하면, 문장 형태에서 nonterminal에 대한 유도를 할 때, 생성 규칙이 여러 개가 있을 수 있으므로 구문 분석기가 어느 생성 규칙을 적용해야 할 지 알 수 없기 때문이다. - 생성규칙을 결정적으로 선택하기 위한 조건을 형식적으로 전개하기 위해 FIRST, FOLLOW등 몇 가지 용어를 정의하고 주어진 생성 규칙으로부터 계산하는 알고리즘을 살펴보기로 한다.
7.1 결정적 구문 분석 7.1.1 FIRST - 문법 G=(VN, VT, P, S)가 context-free문법이고 AVN, aVT일 때, nonterminal A에 대한 FIRST는 다음과 같이 정의 된다. [정의 7.1] FIRST(A) = { aVT{ } | A* a, V* }. 즉, nonterminal A로부터 유도되어 첫번째로 나타날 수 있는 terminal의 집합을 의미 한다.
7.1 결정적 구문 분석 [예 1] 다음 생성 규칙에서 각 nonterminal에 대한 FIRST를 구해 보자. A aB | B B bC | C C c 1. A aB A B bC A B C c FIRST(A) = { a, b, c }. 2. B bC B C c FIRST(B) = { b, c }.
7.1 결정적 구문 분석 3. C c FIRST(C) = { c }. [정의 7.2] nonterminal A가 을 유도할 수 있으면A를 nullable하다고 부른다. 즉, A* 이면 nullable하다. [정의 7.3] 두 개의 집합에 대한 연산자인 ring sum은 다음과 같이 정의되며 기호 를 사용 한다. 1. if A then A B = A 2. if A then A B = ( A - { } ) B. 다시 말해서, ring sum의 의미는 첫번째 피연산자의 집합이 을 갖지 않으면 그 자신이 되며, 을 포함하고 있으면 을 제외한 첫번째 피연산자와 두번째 피연산자를 합집합한 것이 된다.
7.1 결정적 구문 분석 [예 2] { a, b, c } { c, d } = { a, b, c }. { a, b, c, } { c, d } = { a, b, c, d }. { a, b, } { c, d, } = { a, b, c, d, }. 이런 의미로 ring sum을 정의하면, 스트링에 대한 FIRST도 다음과 같이 자연스럽게 확장할 수 있다. [정의 7.4] FIRST(A1A2 ... An) = FIRST(A1) FIRST(A2) ... FIRST(An) 스트링의 FIRST는 왼쪽 심벌로부터 유도되는terminal심벌을 찾는 과정으로 생각할 수 있는데, 만일 첫번째 심벌이 nullable 하면, 그 다음 심벌의 FIRST도 속한다. 이렇게 해서 nullable이 아닐 때까지의 심벌의 FIRST를 합집합한 것이 스트링의 FIRST가 된다.
7.1 결정적 구문 분석 - 각 생성 규칙의 형태에 따라 FIRST(X)를 계산하는 방법은 다음과 같다. 1. X가 terminal이면, X의 FIRST가 자기 자신이 된다. FIRST(X) = { X } if X VT. 2. X a의 생성 규칙이 존재하면 a가 FIRST(X)에 포함된다. FIRST(X) = FIRST(X) {a} if X a P and aVT. 또한, X가 -생성 규칙을 가지면 X의 FIRST에 이 포함 된다. FIRST(X) = FIRST(X) {} if X P. 3. X Y1Y2 ... Yk인 생성 규칙이 있을 때, FIRST(X) = FIRST(X) FIRST(Y1Y2 ... Yk)이다.
7.1 결정적 구문 분석 - 이제 문법 G=(VN, VT, P, S)가 주어졌을 때 각 nonterminal에 대한 FIRST를 구하는 알고리즘을 기술해 보자. [알고리즘 7.1] 각 nonterminal의 FIRST를 구하는 방법: Algorithm Compute_FIRST; begin for each A VN do FIRST(A):= { } ; for A P do if = a, where a VT then FIRST(A) := FIRST(A) { a } P := P - {A} else if = then FIRST(A) := FIRST(A) {}; P:=P- { A } fi fi end for;
7.1 결정적 구문 분석 repeat for A Y1Y2 ... Yk P do FIRST(A):=FIRST(A)( FIRST(Y1)FIRST(Y2) ... FIRST(Yk)) end for until no change end.
7.1 결정적 구문 분석 - 초기에, 모든 nonterminal의 FIRST는 공집합이 된다. 그리고 rhs에 첫번째로 terminal이 나오는 생성 규칙 (-생성 규칙 포함)에서 FIRST를 구한 후, 이 형태의 생성 규칙은 다시 고려할 필요가 없기 때문에 제거 한다. 남은 생성 규칙의 형태는 모두 nonterminal로 시작하므로 ring sum연산을 이용하여, 모든 nonterminal의 FIRST가 변하지 않을 때까지 반복한다. 일반적으로, A-생성 규칙이 A 1 | 2 | ... | nP와 같은 형태일 때, 다음과 같이 된다. FIRST(A) = FIRST(1) FIRST(2) ... FIRST(n)
7.1 결정적 구문 분석 [예 3] 다음 문법의 FIRST를 계산해 보자. S ABe A dB | aS | c B AS | b 먼저 생성 규칙 형태에 따라 각 nonterminal은 다음과 같은 초기값을 갖는다. FIRST(S) = { } FIRST(A) = { a, c, d} FIRST(B) = { b } 따라서 FIRST(S) = FIRST(S) ( FIRST(A)FIRST(B)FIRST(e) ) = { } { a, c, d } = { a, c, d }
7.1 결정적 구문 분석 FIRST(B) = FIRST(B) ( FIRST(A)FIRST(S) ) = { b } { a, c, d } = { a, b, c, d } 가 된다. - FIRST집합에 더 추가되는 terminal이 존재하므로 다시 반복하면, FIRST(S) = { a, c, d } FIRST(B) = { a, b, c, d } 가 되어 변하지 않으므로 알고리즘은 끝나게 된다. 그러므로, 각 nonterminal의 FIRST는 다음과 같다. FIRST(S) = { a, c, d } FIRST(A) = { a, c, d } FIRST(B) = { a, b, c, d }
7.1 결정적 구문 분석 [예 4] 다음 문법의 FIRST를 구하자. E TE’ E’ +TE’ | T FT’ T’ *FT’ | F ( E ) | id 알고리즘에 따라 구하면 다음과 같다. FIRST(E) = FIRST(T) = FIRTST(F) = { (, id } FIRST(E’) = { + , } FIRST(T’) = { * , }
7.1 결정적 구문 분석 7.1.2 FOLLOW - Nonterminal A가 nullable하면 FIRST(A)에 이 속하게 되지만 FIRST를 가지고는 생성 규칙을 결정적으로 선택할 수 없다. 즉, nonterminal A다음에 나오는 심벌에 따라 어느 생성 규칙 으로 유도할 것인가를 결정하게 된다. 따라서, -생성 규칙을 갖는 문법에서는 nonterminal다음에 나오는 terminal심벌이 의미를 갖게 되는데 이것을 FOLLOW라 부르며, 다음과 같이 정의 된다. [정의 7.5] FOLLOW(A)={aVT {$} | S * Aa, ,V* }. 여기서, $는 입력 스트링의 끝을 표기하는 마커 심벌(marker symbol)을 나타낸다.
7.1 결정적 구문 분석 - Nonterminal A의 FOLLOW란 시작 심벌로부터 유도될 수 있는 모든 문장 형태에서 A다음에 나오는 terminal심벌의 집합이다. - 따라서, FOLLOW를 계산하기 위해서는 모든 문장 형태를 고려해야 하나 문장 형태는 생성 규칙의 rhs들로 이루어져 있다. 그러므로, 생성 규칙의 rhs를 이용하여FOLLOW를 구할 수 있다. 각 생성 규칙의 형태에 따른 FOLLOW를 계산하는 방법은 다음과 같다. 1. 시작 심벌은 $를 초기값으로 갖는다. FOLLOW(S) = { $ }. 2. 생성 규칙의 형태가 AB, 일때, FIRST()에서 을 제외한 terminal심벌을 B의 FOLLOW에 추가 한다.
7.1 결정적 구문 분석 1. 시작 심벌은 $를 초기값으로 갖는다. FOLLOW(S) = { $ }. 2. 생성 규칙의 형태가 AB, 일때, FIRST()에서 을 제외한 terminal심벌을 B의 FOLLOW에 추가 한다. If AB, then FOLLOW(B) = FOLLOW(B) ( FIRST() - {} ) 3. AB이거나, AB에서 FIRST()에서 이 속하는 경우 (즉, * ), A의 FOLLOW전체를 B의 FOLLOW에 추가한다. If ABP or (AB and * ) then FOLLOW(B) = FOLLOW(B) FOLLOW(A).
7.1 결정적 구문 분석 - 계산 과정 3번의 의미는 생성 규칙 형태가 AB인 경우, 가 이거나 또는 을 유도할 수 있으면, A의 FOLLOW전체를 B의 FOLLOW에 넣는다는 것이다. 왜냐하면, 임의의 문장 형태에서 A다음에 나올 수 있는 심벌은 모두 B다음에 나올 수 있기 때문이다. 즉, S * 1A2 1B2 * 1B2 이다. 따라서 FOLLOW속성으로 인하여 A B, B A와 같은 형태의 생성 규칙을 갖고 있으면, FOLLOW(A)=FOLLOW(B)가 된다.
7.1 결정적 구문 분석 왜냐하면, 첫번째 형태의 생성 규칙으로부터 FOLLOW(A) FOLLOW(B)이고, 두번째 형태의 생성 규칙으로부터 FOLLOW(A) FOLLOW(B)가 되기 때문이다. 이제 문법이 주어졌을 때, 각 nonterminal에 대한 FOLLOW를 구하는 방법을 생각해 보자. 먼저 문법으로부터 nullable심벌을 구하고,이것을 이용하여 [알고리즘 7.1]에 따라 FIRST를 계산 한다. 그리고 FIRST를 이용하여 FOLLOW를 구한다. FIRST를 이용하여 계산하는 방법은 다음과 같다.
7.1 결정적 구문 분석 왜냐하면, 첫번째 형태의 생성 규칙으로부터 FOLLOW(A) FOLLOW(B)이고, 두번째 형태의 생성 규칙으로부터 FOLLOW(A) FOLLOW(B)가 되기 때문이다. 이제 문법이 주어졌을 때, 각 nonterminal에 대한 FOLLOW를 구하는 방법을 생각해 보자. 먼저 문법으로부터 nullable심벌을 구하고, 이것을 이용하여 [알고리즘 7.1]에 따라 FIRST를 계산 한다. 그리고 FIRST를 이용하여 FOLLOW를 구한다. FIRST를 이용하여 계산하는 방법은 다음과 같다.
7.1 결정적 구문 분석 [ 알고리즘 7.2 ] 각 nonterminal에 대한 FOLLOW를 구하는 방법: Algorithm Compute_FOLLOW; begin for each AVN do FOLLOW(A) := { }; FOLLOW(S):={$}; for ABP, where do FOLLOW(B) := FOLLOW(B) ( FIRST() - {}); repeat for each AP do if = B or ( = B and * ) then FOLLOW(B) := FOLLOW(B) FOLLOW(A) fi end for until no change end.
7.1 결정적 구문 분석 - 생성 규칙의 형태가 AB1 ... Bn-1Bn P일 때, A의 FOLLOW 전체를 Bn의 FOLLOW에 추가 한다. 또한, Bn이 nullable하면 A의 FOLLOW전체를 Bn-1의 FOLLOW에 추가 한다. 같은 방법으로 nullable하지 않을 때까지 계속 한다. [예5] [예4]에서 주어진 문법의 FOLLOW를 구하자. 처음에 생성 규칙 형태에 따라 다음과 같은 초기값을 갖는다. FOLLOW(E) = { $ } FIRST( ) ) = { $, ) } FOLLOW(T) = ( FIRST(E’) - { } ) = { + } FOLLOW(F) = ( FIRST(T’) - { } ) = { * } 그리고 E TE’에서 E의 FOLLOW전체를 E’에 넣고 또 E’이 nullable하기 때문에 E의 FOLLOW전체를 T의 FOLLOW에 포함 시킨다.
7.1 결정적 구문 분석 - 이렇게 해서 더 이상 FOLLOW집합이 변하지 않을 때까지 계속하면 다음과 같은 FOLLOW집합이 구해진다. FOLLOW(E) = FOLLOW(E’) = { ), $ } FOLLOW(T) = FOLLOW(T’) = { +, ), $ } FOLLOW(F) = { + , * , ) , $}
7.1 결정적 구문 분석 7.1.3 LL조건 LL조건은 유도 과정에서 나타난 문장 형태에서 nonterminal을 대치하기 위한 생성 규칙을 결정적으로 선택하기 위한 조건이다. 즉, 주어진 입력과 생성된 문장 형태가 다음과 같을 때(a1부터 ai-1까지는 매칭 되었고, nonterminal X를 대치해야 하는 상황임.) 문장형태 : a1a2 ... ai-1 X 입력 스트링 : a1a2 ... a i-1aiai+1 ... An 그리고 X 1 | 2 | 3 | ... | n P일때, 현재의 입력 심벌 ai를 보고 X-생성 규칙 중에 한 개를 결정적으로 선택할 수 있는 조건이다.
7.1 결정적 구문 분석 - LL조건은 FIRST와 FOLLOW를 이용하여 다음과 같이 정의할 수 있다. [정의 7.7] 임의의 생성 규칙 A | P에 대하여, 다음 조건을 만족해야 한다. 1. FIRST() FIRST() = ; 2. If FIRST() then FOLLOW(A) FIRST() = . 생성 규칙의 FIRST가 서로 다르다는 것은 유도하는 스트링이 다르다는 것이므로 문장 형태에서 적용할 생성 규칙을 결정적으로 선택할 수 있다. 그러나, FIRST가 같으면 유도하는 스트링이 같아서 생성 규칙 중 어느 것을 적용해야 올바른 것인지 알 수 없게 된다.
7.1 결정적 구문 분석 - 또한 생성 규칙이 을 유도할 수 있으면FOLLOW심벌에 대하여 그 생성 규칙을 선택하게 된다. 따라서 FOLLOW심벌과도 서로 분리된 집합이어야 한다. [예 6] 다음 문법을 가지고 스트링 i[e]e를 결정적으로 구문 분석할 수 있는지 알아보자. A iB e B SB | S [eC] | .i C eC |
7.1 결정적 구문 분석 (1) LL조건 테스트 : 1. NULLABLE = { B, C } 2. [ 알고리즘 7.1 ]에 따라 각 nonterminal의 FIRST를 구하면, FIRST(A) = { i } FIRST(B) = { [ , . , } FIRST(S) = { [ , . } FIRST(C) = { e, } 3. [알고리즘 7.2]에 따라 각 nonterminal의 FOLLOW를 구하면, FOLLOW(A) = { $ } FOLLOW(B) = { } FOLLOW(S) = { [ , . , } FOLLOW(C) = { ] }
7.1 결정적 구문 분석 4. FIRST와 FOLLOW를 이용하여 LL조건을 테스트하면, 1) B SB | 에서, FIRST(SB) FOLLOW(B) = { [, . } { } = . 2) S [eC] | .i에서, FIRST([eC]) FIRST(.i) = { [ } { . } = . 3) C eC | 에서, FIRST(eC) FOLLOW(C) = { e } { ] } = . 따라서, 모든 택일 생성 규칙에 대하여 교집합한 것이 공집합이므로 LL조건을 만족 한다.
7.1 결정적 구문 분석 (2) 좌측 유도 과정: <문장 형태> <입력 스트링> A iB e i[e] e iSB e i[e] e i[eC]B e i[e] e i[e]B e i[e] e i[e] e i[e] e
7.1 결정적 구문 분석 - 처음에, 입력 스트링 첫 심벌 i를 보고 시작 심벌 A를 첫번째 생성 규칙으로 확장 한다. 그리고, 생성된 문장 형태 iBe에서, 첫 심벌 i와 입력 스트링의 i가 같으므로, 다음 심벌을 비교한다. 문장 형태의 다음 심벌은 nonterminal B이고, 현재의 입력 심벌은 [이므로, B-생성 규칙중에서 [를 유도할 수 있는 생성 규칙 BSB를 문장 형태 iBe에 적용하여 유도 한다. 이와 같은 과정을 계속 행하면, 시작 심벌로부터 주어진 스트링을 결정적으로 유도할 수 있다.
7.1 결정적 구문 분석 - FIRST와 FOLLOW는 이와 같이 문장 형태에서 각 nonterminal이 생성할 수 있는 스트링이 무엇인지를 가리켜 주어서 현재의 입력 심벌을 보고, 생성 규칙을 결정적으로 선택할 수 있게 해 준다. - LL조건은 top-down방법으로 backtracking없이 파싱하기 위하여 문장 형태에서 대치해야 할 nonterminal에 대한 생성 규칙을 결정적으로 선택하기 위한 조건이고, FIRST와 FOLLOW는 이와 같은 정보를 찾는 방법이다. - 이론적으로는 LL조건과 strong LL조건을 구분해야 한다. 이 절에서 언급한 LL조건은 엄격히 말한다면, strong LL조건이다. 그런데 현재 보고 있는 심벌이 하나인 경우, 즉 LL(1)과 strong LL(1)이 같기 때문에 그냥 LL조건이라 불렀다.
7.1 결정적 구문 분석 이와 같은 이론적인 부분은 7.5절에서 다루기로 한다. 이제까지 설명한 LL방법을 실제로 파싱 알고리즘으로 사용하는 구문 분석기에는 recursive-descent파서와 predictive파서가 있는데 이들 내용을 다음 절에서 살펴 보자.
7.2 Recursive-descent 파서 - Recursive-descent파서는 LL파서의 일종으로 주어진 입력 스트링을 파싱하기 위하여 일련의 순환 프로시져(recursive procedure)를 사용 한다. 각 순환 프로시저는 각 nonterminal에 해당하는 것으로 nonterminal에 대한 유도를 프로시저 호출로 처리하는 방법. - Recursive-descent 파서를 구현하는 방법은 먼저 각 nonterminal에 대한 프로시저를 작성하고 이를 통합하여 프로그램으로서 작성한다. 이때, 가장 중요한 문제는 각 프로시저 내에서 입력 심벌에 따라 어떤 생성 규칙을 선택하느냐 하는 문제이다. 그래서 한 생성 규칙을 적용 했을 때 그 생성 규칙에서 생성할 수 있는 terminal을 알아야 하는데 이것을 그 생성 규칙의 LOOKAHEAD라 부른다.
7.2 Recursive-descent 파서 - 따라서 현재의 입력 심벌이 한 생성 규칙의 LOOKAHEAD에 속하게 되면 그 생성 규칙으로 확장할 수 있도록 프로시저를 작성하면 된다. - 주어진 문법으로부터 recursive-descent파서를 구현하려면 먼저 각 생성 규칙의 LOOKAHEAD를 구해야 하는데 그 정의와 계산하는 방법은 다음과 같다. [정의 7.7] LOOKAHEAD(A) = FIRST( | S * A * VT* } ). 생성 규칙에 대한 LOOKAHEAD의 의미는 그 생성 규칙을 적용 했을 때, 생성될 수 있는 terminal의 집합이다. 즉, rhs의 FIRST가 되든지 또는 rhs가 을 유도할 수 있으면 lhs의 FOLLOW도 속하게 된다.
7.2 Recursive-descent 파서 - 이런 의미를 갖는 LOOKAHEAD를 FIRST와 FOLLOW를 이용하여 계산하는 방법은 다음과 같다. LOOKAHEAD(A X1X2 ... Xn ) = FIRST(X1) FIRST(X2) ... FIRST(Xn) FOLLOW(A). - 생성 규칙의 형태에서, rhs가 terminal로 시작하는 생성 규칙의 LOOKAHEAD는 바로 그 terminal만이 된다. 예를 들어, LOOKAHEAD(A a) = { a }가 된다.
7.2 Recursive-descent 파서 [예 7] [예 3]의 문법에서, 각 생성 규칙의 LOOKAHEAD를 구하자. LOOKAHEAD(ETE’) = FIRST(T) FIRST(E’)FOLLOW(E) = { (, id } { +, } { ), $ } = { ( , id } LOOKAHEAD(E’+TE’) = FIRST(+) FIRST(T) FIRST(E’) FOLLOW(E’) = { + } { (, id } { +, } { ), $ } = { + } LOOKAHEAD(E’ ) = FIRST() FOLLOW(E’) = { ), $ } LOOKAHEAD(TFT’) = FIRST(F) FIRST(T’) FOLLOW(T) = { (, id } { *, } { +, ), $} = { (, id }
7.2 Recursive-descent 파서 LOOKAHEAD(T’*FT’) = FIRST(*) FIRST(F) FIRST(T’) FOLLOW(T’) = { * } { (, id } { *, } { +, ), $} = { * } LOOKAHEAD(T’ ) = FIRST() FOLLOW(T’) = { +, ), $ } LOOKAHEAD(F (E)) = FIRST( ( ) FIRST(E) FIRST( ) ) FOLLOW(F) = { ( } { (, id } { ( } { *, + , ), $ } = { ( } LOOKAHEAD(F id) = FIRST(id) FOLLOW(F) = { id } { *, +, ), $ } = { id }
7.2 Recursive-descent 파서 - Recursive-descent파서에서, nobacktracking의 조건을 설명할 수 있는 생성 규칙의 LOOKAHEAD는 각 생성 규칙에서 첫번째로 유도될 수 있는 terminal심벌들의 집합으로 문장 형태에서 nonterminal에 적용할 생성 규칙을 선택할 경우에 이 정보를 이용하여 결정적으로 선택 한다 [정의 7.8] 임의의 택일 규칙 A | P에 대하여, 다음을 strong LL조건이라 부른다. LOOKAHEAD(A) LOOKAHEAD(A ) = .
7.2 Recursive-descent 파서 - Strong LL조건에서 LOOKAHEAD가 같으면 생성 규칙이 유도 하는 첫번째 심벌이 같으므로 문장 형태에서 나타난 nonterminal에 어느 생성 규칙을 적용해야 올바른 스트링을 유도할지 알 수 없다. 그러나 LOOKAHEAD가 다르면 생성하는 스트링이 다르고 적용할 생성 규칙이 유일하므로 현재의 입력 심벌에 따라 결정적으로 구문 분석할 수 있다. - Strong LL조건을 만족하는 문법을 Strong LL(1) 문법이라 부르며, recursive-descent구문 분석기가 결정적으로 구문 분석 하기 위해서는 strong LL조건을 만족해야 한다.
7.2 Recursive-descent 파서 [예 8] 다음 문법이 strong LL(1) 문법인지를 살펴보자. S bRS | RcSa | R acR | b 1. LOOKAHEAD(SbRS) = FIRST(b) FIRST(R) FIRST(S) FOLLOW(S) = {b} {a,b} { a, b, } { a, $ } = {b} 2. LOOKAHEAD(SRcSa) = FIRST(R) FIRST(c) FIRST(S) FIRST(a) FOLLOW(S) = { a, b } { c } { a, b, } { a } { a, $ } = { a, b }
7.2 Recursive-descent 파서 LOOKAHEAD(SbRS) LOOKAHEAD(SRcSa) = {b} 교집합한 것이 공집합이 아니므로 strong LL조건을 만족하지 않는다. 따라서, strong LL문법이 아니다. - 위 문법이 strong LL문법이 아니므로 구문 분석시 적용할 생성 규칙을 결정적으로 선택할 수 없다. 즉 현재의 입력 심벌이 b이고 확장해야 될 nonterminal이 S일때, 생성 규칙 SbRS와 SRcSa가 모두 b를 생성하므로 어느 생성 규칙을 적용해야 할지 알 수 없다. - 정의된 문법이 strong LL(1) 문법이라면recursive-descent구문 분석기를 terminal과 nonterminal에 대한 프로시저를 작성함으로써 구현할 수 있다.
7.2 Recursive-descent 파서 - Terminal에 대한 프로시져는 현재의 입력 심벌이 자신의 심벌과 같은지를 비교하여 만일 같다면, 스캐너를 호출하여 현재의 입력 심벌을 새로 읽은 토큰으로 배정하는 기능을 한다. procedure pa; begin if nextsymbol = qa then get_nextsymbol else error end; (* pa *) - Terminal심벌 a에 대한 프로시저 이름을 pa로 쓰고 a에 대한 토큰 번호를 qa로 사용하였다. 프로시져 get_nextsymbol은 스캐너에 해당하는 루틴으로 입력 스트림으로부터 토큰 한 개를 읽어 변수 nextsymbol에 할당하는 일을 한다.
7.2 Recursive-descent 파서 - Nonterminal의 경우에, 현재의 입력 심벌과 LOOKAHEAD가 같은 생성 규칙을 선택해야 한다. 즉, 현재의 입력 심벌을 생성할 수 있는 생성 규칙을 선택하도록 프로시저를 작성해야 한다. procedure pA; begin case nextsymbol of LOOKAHEAD(AX1X2 ... Xn): for i:=1 to m do pXi ; LOOKAHEAD(AY1Y2 ... Yn): for i:= 1 to n do pYi; ... LOOKAHEAD(AZ1Z2 ... Zr); for i:=1 to r do pZi; otherwise : error end end; (* pA *)
7.2 Recursive-descent 파서 - 이상과 같은 방법으로 작성된 일련의 프로시저들은 주프로그램과 함께 통합하여 recursive-descent파서로 작동하게 된다. 주어진 스트링에 대한 구문 분석은 일련의 프로시저를 호출함으로써 실현 된다. - 주 프로그램에서 해야 할 작업은 먼저 스캐너를 호출하여 현재의 입력 심벌을 정하고 시작 심벌에 대한 프로시저를 호출 한다. 그리고 파싱이 모두 끝난 후에 현재의 입력 심벌이 입력의 끝을 표기하는 $기호이면 accept이고 그렇지 않으면 error로 처리 하는 일을 한다.
7.2 Recursive-descent 파서 begin (* main *) get_nextsymbol; pS; if nextsymbol = q$ then accept else error end. 여기서, 변수 nextsymbol이 현재의 입력 심벌을 저장하고 있으며 q$는 $기호에 대한 토큰 번호를 나타낸다.
7.2 Recursive-descent 파서 [예9] 다음 문법을 위한 recursive-descent 프로시저를 구성하자. S aAb A aS | b (1) Terminal 심벌에 대한 프로시저 : procedure pa; begin if nextsymbol = qa then get_nextsymbol else error end; (* pa *) procedure pb; begin if nextsymbol = qb then get_nextsymbol else error end; (* pb *)
7.2 Recursive-descent 파서 (2) Terminal 심벌에 대한 프로시저 : procedure pS; begin if nextsymbol = qa then begin pa; pA; pb end else error end; (* pS *) procedure pA; begin case nextsymbol of qa: begin pa; pS end; qb: pb; otherwise: error end end; (* pA *)