Download
slide1 n.
Skip this Video
Loading SlideShow in 5 Seconds..
COMPILER PowerPoint Presentation

COMPILER

368 Views Download Presentation
Download Presentation

COMPILER

- - - - - - - - - - - - - - - - - - - - - - - - - - - E N D - - - - - - - - - - - - - - - - - - - - - - - - - - -
Presentation Transcript

  1. COMPILER Chapter 7. LL구문 분석 김 영 균 ygkim@cespc1.kumoh.ac.kr

  2. Contents 1. 결정적 구문 분석 2. Recursive-descent 파서 3. Predictive 파서 4. Predictive 파싱 테이블의 구성 5. Strong LL(k)문법과 LL(k)문법

  3. 7. 구문 분석 - Top-down 구문분석이란, 시작 심벌로부터 유도과정을 거쳐 주어진 스트링과 같은 문장을 생성하기 위한 방법으로 생각. - 입력 심벌을 반복해서 검조(scanning)하면서, 구문 분석을 하는 일반적인 top-down방식은 6장에서 이미 설명. - 일반적인 top-down방식은 주어진 스트링을 파싱하는데 많은 시간을 필요로 함. - 실제적인 컴파일러의 파싱 알고리즘으로 사용하기에는 부적당. 따라서, backtracking을 하지 않고 결정적으로 파싱할 수 있는 방법이 요구됨. 이와 같은 결정적 파싱 방법을 LL파싱이라 부름.

  4. 7. 구문 분석 - LL은 입력 스트링을 왼쪽에서 오른쪽으로 읽어 가며 (left to right scanning) 좌파스(left parse)를 생성하기 때문에 붙여진 이름. - LL방법은 주어진 스트링과 같은 문장을 생성하기 위하여 현재의 입력 심벌을 보고,적용될 생성 규칙을 결정적으로 선택하여 유도 한다. - 현재의 입력 심벌과 생성된 terminal심벌이 같지 않으면, 주어진 스트링을 틀린 문장으로 간주하는 방법. 이와 같이 결정적으로 파싱하기 위해서는 문법이 어떤 특정한 조건을 만족해야 하는데 이 조건을 LL 조건(LL condition)이라고 부름.

  5. 7. 구문 분석 - LL 조건을 형식적으로 정의하고, LL조건을 만족하는 문법을 결정적으로 파싱할 수 있는 recursive-descent파서와predictive 파서에 관한 내용을 다룸. - LL은 이제까지 본 심벌과현재 보고 있는 심벌에 따라 파싱 행동을 결정하고 strong LL은 현재 보고 있는 심벌에 따라, 파싱 행동을 결정한다. - 그런데 현재 보고 있는 심벌이 한 개인 경우, 즉 LL(1)과 strong LL(1)이 같기 때문에 LL조건이라 부름.

  6. 7.1 결정적 구문 분석 7.1 결정적 구문 분석 - Top-down방법으로 구문 분석을 할 때, backtracking을 하지 않기 위해서는6장에서 설명한 대로 문법이 left-recursion을 갖지 않아야 하며, left-factoring이 되어야 하는 것 이외에 몇가지 조건을 더 만족해야 한다. - 왜냐하면, 문장 형태에서 nonterminal에 대한 유도를 할 때, 생성 규칙이 여러 개가 있을 수 있으므로 구문 분석기가 어느 생성 규칙을 적용해야 할 지 알 수 없기 때문이다. - 생성규칙을 결정적으로 선택하기 위한 조건을 형식적으로 전개하기 위해 FIRST, FOLLOW등 몇 가지 용어를 정의하고 주어진 생성 규칙으로부터 계산하는 알고리즘을 살펴보기로 한다.

  7. 7.1 결정적 구문 분석 7.1.1 FIRST - 문법 G=(VN, VT, P, S)가 context-free문법이고 AVN, aVT일 때, nonterminal A에 대한 FIRST는 다음과 같이 정의 된다. [정의 7.1] FIRST(A) = { aVT{ } | A* a, V* }. 즉, nonterminal A로부터 유도되어 첫번째로 나타날 수 있는 terminal의 집합을 의미 한다.

  8. 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 }.

  9. 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의 의미는 첫번째 피연산자의 집합이 을 갖지 않으면 그 자신이 되며, 을 포함하고 있으면 을 제외한 첫번째 피연산자와 두번째 피연산자를 합집합한 것이 된다.

  10. 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가 된다.

  11. 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 aVT. 또한, X가 -생성 규칙을 가지면 X의 FIRST에 이 포함 된다. FIRST(X) = FIRST(X)  {} if X   P. 3. X  Y1Y2 ... Yk인 생성 규칙이 있을 때, FIRST(X) = FIRST(X)  FIRST(Y1Y2 ... Yk)이다.

  12. 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;

  13. 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.

  14. 7.1 결정적 구문 분석 - 초기에, 모든 nonterminal의 FIRST는 공집합이 된다. 그리고 rhs에 첫번째로 terminal이 나오는 생성 규칙 (-생성 규칙 포함)에서 FIRST를 구한 후, 이 형태의 생성 규칙은 다시 고려할 필요가 없기 때문에 제거 한다. 남은 생성 규칙의 형태는 모두 nonterminal로 시작하므로 ring sum연산을 이용하여, 모든 nonterminal의 FIRST가 변하지 않을 때까지 반복한다. 일반적으로, A-생성 규칙이 A 1 | 2 | ... | nP와 같은 형태일 때, 다음과 같이 된다. FIRST(A) = FIRST(1)  FIRST(2)  ...  FIRST(n)

  15. 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 }

  16. 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 }

  17. 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’) = { * ,  }

  18. 7.1 결정적 구문 분석 7.1.2 FOLLOW - Nonterminal A가 nullable하면 FIRST(A)에 이 속하게 되지만 FIRST를 가지고는 생성 규칙을 결정적으로 선택할 수 없다. 즉, nonterminal A다음에 나오는 심벌에 따라 어느 생성 규칙 으로 유도할 것인가를 결정하게 된다. 따라서, -생성 규칙을 갖는 문법에서는 nonterminal다음에 나오는 terminal심벌이 의미를 갖게 되는데 이것을 FOLLOW라 부르며, 다음과 같이 정의 된다. [정의 7.5] FOLLOW(A)={aVT  {$} | S * Aa, ,V* }. 여기서, $는 입력 스트링의 끝을 표기하는 마커 심벌(marker symbol)을 나타낸다.

  19. 7.1 결정적 구문 분석 - Nonterminal A의 FOLLOW란 시작 심벌로부터 유도될 수 있는 모든 문장 형태에서 A다음에 나오는 terminal심벌의 집합이다. - 따라서, FOLLOW를 계산하기 위해서는 모든 문장 형태를 고려해야 하나 문장 형태는 생성 규칙의 rhs들로 이루어져 있다. 그러므로, 생성 규칙의 rhs를 이용하여FOLLOW를 구할 수 있다. 각 생성 규칙의 형태에 따른 FOLLOW를 계산하는 방법은 다음과 같다. 1. 시작 심벌은 $를 초기값으로 갖는다. FOLLOW(S) = { $ }. 2. 생성 규칙의 형태가 AB,  일때, FIRST()에서 을 제외한 terminal심벌을 B의 FOLLOW에 추가 한다.

  20. 7.1 결정적 구문 분석 1. 시작 심벌은 $를 초기값으로 갖는다. FOLLOW(S) = { $ }. 2. 생성 규칙의 형태가 AB,  일때, FIRST()에서 을 제외한 terminal심벌을 B의 FOLLOW에 추가 한다. If AB,  then FOLLOW(B) = FOLLOW(B)  ( FIRST() - {} ) 3. AB이거나, AB에서 FIRST()에서 이 속하는 경우 (즉,  * ), A의 FOLLOW전체를 B의 FOLLOW에 추가한다. If ABP or (AB and  *  ) then FOLLOW(B) = FOLLOW(B)  FOLLOW(A).

  21. 7.1 결정적 구문 분석 - 계산 과정 3번의 의미는 생성 규칙 형태가 AB인 경우, 가 이거나 또는 을 유도할 수 있으면, A의 FOLLOW전체를 B의 FOLLOW에 넣는다는 것이다. 왜냐하면, 임의의 문장 형태에서 A다음에 나올 수 있는 심벌은 모두 B다음에 나올 수 있기 때문이다. 즉, S * 1A2  1B2 * 1B2 이다. 따라서 FOLLOW속성으로 인하여 A  B, B  A와 같은 형태의 생성 규칙을 갖고 있으면, FOLLOW(A)=FOLLOW(B)가 된다.

  22. 7.1 결정적 구문 분석 왜냐하면, 첫번째 형태의 생성 규칙으로부터 FOLLOW(A)  FOLLOW(B)이고, 두번째 형태의 생성 규칙으로부터 FOLLOW(A) FOLLOW(B)가 되기 때문이다. 이제 문법이 주어졌을 때, 각 nonterminal에 대한 FOLLOW를 구하는 방법을 생각해 보자. 먼저 문법으로부터 nullable심벌을 구하고,이것을 이용하여 [알고리즘 7.1]에 따라 FIRST를 계산 한다. 그리고 FIRST를 이용하여 FOLLOW를 구한다. FIRST를 이용하여 계산하는 방법은 다음과 같다.

  23. 7.1 결정적 구문 분석 왜냐하면, 첫번째 형태의 생성 규칙으로부터 FOLLOW(A)  FOLLOW(B)이고, 두번째 형태의 생성 규칙으로부터 FOLLOW(A) FOLLOW(B)가 되기 때문이다. 이제 문법이 주어졌을 때, 각 nonterminal에 대한 FOLLOW를 구하는 방법을 생각해 보자. 먼저 문법으로부터 nullable심벌을 구하고, 이것을 이용하여 [알고리즘 7.1]에 따라 FIRST를 계산 한다. 그리고 FIRST를 이용하여 FOLLOW를 구한다. FIRST를 이용하여 계산하는 방법은 다음과 같다.

  24. 7.1 결정적 구문 분석 [ 알고리즘 7.2 ] 각 nonterminal에 대한 FOLLOW를 구하는 방법: Algorithm Compute_FOLLOW; begin for each AVN do FOLLOW(A) := { }; FOLLOW(S):={$}; for ABP, where  do FOLLOW(B) := FOLLOW(B)  ( FIRST() - {}); repeat for each AP do if  = B or ( = B and  *  ) then FOLLOW(B) := FOLLOW(B)  FOLLOW(A) fi end for until no change end.

  25. 7.1 결정적 구문 분석 - 생성 규칙의 형태가 AB1 ... 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에 포함 시킨다.

  26. 7.1 결정적 구문 분석 - 이렇게 해서 더 이상 FOLLOW집합이 변하지 않을 때까지 계속하면 다음과 같은 FOLLOW집합이 구해진다. FOLLOW(E) = FOLLOW(E’) = { ), $ } FOLLOW(T) = FOLLOW(T’) = { +, ), $ } FOLLOW(F) = { + , * , ) , $}

  27. 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-생성 규칙 중에 한 개를 결정적으로 선택할 수 있는 조건이다.

  28. 7.1 결정적 구문 분석 - LL조건은 FIRST와 FOLLOW를 이용하여 다음과 같이 정의할 수 있다. [정의 7.7] 임의의 생성 규칙 A | P에 대하여, 다음 조건을 만족해야 한다. 1. FIRST()  FIRST() = ; 2. If FIRST() then FOLLOW(A)  FIRST() = . 생성 규칙의 FIRST가 서로 다르다는 것은 유도하는 스트링이 다르다는 것이므로 문장 형태에서 적용할 생성 규칙을 결정적으로 선택할 수 있다. 그러나, FIRST가 같으면 유도하는 스트링이 같아서 생성 규칙 중 어느 것을 적용해야 올바른 것인지 알 수 없게 된다.

  29. 7.1 결정적 구문 분석 - 또한 생성 규칙이 을 유도할 수 있으면FOLLOW심벌에 대하여 그 생성 규칙을 선택하게 된다. 따라서 FOLLOW심벌과도 서로 분리된 집합이어야 한다. [예 6] 다음 문법을 가지고 스트링 i[e]e를 결정적으로 구문 분석할 수 있는지 알아보자. A  iB  e B  SB |  S  [eC] | .i C  eC | 

  30. 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) = { ] }

  31. 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조건을 만족 한다.

  32. 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 

  33. 7.1 결정적 구문 분석 - 처음에, 입력 스트링 첫 심벌 i를 보고 시작 심벌 A를 첫번째 생성 규칙으로 확장 한다. 그리고, 생성된 문장 형태 iBe에서, 첫 심벌 i와 입력 스트링의 i가 같으므로, 다음 심벌을 비교한다. 문장 형태의 다음 심벌은 nonterminal B이고, 현재의 입력 심벌은 [이므로, B-생성 규칙중에서 [를 유도할 수 있는 생성 규칙 BSB를 문장 형태 iBe에 적용하여 유도 한다. 이와 같은 과정을 계속 행하면, 시작 심벌로부터 주어진 스트링을 결정적으로 유도할 수 있다.

  34. 7.1 결정적 구문 분석 - FIRST와 FOLLOW는 이와 같이 문장 형태에서 각 nonterminal이 생성할 수 있는 스트링이 무엇인지를 가리켜 주어서 현재의 입력 심벌을 보고, 생성 규칙을 결정적으로 선택할 수 있게 해 준다. - LL조건은 top-down방법으로 backtracking없이 파싱하기 위하여 문장 형태에서 대치해야 할 nonterminal에 대한 생성 규칙을 결정적으로 선택하기 위한 조건이고, FIRST와 FOLLOW는 이와 같은 정보를 찾는 방법이다. - 이론적으로는 LL조건과 strong LL조건을 구분해야 한다. 이 절에서 언급한 LL조건은 엄격히 말한다면, strong LL조건이다. 그런데 현재 보고 있는 심벌이 하나인 경우, 즉 LL(1)과 strong LL(1)이 같기 때문에 그냥 LL조건이라 불렀다.

  35. 7.1 결정적 구문 분석 이와 같은 이론적인 부분은 7.5절에서 다루기로 한다. 이제까지 설명한 LL방법을 실제로 파싱 알고리즘으로 사용하는 구문 분석기에는 recursive-descent파서와 predictive파서가 있는데 이들 내용을 다음 절에서 살펴 보자.

  36. 7.2 Recursive-descent 파서 - Recursive-descent파서는 LL파서의 일종으로 주어진 입력 스트링을 파싱하기 위하여 일련의 순환 프로시져(recursive procedure)를 사용 한다. 각 순환 프로시저는 각 nonterminal에 해당하는 것으로 nonterminal에 대한 유도를 프로시저 호출로 처리하는 방법. - Recursive-descent 파서를 구현하는 방법은 먼저 각 nonterminal에 대한 프로시저를 작성하고 이를 통합하여 프로그램으로서 작성한다. 이때, 가장 중요한 문제는 각 프로시저 내에서 입력 심벌에 따라 어떤 생성 규칙을 선택하느냐 하는 문제이다. 그래서 한 생성 규칙을 적용 했을 때 그 생성 규칙에서 생성할 수 있는 terminal을 알아야 하는데 이것을 그 생성 규칙의 LOOKAHEAD라 부른다.

  37. 7.2 Recursive-descent 파서 - 따라서 현재의 입력 심벌이 한 생성 규칙의 LOOKAHEAD에 속하게 되면 그 생성 규칙으로 확장할 수 있도록 프로시저를 작성하면 된다. - 주어진 문법으로부터 recursive-descent파서를 구현하려면 먼저 각 생성 규칙의 LOOKAHEAD를 구해야 하는데 그 정의와 계산하는 방법은 다음과 같다. [정의 7.7] LOOKAHEAD(A) = FIRST(  | S * A    *  VT* } ). 생성 규칙에 대한 LOOKAHEAD의 의미는 그 생성 규칙을 적용 했을 때, 생성될 수 있는 terminal의 집합이다. 즉, rhs의 FIRST가 되든지 또는 rhs가 을 유도할 수 있으면 lhs의 FOLLOW도 속하게 된다.

  38. 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 }가 된다.

  39. 7.2 Recursive-descent 파서 [예 7] [예 3]의 문법에서, 각 생성 규칙의 LOOKAHEAD를 구하자. LOOKAHEAD(ETE’) = 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(TFT’) = FIRST(F)  FIRST(T’)  FOLLOW(T) = { (, id }  { *,  }  { +, ), $} = { (, id }

  40. 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 }

  41. 7.2 Recursive-descent 파서 - Recursive-descent파서에서, nobacktracking의 조건을 설명할 수 있는 생성 규칙의 LOOKAHEAD는 각 생성 규칙에서 첫번째로 유도될 수 있는 terminal심벌들의 집합으로 문장 형태에서 nonterminal에 적용할 생성 규칙을 선택할 경우에 이 정보를 이용하여 결정적으로 선택 한다 [정의 7.8] 임의의 택일 규칙 A |   P에 대하여, 다음을 strong LL조건이라 부른다. LOOKAHEAD(A)  LOOKAHEAD(A ) = .

  42. 7.2 Recursive-descent 파서 - Strong LL조건에서 LOOKAHEAD가 같으면 생성 규칙이 유도 하는 첫번째 심벌이 같으므로 문장 형태에서 나타난 nonterminal에 어느 생성 규칙을 적용해야 올바른 스트링을 유도할지 알 수 없다. 그러나 LOOKAHEAD가 다르면 생성하는 스트링이 다르고 적용할 생성 규칙이 유일하므로 현재의 입력 심벌에 따라 결정적으로 구문 분석할 수 있다. - Strong LL조건을 만족하는 문법을 Strong LL(1) 문법이라 부르며, recursive-descent구문 분석기가 결정적으로 구문 분석 하기 위해서는 strong LL조건을 만족해야 한다.

  43. 7.2 Recursive-descent 파서 [예 8] 다음 문법이 strong LL(1) 문법인지를 살펴보자. S  bRS | RcSa |  R acR | b 1. LOOKAHEAD(SbRS) = FIRST(b)  FIRST(R)  FIRST(S)  FOLLOW(S) = {b}  {a,b}  { a, b, }  { a, $ } = {b} 2. LOOKAHEAD(SRcSa) = FIRST(R)  FIRST(c)  FIRST(S)  FIRST(a)  FOLLOW(S) = { a, b }  { c }  { a, b, }  { a }  { a, $ } = { a, b }

  44. 7.2 Recursive-descent 파서 LOOKAHEAD(SbRS)  LOOKAHEAD(SRcSa) = {b} 교집합한 것이 공집합이 아니므로 strong LL조건을 만족하지 않는다. 따라서, strong LL문법이 아니다. - 위 문법이 strong LL문법이 아니므로 구문 분석시 적용할 생성 규칙을 결정적으로 선택할 수 없다. 즉 현재의 입력 심벌이 b이고 확장해야 될 nonterminal이 S일때, 생성 규칙 SbRS와 SRcSa가 모두 b를 생성하므로 어느 생성 규칙을 적용해야 할지 알 수 없다. - 정의된 문법이 strong LL(1) 문법이라면recursive-descent구문 분석기를 terminal과 nonterminal에 대한 프로시저를 작성함으로써 구현할 수 있다.

  45. 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에 할당하는 일을 한다.

  46. 7.2 Recursive-descent 파서 - Nonterminal의 경우에, 현재의 입력 심벌과 LOOKAHEAD가 같은 생성 규칙을 선택해야 한다. 즉, 현재의 입력 심벌을 생성할 수 있는 생성 규칙을 선택하도록 프로시저를 작성해야 한다. procedure pA; begin case nextsymbol of LOOKAHEAD(AX1X2 ... Xn): for i:=1 to m do pXi ; LOOKAHEAD(AY1Y2 ... Yn): for i:= 1 to n do pYi; ... LOOKAHEAD(AZ1Z2 ... Zr); for i:=1 to r do pZi; otherwise : error end end; (* pA *)

  47. 7.2 Recursive-descent 파서 - 이상과 같은 방법으로 작성된 일련의 프로시저들은 주프로그램과 함께 통합하여 recursive-descent파서로 작동하게 된다. 주어진 스트링에 대한 구문 분석은 일련의 프로시저를 호출함으로써 실현 된다. - 주 프로그램에서 해야 할 작업은 먼저 스캐너를 호출하여 현재의 입력 심벌을 정하고 시작 심벌에 대한 프로시저를 호출 한다. 그리고 파싱이 모두 끝난 후에 현재의 입력 심벌이 입력의 끝을 표기하는 $기호이면 accept이고 그렇지 않으면 error로 처리 하는 일을 한다.

  48. 7.2 Recursive-descent 파서 begin (* main *) get_nextsymbol; pS; if nextsymbol = q$ then accept else error end. 여기서, 변수 nextsymbol이 현재의 입력 심벌을 저장하고 있으며 q$는 $기호에 대한 토큰 번호를 나타낸다.

  49. 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 *)

  50. 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 *)