이번 글에서는 토큰 분석기, Lexer를 파이썬을 이용해 만들어 보는 내용을 담았습니다.
이 글은 https://riptutorial.com/en/python/example/31584을 참고하여 작성하였습니다.
먼저 ply 모듈에 대한 설치가 필요합니다.
conda나 pip 명령어를 활용하여, ply 모듈을 다운로드하여 주세요.
다운로드가 잘 되어있는지 확인은
import ply.lex 를 통해 확인하실 수 있습니다.
Code
전체적인 코드입니다.
import ply.lex as lex
tokens = [ # 사용되는 토큰 정의하기, 필수
'NUMBER',
'PLUS',
'MINUS',
'TIMES',
'DIVIDE',
'LPAREN',
'RPAREN',
] # 토큰 별로 정규식이 정의되어야 한다 함수를 통해서 혹은 단순히 아래처럼, 둘 다 규칙은 t_이름
t_PLUS = r'\+'
t_MINUS = r'-'
t_TIMES = r'\*'
t_DIVIDE = r'/'
t_LPAREN = r'\('
t_RPAREN = r'\)'
# 값 변환 등 액션이 필요한 토큰은 함수로 정의한다.
def t_NUMBER(t): # t가 인스턴스 토큰
r'\d+'
t.value = int(t.value)
return t
def t_newline(t):
r'\n+'
t.lexer.lineno += len(t.value)
t_ignore = ' \t'
def t_error(t):
print("Illegal character '%s'" % t.value[0])
t.lexer.skip(1)
lexer = lex.lex()
while True:
tok = lexer.token()
if not tok:
break # No more input
print(tok)
1) tokens 라는 리스트로 사용할 토큰들을 정의하는 과정이 필요합니다.
위 코드에서는 수식을 토큰들로 나누는 예제입니다.
2) 토큰들을 정의한 후, 각각의 토큰들이 어떤 형식으로 표현되는 토큰들인지를 정의해야 합니다.
어떤 토큰을 정의하는 것인지를 표현할 때는 < t_토큰 이름 > 형식으로 사용하시면 됩니다.
간단하게 정의를 할 때는 아래와 같이 정의할 수 있습니다. 표현은 정규식의 형태로 되어 있습니다.
t_PLUS = r'\+'
t_MINUS = r'-'
t_TIMES = r'\*'
t_DIVIDE = r'/'
t_LPAREN = r'\('
t_RPAREN = r'\)'
토큰을 표현하면서 추가적인 작업이 필요한 경우에는 함수로 구현할 수 있습니다.
def t_NUMBER(t): # t가 인스턴스 토큰
r'\d+'
t.value = int(t.value)
return t
함수 정의 맨 윗줄의 정규식이 토큰의 표현식이 됩니다. 이 함수의 매개변수로 들어오는 t는 인스턴스 토큰입니다. 위 같은 경우에는 숫자 자료형으로 변환시켜주기 위해서 함수로 정의를 했습니다.
t는 다음과 같은 속성값들을 가지고 있습니다.
1) t.type : 토큰의 타입을 의미합니다. 기본값으로는 t_ 전두어 다음으로 들어오는 이름입니다.
2) t.value : 실제 텍스트 값을 의미합니다.
3) t.lineno : 몇번째 줄인지를 표시하는 값인데, lexer 자체로는 라인을 알 수 없기 때문에 아래와 같은 함수를 정의하면 됩니다.
def t_newline(t):
r'\n+'
t.lexer.lineno += len(t.value)
4) t.lexpos : 들어오는 글의 시작부터 상대적인 위치를 나타냅니다.
만약에 무시하고 싶은 토큰이라면 아래 두 가지 방법 중 한 가지를 쓰면 됩니다.
def t_COMMENT(t):
r'\#.*'
pass
t_ignore_COMMENT = r'\#.*'
함수로 정의할 땐 반환값을 적지 않으면 됩니다. 혹은 < t_ignore_토큰 이름 > 으로 더 간편하게 정의할 수 있습니다. 딱히 토큰의 이름을 정의하지 않고도 무시하고 싶다면 < t_ignore > 로 정의할 수 있습니다.
t_ignore = ' \t' # 스페이스와 탭을 무시하게 됩니다.
Literals : t.type 과 t.value가 문자 자체로 표현되는 토큰입니다.
literals = [ '+', '-', '*', '/' ]
literals = "+-*/"
윗 줄과 아랫 줄은 같은 의미입니다. Literal 또한 함수로 정의하여 추가적인 작업을 하게끔 할 수 있습니다.
literals = [ '{', '}' ]
def t_lbrace(t):
r'\{'
t.type = '{' # 토큰 타입을 매칭되는 Literal로 설정합니다. (Literal이라면 꼭 필요합니다.)
return t
에러는 t_error 함수로 다룰 수 있습니다.
def t_error(t):
print("옳지 않은 문자 '%s'" % t.value[0])
t.lexer.skip(1) # 몇개의 문자를 넘어가는 함수입니다.
모든 준비가 끝나셨으면 lexer를 만드시면 됩니다.
lexer = lex.lex()
이런 지금까지의 준비 코드는 임의의 class 안에 정의하여 사용할 수도 있습니다.
import ply.lex as lex
class MyLexer(object):
... # 위와 같은 내용들을 적습니다.
# Build the lexer
def build(self, **kwargs):
self.lexer = lex.lex(module=self, **kwargs)
def test(self, data):
self.lexer.input(data) # 데이터를 lexer에 넣는 함수입니다.
for token in self.lexer.token(): # 토큰들을 확인할 수 있습니다.
print(token)
m = MyLexer()
m.build()
m.test("3 + 4")
추가적으로 반복문을 활용해서 분리해낸 토큰들을 얻을 수도 있습니다.
for i in lexer:
print(i)
다음 글로는 Lex와 연계되어 자주 사용되는 Yacc 모듈에 대해 알아보겠습니다.
'AI > 머신러닝' 카테고리의 다른 글
[파이썬] UCI 데이터셋과 싸이킷런을 이용한 결정트리 만들기 (6) | 2019.09.30 |
---|