최근 수정 시각 : 2024-10-17 17:20:21

파싱/하향식


파일:상위 문서 아이콘.svg   상위 문서: 파싱
''' 이론 컴퓨터 과학
{{{#!wiki style="display: inline-block; font-family:Times New Roman, serif;font-style:italic"'''
{{{#!wiki style="margin: 0 -10px -5px; min-height: calc(1.5em + 5px)"
{{{#!folding [ 펼치기 · 접기 ]
{{{#!wiki style="margin: -5px -1px -11px"
<colbgcolor=#a36> 이론
기본 대상 수학기초론{ 수리논리학( 논리 연산) · 계산 가능성 이론 · 범주론 · 집합론} · 이산수학( 그래프 이론) · 수치해석학 · 확률론 통계학 · 선형대수학
다루는 대상과 주요 토픽
계산 가능성 이론 재귀함수 · 튜링 머신 · 람다대수 · 처치-튜링 명제 · 바쁜 비버
오토마타 이론 FSM · 푸시다운 · 튜링 머신( 폰노이만 구조) · 정규 표현식 · 콘웨이의 생명 게임 · 형식언어
계산 복잡도 이론 점근 표기법 · 튜링 기계^ 고전, 양자, 비결정론적, 병렬 임의접근 기계^ · 알고리즘 · 자료구조 · 알고리즘 패러다임( 그리디 알고리즘, 동적 계획법)
정보이론 데이터 압축( 무손실 압축 포맷 · 손실 압축 포맷) · 채널 코딩(채널 용량) · 알고리즘 정보 이론(AIT) · 양자정보과학
프로그래밍 언어이론 프로그래밍 언어( 함수형 언어 · 객체 지향 프로그래밍 · 증명보조기) · 메타 프로그래밍 · 유형 이론 · 프로그래밍 언어 의미론 · 파싱 · 컴파일러 이론
주요 알고리즘 및 자료구조
기초 정렬 알고리즘 · 순서도 · 탐색 알고리즘
추상적 자료형 및 구현 배열^ 벡터^ · 리스트^ 연결 리스트^ · 셋(set)^ 레드-블랙 트리, B-트리^ · 우선순위 큐^, 피보나치 힙^
수학적 최적화 조합 최적화 외판원 순회 문제 · 담금질 기법 · 유전 알고리즘 · 기계학습
볼록 최적화 내부점 방법 · 경사하강법
선형계획법 심플렉스법
계산 수론 및 암호학 밀러-라빈 소수판별법 · Pollard-rho 알고리즘 · 쇼어 알고리즘 · LLL 알고리즘 · 해시( MD5 · 암호화폐 · 사전 공격( 레인보우 테이블) · SHA) · 양자 암호
대칭키 암호화 방식 블록 암호 알고리즘( AES · ARIA · LEA · Camellia) · 스트림 암호 알고리즘(RC4)
공개키 암호화 방식 공개키 암호 알고리즘( 타원 곡선 암호 · RSA) · 신원 기반 암호 알고리즘(SM9)
계산기하학 볼록 껍질 · 들로네 삼각분할 및 보로노이 도형^Fortune의 line-sweeping 알고리즘^ · 범위 탐색^vp-tree, R-tree^ · k-NN
그래프 이론 탐색^ BFS, DFS, 다익스트라 알고리즘, A* 알고리즘^ · 에드몬드-카프 · 크루스칼 알고리즘 · 위상 정렬 · 네트워크 이론
정리
정지 문제 대각선 논법 · 암달의 법칙 · P-NP 문제미해결 · 콜라츠 추측미해결
틀:이산수학 · 틀:수학기초론 · 틀:컴퓨터공학 }}}}}}}}}



[[컴퓨터공학|컴퓨터 과학 & 공학
Computer Science & Engineering
]]
[ 펼치기 · 접기 ]
||<tablebgcolor=#fff,#1c1d1f><tablecolor=#373a3c,#ddd><colbgcolor=#0066DC><colcolor=white> 기반 학문 || 수학( 해석학 · 이산수학 · 수리논리학 · 선형대수학 · 미적분학 · 미분방정식 · 대수학( 환론 · 범주론) · 정수론) · 이론 컴퓨터 과학 · 암호학 · 전자공학 · 언어학( 형태론 · 통사론 · 의미론 · 화용론 · 음운론) · 인지과학 ||
하드웨어 구성 SoC · CPU · GPU( 그래픽 카드 · GPGPU) · ROM · RAM · SSD · HDD · 참조: 틀:컴퓨터 부품
기술 기계어 · 어셈블리어 · C/ C++ · C# · Java · Python · BIOS · 절차적 프로그래밍 · 객체 지향 프로그래밍 · 해킹 · ROT13 · 일회용 비밀번호 · 사물인터넷 · 와이파이 · GPS · 임베디드 · 인공신경망 · OpenGL · EXIF · 마이크로아키텍처 · ACPI · UEFI · NERF · gRPC · 리버스 엔지니어링 · HCI · UI · UX · 대역폭 · DBMS · NoSQL · 해시( SHA · 브루트 포스 · 레인보우 테이블 · salt · 암호화폐) · RSA 암호화 · 하드웨어 가속
연구

기타
논리 회로( 보수기 · 가산기 · 논리 연산 · 불 대수 · 플립플롭) · 정보이론 · 임베디드 시스템 · 운영 체제 · 데이터베이스 · 프로그래밍 언어{ 컴파일러( 어셈블러 · JIT) · 인터프리터 · 유형 이론 · 파싱 · 링커 · 난해한 프로그래밍 언어} · 메타데이터 · 기계학습 · 빅데이터 · 폰노이만 구조 · 양자컴퓨터 · 행위자 모델 · 인코딩( 유니코드 · MBCS) · 네트워크 · 컴퓨터 보안 · OCR · 슈퍼컴퓨터 · 튜링 머신 · FPGA · 딥러닝 · 컴퓨터 구조론 · 컴퓨터 비전 · 컴퓨터 그래픽스 · 인공지능 · 시간 복잡도( 최적화) · 소프트웨어 개발 방법론 · 디자인 패턴 · 정보처리이론 · 재귀 이론 · 자연어 처리( 기계 번역 · 음성인식) · 버전 ( 버전 관리 시스템 · Git · GitHub)

1. 개요2. 고려 사항
2.1. Left recursion 문제2.2. Left factoring
3. 재귀 하강 파서(Recursive Descent Parser)
3.1. Pseudo code3.2. Python 구현 예시
4. Predictive Parser(예측 파서)

1. 개요

하향식 파서(Top-down parser) 파서 유형 중 하나로, 시작 symbol의 생성 규칙에서 terminal 노드까지 유도하며 진행되는 parser이다. 입력 문자열을 시작 기호부터 시작하는 문법의 생성 규칙과 일치시키는 과정을 수행한다.

2. 고려 사항

2.1. Left recursion 문제

하향식 파서에서 left-recursion 문법을 활용하는 경우, 무한 루프가 발생할 수 있다. 예를 들어 다음 BNF를 고려해보자
[math(
\begin{array}{l}
\textbf{A}::= \textbf{A} \text{b}\ |\ c \\
\end{array}
)]

여기서 A는 우선 자기 자신을 참조하고, 이로 인해 파서는 A를 파싱하려고 할 때 항상 A를 먼저 시도하게 되며, 이 과정이 반복되어 무한 루프에 빠지게 된다. 무한 루프에 빠지게 되면, 재귀 호출이 계속 쌓이게 되어 결국 스택 오버플로우가 발생할 수 있다.

파일:left-rec-problem-1.jpg
조금 더 세분화된 과정은 다음과 같다.
  • parser가 non-terminal [math(\textbf{A})] 생성 규칙을 적용하려 한다.
  • 규칙 [math(\textbf{A}::= \textbf{A} \text{b})]를 적용 후 다시 non-terminal [math(\textbf{A})]를 만난다. 이 non-terminal A를 파싱하기 위해, 규칙 [math(\textbf{A}::= \textbf{A} \text{b})]를 다시 적용하려할 것이다. 이러한 과정이 계속 반복된다.
  • parser가 [math(\textbf{A})]를 다시 구문 분석하려고 할 때마다 호출 스택에 재귀 호출이 계속 추가되므로, 스택 오버플로 오류가 발생하게 된다.

좌측 재귀 문제를 해결하기 위해, 문법을 right recursion 또는 recursion 자체[1]가 없도록 변환해야 한다. 예를 들어, 위의 문법은 다음과 같이 right recursion으로 변환 가능하다.
[math(
\begin{array}{l}
\textbf{A}::= \text{c}\textbf{A}' \\
\textbf{A}' ::= \text{b} \textbf{A}'\ \vert\ \epsilon
\end{array}
)]

2.2. Left factoring

문법 규칙의 RHS의 첫부분에 공통적인 심볼이 존재하는 경우 문법이 비결정적일 수 있다. 특히 앞의 하나의 심볼로 다음 유도 방법을 결정하는 경우 이러한 문제는 더 자주 생긴다. 해당 부분에 새로운 non-terminal을 적용해 분리하는 방법도 있다.

[math(
\textbf{A} ::= \alpha \beta\ |\ \alpha \gamma \Rightarrow
\begin{array}{l} \textbf{A} ::= \alpha\ \textbf{A}'\\
\textbf{A}' ::= \beta\ |\ \gamma
\end{array}
)]

이러한 방법을 left factoring이라고 한다. 이 방식으로 파서가 α prefix를 통해 A non-terminal로 진입 후 다음 토큰을 기반으로 하여 β, γ 중 선택할 부분을 결정할 수 있다.

3. 재귀 하강 파서(Recursive Descent Parser)

재귀 하강 파서는 재귀 호출을 사용하여 입력 문자열을 분석한다. 각 non-terminal에 대해 하나의 함수가 존재하며, 이 함수들은 문법의 생성 규칙에 따라 입력을 분석한다. 구현이 비교적 간단하지만, left recursion 문법을 처리할 수는 없다.

3.1. Pseudo code

[math(
procedure\ A () \{\\
\quad \text{A에 대해 규칙}\ A\rarr X_1X_2\cdots X_n\ \text{선택}; \\
\quad \text{for}\ (i=1, 2, \cdots, k)\ \{ \\
\quad\quad\quad \text{if } (X_i \text{가 non-terminal}) \\
\quad\quad\quad\quad \text{call procedure } X_i(); \\
\quad\quad\quad \text{else if } (X_i\text{가 현재 input symbol a와 일치})\\
\quad\quad\quad\quad (i+1) \text{번째 symbol로 이동}; \\
\quad\quad\quad \text{else Syntax error}; \\
\quad \} \\
\}
)]

3.2. Python 구현 예시

예를 들어, 다음의 이진수와 이진수 간의 덧셈에 대한 BNF 문법 검증은 Python의 경우 다음과 같이 구현할 수 있다.

[math(
\begin{array}{l}
\textbf{expr}::= \textbf{binary} + \textbf{binary}~|~\textbf{binary} \\
\textbf{binary} ::= \textbf{digit}~|~\textbf{digit}\ \textbf{binary} \\
\textbf{digit} ::= 0~|~1
\end{array}
)]

#!syntax python
class RecursiveDescentParser:
    def __init__(self, input_string):
        self.input = input_string.replace(" ", "")
        self.index = 0

    def parse(self):
        result = self.expr()
        if self.index < len(self.input):
            raise SyntaxError("Unexpected character at index {}".format(self.index))
        return result

    def expr(self):
        # expr ::= binary + binary | binary
        left = self.binary()
        if self.index < len(self.input) and self.input[self.index] == "+":
            self.index += 1  # skip '+'
            right = self.binary()
            return left and right  # 두 binary의 문법 일치 여부 출력
        else:
            return left # 단일 binary의 경우

    def binary(self):
        # binary ::= digit | digit binary
        if not self.digit():
            return False
        while self.index < len(self.input) and self.input[self.index] in "01":
            self.index += 1
        return True

    def digit(self):
        # digit ::= 0 | 1
        if self.index < len(self.input) and self.input[self.index] in "01":
            self.index += 1
            return True
        else:
            return False


# 예시: 101 + 010
code = "101 + 010"
parser = RecursiveDescentParser(code)
result = parser.parse()
print(result) # True

4. Predictive Parser(예측 파서)


[1] 다만 간접적인 left recursion도 문제가 될 수 있다.