본문 바로가기

23년 2학기 학교공부/컴파일러개론

[CP] Bottom-up 파싱, Reduce와 Shift-Reduce 파싱

목차

    728x90
    반응형
    SMALL

    📁 Bottom-up으로 구문 분석(Parsing)하기

     

     

     

     

    Bottom-up의 파싱 결과는 우파스로, 우측 유도의 역순이다.

     

    terminal 심벌들로부터 시작해서 시작 심벌을 유도하는 방식이며, 생성 규칙의 RHS와 매치되는지 확인하고, 매치되면 LHS로 바꿔치기를 반복하여 진행한다.

     

     

     

    예를 들어 아래 문법을 Bottom-up 방식으로 파싱하면 다음과 같다.

     

     

     

    📁 LL, LR

     

    1. LL(k)

    LL이란 입력을 Left-to-right, 왼쪽에서 오른쪽으로 스캔하여, Left-most를 유도하는 것?을 말한다.

     

    k개의 심벌을 LOOKAHEAD로 사용한다.

     

     

     

     

     

     

    2. LR(k)

    LR이란 입력을 Left-to-right, 왼쪽에서 오른쪽으로 스캔하여, Right-most를 유도하는 것을 말한다.

     

    마찬가지로 k개의 심벌을 LOOKAHEAD로 사용한다.

     

     

     

     

     

    📁 Reduce

     

    Reduce란, S => 이고 A→ 의 생성규칙이 존재할 때, 문장형태 에서 를 A로 대치하는 것을 말한다.

     

    즉, Bottom-up 파싱에서의 파싱 결과는 시작 심벌이 나올때 까지 reduce한 결과와 같다.

     

     

     

    예를 들어 아래 문법을 시작 심벌이 나올때까지 reduce하는 과정, 즉 Bottom-up 파싱하는 과정은 다음과 같다.

     

     

     

     

     

     

    📁 Handle

     

    Handle이란, 한 문장 형태 안에서 reduce될 부분을 지칭한다.

     

    즉 S => A => 의 과정이 있을 때 을 문장형태  의 handle이라고 한다.

     

     

    예를 들어 아래 문법의 reduce 과정에서 Handle을 살펴보자.

     

     

     

    이때 같은 문장 형태에서 서로 다른 두 개 이상의 Handle이 존재하는 경우, 해당 문법이 모호하다고 판단한다.

     

     

    예를 들어 아래 문법을 이용하여 스트링 "id + id * id"를 파싱해보자.

     

    위 reduce 과정에서 두 가지 handle이 존재할 수 있으므로, 위 문법은 모호한 문법이다.

     

     

     

     

     

    📁 Shift-Reduce Parsing

     

    Shift란, 스택의 top에 handle이 나타날 때 까지 계속해서 입력 심벌을 스택에 옮겨 담는 작업을 말한다.

     

    Reduce는 위에서 설명했듯이 handle을 찾으면 생성 규칙을 결정하여 LHS인 Nonterminal로 바꿔치는 작업이다.

     

     

    위의 두 과정을 문법의 시작 심벌에 이를때까지 반복 수행하는 파싱이 Shift-Reduce Parsing이다.

     

     

     

    Shift 작업의 예시를 들어보면, 다음과 같이 LOOKAHEAD 토큰 하나를 스택으로 옮길 수 있다.

     

    이후 수행될 Reduce 작업의 예시는 다음과 같이, 생성 규칙이 X->b인 경우 스택 top에 있는 handle b를 Nonterminal 심벌 X로 바꿔치기한다.

     

     

     

     

    Shift-Reduce Parsing의 예를 다음 문법을 통해 알아보자.

     

     

    먼저 reduce를 진행하면 다음과 같다.

     

    이후 스택을 사용한 Shift-Reduce 구문 분석 과정은 다음과 같다.

     

    가장 초기 상태는 스택에 $를 넣고, 입력버퍼에 입력스트링과 $를 넣은 상태로 시작한다.

    마지막 accept 상태는 스택 top에 시작 심벌이 있고, 입력은 모두 봐서 입력 버퍼의 입력스트링이 없어진 상태가 된다.

     

     

     

    이때 Shift-Reduce Parsing 과정에는 여러 이슈가 존재한다.

     

     

    1. 파싱의 각 과정에서, Shift와 Reduce 중 어느 작업을 수행해야 하는지를 어떻게 알 수 있을까?

     

     

    예를 들어 스택에 β가 있고, 입력 글자가 b라고 가정하자.

     

     

    β = αγ 이고, X -> γ 라는 생성 규칙이 존재한다면,

     

    1) Reduce를 진행하여 스택을 αX로 만들어야하는가?

    2) Shift를 진행하여 스택을 βb로 만들어야하는가?

     

     

     

    2.  β = αγ이고, 스택이 αγ이며 X -> γ 라는 생성 규칙을 따라 reduce를 진행하는 경우, 가능한 γ의 길이가 여러가지일 수 있다. 이런 경우 스택의 top 중 어디까지를 handle이라고 인식해야할까?

     

     

     

     

    📁 LR Parsing Engine

     

    위의 여러 이슈들은 '파서 상태'를 사용하여 해결할 수 있다.

     

    스택에 입력 심벌을 shift할 때, 관련된 '파서 상태'도 함께 넣는 방식이다.

     

     

    예를 들면 스택이 아래와 같이 구성될 수 있다.

     

     

    '파서 상태'가 무엇인지에 따라서 reduce를 수행해야하는지, shift를 수행해야하는지가 결정되고, 얼만큼을 stack top으로 간주해야하는지 또한 결정된다.

     

     

    '파서 상태'가 추가됨에 따라 파싱테이블도 <상태> X <심벌들> 의 형태가 된다.

     

    파서는 파싱테이블을 보고 현재상태, 입력 심벌을 찾아 shift할지 reduce할지를 결정한 이후, (action table)

    파서 상태를 변경시킨다. (goto table)

     

     

     

    즉, 현재 상태가 S이고 입력문자가 a라고 가정하자.

     

     

    M[S,a] = “shift S’ ” 라면 shift를 진행하여

    1 .push(a)

    2. push(S’)를 수행하고,

     

     

    M[S,a] = “reduce X→ α ”라면 reduce를 진행하여

    1. pop(2*| α |)

    2. S’= top()

    3. push(X)

    4. push(M[S’,X])을 수행한다.

     

     

     

    🌱 LR(0) 파싱 테이블 만들기

     

    1. 파서 상태가 될 만한 것들이 무엇인지 정한다.

     

    2. 각 파서 상태들 간의 상태 전이도(DFA : Deterministic Finite Automata)를 만든다.

     

    3. 위 결과를 파싱 테이블에 담아낸다.

     

     

    자세한 내용은 아래 포스팅에 작성하였다.

     

     

     

     

    예를 들어 아래 문법을 이용하여 "a, a" 스트링을 파싱해보자.

     

     

    0을 시작상태라고 가정하고, 파싱 테이블은 다음과 같이 만들어진다.

     

    파싱 과정은 다음과 같다.

    728x90
    반응형
    LIST