Post

파이썬 Lark를 이용하여 LaTeX 문법 파싱하기 (1)

파이썬 Lark를 이용하여 LaTeX 문법 파싱하기 (1)

파서는 무엇인가?

파서는 데이터 스트림을 읽고 규칙에 따라 토큰으로 분해하는 도구입니다.

이러한 규칙의 집합을 문법(grammar)이라고 합니다.

프로그래밍 언어의 경우, 파서는 코드의 각 줄을 읽어 들이고 이를 토큰으로 분해하여 그 의미를 파악합니다.

예를 들어, A = 14라는 코드를 파싱하면 다음과 같은 토큰으로 분해됩니다:

  • A: 식별자(identifier)
  • =: 등호(equal sign)
  • 14: 숫자(number)

이렇게 분해된 토큰을 기반으로 프로그램은 해당 코드의 의미를 이해하고 실행할 수 있게 됩니다.

파서의 활용 분야

파서는 단순히 컴파일러를 작성하는 데만 사용되는 것이 아닙니다.

데이터의 구조를 파악하고 필요한 정보를 추출해야 하는 다양한 상황에서 유용하게 활용됩니다.

  • 정규 표현식 대체: 복잡한 패턴 매칭이 필요한 경우 정규 표현식보다 파서가 더 적합할 수 있습니다.
  • 도메인 특화 언어 처리: 특정 문제 영역에 맞는 언어를 정의하고 처리할 때 사용됩니다.
  • 데이터 구조 생성: 파싱 결과로부터 원하는 데이터 구조를 생성할 수 있습니다.

파서의 활용 예시

  • 파이썬 코드 바이트코드 변환: 소스 코드를 바이트코드로 변환하여 실행합니다.
  • 계산기 구현: 수식을 입력받아 계산 결과를 출력합니다.
  • 그래프 데이터 처리: 그래프 데이터를 파싱하여 시각화하거나 분석합니다.
  • 모스 부호 해석: 점과 대시로 이루어진 모스 부호를 문자로 변환합니다.
  • 음성 합성: 텍스트를 파싱하여 음성으로 변환합니다.

Lark 소개

https://github.com/lark-parser/lark

 [GitHub - lark-parser/lark: Lark is a parsing toolkit for Python, built with a focus on ergonomics, performance and modularity.

Lark is a parsing toolkit for Python, built with a focus on ergonomics, performance and modularity. - lark-parser/lark

github.com](https://github.com/lark-parser/lark)

Lark는 파이썬으로 작성된 강력한 파싱 라이브러리입니다.

다양한 형태의 데이터를 파싱 할 수 있으며, 복잡한 문법도 간단하게 정의할 수 있습니다.

Lark의 주요 특징

  • 유연한 문법 정의: 직관적이고 간단한 문법으로 복잡한 언어를 파싱 할 수 있습니다.
  • 빠른 성능: 효율적인 파싱 알고리즘을 사용하여 빠른 속도를 자랑합니다.
  • 공통 라이브러리 지원: 정수, 공백, 단어 등 일반적인 토큰을 위한 공통 정의를 제공합니다.
  • 트리 변환 지원: 파싱 결과로 생성된 트리를 쉽게 변환하고 조작할 수 있습니다.

Lark를 사용한 데이터 파싱 기본 과정

  1. 문법 정의: 파싱할 데이터에 대한 문법을 작성합니다.
  2. 파서 생성: Lark 라이브러리를 사용하여 파서를 생성합니다.
  3. 입력 파싱: 파서에 데이터를 입력하여 파싱 트리를 얻습니다.
  4. 트리 변환: 파싱 트리를 원하는 형태로 변환하거나 결과를 도출합니다.

문법 정의하기

파서를 사용하려면 먼저 파싱 할 데이터의 구조를 정의하는 문법이 필요합니다.

예를 들어, 간단한 덧셈 표현식 45 + 33을 파싱 하기 위한 문법은 다음과 같이 작성할 수 있습니다.

1
2
3
4
5
6
?sum: addend "+" addend
addend: INT

%import common.INT
%import common.WS
%ignore WS

문법의 구성 요소

  • 규칙(Rule): 파싱 대상의 구조를 정의하며, 소문자로 시작합니다.
  • 터미널(Terminal): 더 이상 분해되지 않는 기본 요소로, 대문자로 시작합니다.
  • 공통 라이브러리 사용: %import common.INT와 같이 Lark에서 제공하는 기본 정의를 가져옵니다.
  • 공백 무시: %ignore WS를 사용하여 공백을 무시하도록 설정합니다.

파싱 트리 생성 및 확인

파서를 사용하여 입력 데이터를 파싱 하면 파싱 트리가 생성됩니다.

이 트리는 입력 데이터의 구조를 계층적으로 나타냅니다.

파싱 트리 확인 방법

  1. 트리 출력: print(tree)를 사용하여 트리의 구조를 텍스트로 확인합니다.
  2. 트리 시각화: tree.pretty()를 사용하여 계층적으로 트리를 출력합니다.
  3. 그래프 생성: tree.draw()를 사용하여 트리를 그래프로 시각화합니다.

예시 코드

1
2
3
4
5
6
7
8
from lark import Lark

parser = Lark(grammar, start='sum')
tree = parser.parse("45 + 33")

print(tree)          # 트리 객체 출력
print(tree.pretty()) # 계층적 트리 출력
tree.draw()          # 그래프 시각화

트리 변환 및 계산 수행

파싱 트리는 그대로 사용하기보다는 원하는 결과를 얻기 위해 변환이 필요합니다.

이를 위해 Lark의 Transformer 클래스를 사용합니다.

Transformer 클래스 사용

Transformer를 상속하여 각 규칙에 해당하는 메서드를 정의합니다.

이 메서드들은 파싱 트리의 각 노드에서 호출되며, 반환값은 부모 노드로 전달됩니다.

예시 코드

1
2
3
4
5
6
7
8
from lark import Transformer

class CalculateTransformer(Transformer):
    def addend(self, items):
        return int(items[0])

    def sum(self, items):
        return items[0] + items[1]

사용 방법

1
2
3
transformer = CalculateTransformer()
result = transformer.transform(tree)
print(result)  # 출력: 78

예제: 덧셈 계산기 구현

위에서 정의한 문법과 Transformer를 사용하여 간단한 덧셈 계산기를 구현할 수 있습니다.

전체 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from lark import Lark, Transformer

grammar = """
?sum: addend "+" addend
addend: INT

%import common.INT
%import common.WS
%ignore WS
"""

parser = Lark(grammar, start='sum')

class CalculateTransformer(Transformer):
    def addend(self, items):
        return int(items[0])

    def sum(self, items):
        return items[0] + items[1]

expression = "45 + 33"
tree = parser.parse(expression)
result = CalculateTransformer().transform(tree)
print(result)  # 출력: 78

복잡한 산술 표현식 파싱

더 복잡한 산술 표현식을 파싱하기 위해 문법을 확장할 수 있습니다.

연산자 우선순위, 괄호 등의 처리를 위해 재귀적인 문법을 작성합니다.

확장된 문법 예시

1
2
3
4
5
6
7
8
9
10
11
12
13
14
?expr: expr "+" term   -> add
     | expr "-" term   -> sub
     | term

?term: term "*" factor -> mul
     | term "/" factor -> div
     | factor

?factor: "(" expr ")"
       | INT

%import common.INT
%import common.WS
%ignore WS

확장된 Transformer 클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class CalculateTransformer(Transformer):
    def add(self, items):
        return items[0] + items[1]

    def sub(self, items):
        return items[0] - items[1]

    def mul(self, items):
        return items[0] * items[1]

    def div(self, items):
        return items[0] / items[1]

    def INT(self, token):
        return int(token)

예시 계산

1
2
3
4
expression = "18 / 6 + (4 * 5)"
tree = parser.parse(expression)
result = CalculateTransformer().transform(tree)
print(result)  # 출력: 23.0

파싱 트리 시각화

복잡한 표현식의 파싱 트리를 시각화하여 연산자 우선순위와 계산 순서를 명확하게 파악할 수 있습니다.

1
tree.draw()

파싱 트리를 활용한 추가 작업

파싱 트리는 다양한 방식으로 활용될 수 있습니다.

트리를 변환하거나 탐색하여 원하는 결과를 얻을 수 있습니다.

예시: 후위 표기법 변환

파싱 트리를 순회하여 중위 표기법을 후위 표기법으로 변환할 수 있습니다.

이를 통해 스택 기반의 계산을 수행할 수 있습니다.

예시: 모스 부호 해석

Lark를 사용하여 모스 부호를 파싱하고 문자로 변환하는 작업도 가능합니다.

점(.)과 대시(-)로 이루어진 모스 부호를 규칙에 따라 해석합니다.

연습 문제

  1. 빈칸 채우기: Lark에서 문법을 정의할 때, 규칙은 ______로 시작하고, 터미널은 ______로 시작합니다.
    답변: 규칙은 소문자로 시작하고, 터미널은 대문자로 시작합니다.
  2. 확인 문제: Transformer 클래스에서 각 메서드는 어떤 역할을 하나요?
    a) 파싱 트리를 생성한다.
    b) 파싱 트리의 노드를 변환한다.
    c) 입력 데이터를 토큰으로 분해한다.
    답변: b) 파싱 트리의 노드를 변환한다.
  3. 실습 문제: 다음 산술 표현식 3 + 4 * 2 / (1 - 5) ** 2를 계산하는 파서를 Lark를 사용하여 작성해 보세요.
This post is licensed under CC BY 4.0 by the author.