Python Language
Python Lex-Yacc
수색…
소개
PLY는 인기있는 컴파일러 생성 도구 lex 및 yacc의 순수 Python 구현입니다.
비고
PLY 시작하기
Python2 / 3 용으로 PLY를 설치하려면 아래에 설명 된 단계를 따르십시오.
- 여기 에서 소스 코드를 다운로드 하십시오 .
- 다운로드 한 zip 파일의 압축을 풉니 다.
- 압축을 푼
ply-3.10
폴더로 이동하십시오. - 터미널에서 다음 명령을 실행하십시오 :
python setup.py install
위의 작업을 완료했다면 이제 PLY 모듈을 사용할 수 있습니다. 파이썬 인터프리터를 열고 import ply.lex
입력하여 테스트해볼 수 있습니다.
참고 : 사용하지 마십시오 pip
PLY를 설치하기 위해서는 컴퓨터에 깨진 분포를 설치합니다.
"안녕, 세상!" 의 PLY - 간단한 계산기
간단한 예제를 통해 PLY의 힘을 설명해 보겠습니다.이 프로그램은 산술 식을 문자열 입력으로 사용하여이를 해결하려고 시도합니다.
좋아하는 편집기를 열고 다음 코드를 복사하십시오.
from ply import lex
import ply.yacc as yacc
tokens = (
'PLUS',
'MINUS',
'TIMES',
'DIV',
'LPAREN',
'RPAREN',
'NUMBER',
)
t_ignore = ' \t'
t_PLUS = r'\+'
t_MINUS = r'-'
t_TIMES = r'\*'
t_DIV = r'/'
t_LPAREN = r'\('
t_RPAREN = r'\)'
def t_NUMBER( t ) :
r'[0-9]+'
t.value = int( t.value )
return t
def t_newline( t ):
r'\n+'
t.lexer.lineno += len( t.value )
def t_error( t ):
print("Invalid Token:",t.value[0])
t.lexer.skip( 1 )
lexer = lex.lex()
precedence = (
( 'left', 'PLUS', 'MINUS' ),
( 'left', 'TIMES', 'DIV' ),
( 'nonassoc', 'UMINUS' )
)
def p_add( p ) :
'expr : expr PLUS expr'
p[0] = p[1] + p[3]
def p_sub( p ) :
'expr : expr MINUS expr'
p[0] = p[1] - p[3]
def p_expr2uminus( p ) :
'expr : MINUS expr %prec UMINUS'
p[0] = - p[2]
def p_mult_div( p ) :
'''expr : expr TIMES expr
| expr DIV expr'''
if p[2] == '*' :
p[0] = p[1] * p[3]
else :
if p[3] == 0 :
print("Can't divide by 0")
raise ZeroDivisionError('integer division by 0')
p[0] = p[1] / p[3]
def p_expr2NUM( p ) :
'expr : NUMBER'
p[0] = p[1]
def p_parens( p ) :
'expr : LPAREN expr RPAREN'
p[0] = p[2]
def p_error( p ):
print("Syntax error in input!")
parser = yacc.yacc()
res = parser.parse("-4*-(3-5)") # the input
print(res)
이 파일을 calc.py
로 저장하고 실행하십시오.
산출:
-8
-4 * - (3 - 5)
대한 올바른 대답은 무엇입니까?
파트 1 : Lex로 입력 토큰 화하기
예제 1의 코드가 수행 한 두 단계는 입력을 토큰 화 하는 것입니다. 즉, 산술 표현식을 구성하는 기호를 찾는 것이고 두 번째 단계는 추출 된 토큰을 분석하고 결과를 평가하는 파싱 입니다.
이 절에서는 사용자 입력을 토큰 화 한 다음 줄 단위로 구분하는 간단한 예제를 제공합니다.
import ply.lex as lex
# List of token names. This is always required
tokens = [
'NUMBER',
'PLUS',
'MINUS',
'TIMES',
'DIVIDE',
'LPAREN',
'RPAREN',
]
# Regular expression rules for simple tokens
t_PLUS = r'\+'
t_MINUS = r'-'
t_TIMES = r'\*'
t_DIVIDE = r'/'
t_LPAREN = r'\('
t_RPAREN = r'\)'
# A regular expression rule with some action code
def t_NUMBER(t):
r'\d+'
t.value = int(t.value)
return t
# Define a rule so we can track line numbers
def t_newline(t):
r'\n+'
t.lexer.lineno += len(t.value)
# A string containing ignored characters (spaces and tabs)
t_ignore = ' \t'
# Error handling rule
def t_error(t):
print("Illegal character '%s'" % t.value[0])
t.lexer.skip(1)
# Build the lexer
lexer = lex.lex()
# Give the lexer some input
lexer.input(data)
# Tokenize
while True:
tok = lexer.token()
if not tok:
break # No more input
print(tok)
이 파일을 calclex.py
로 저장하십시오. 우리는 Yacc 파서를 만들 때 이것을 사용할 것입니다.
고장
import ply.lex
사용하여 모듈import ply.lex
모든 렉서는 렉서가 생성 할 수있는 모든 가능한 토큰 이름을 정의하는
tokens
이라고하는 목록을 제공해야합니다. 이 목록은 항상 필요합니다.tokens = [ 'NUMBER', 'PLUS', 'MINUS', 'TIMES', 'DIVIDE', 'LPAREN', 'RPAREN', ]
tokens
은 문자열이 아닌 문자열의 튜플 일 수도 있습니다. 각 문자열은 이전과 같이 토큰을 나타냅니다.
각 문자열에 대한 정규식 규칙은 문자열 또는 함수로 정의 할 수 있습니다. 두 경우 모두, 변수 이름 앞에는 토큰을 일치시키는 규칙임을 나타내는 t_가 붙어야합니다.
간단한 토큰의 경우 정규 표현식을 문자열로 지정할 수 있습니다.
t_PLUS = r'\+'
일종의 작업을 수행해야하는 경우 토큰 규칙을 함수로 지정할 수 있습니다.
def t_NUMBER(t): r'\d+' t.value = int(t.value) return t
규칙은 함수 내에서 문서 문자열로 지정됩니다. 이 함수는
LexToken
의 인스턴스 인 하나의 인수를 받아들이고 일부 동작을 수행 한 다음 인수를 반환합니다.문서 문자열을 지정하는 대신 함수의 정규식 규칙으로 외부 문자열을 사용하려면 다음 예제를 고려하십시오.
@TOKEN(identifier) # identifier is a string holding the regex def t_ID(t): ... # actions
LexToken
객체 (이 객체를t
라고 부름)의 인스턴스에는 다음과 같은 속성이 있습니다.-
t.type
은 토큰 유형 (문자열)입니다 (예 :'NUMBER'
,'PLUS'
등). 기본적으로t.type
은t_
접두어 다음의 이름으로 설정됩니다. - 어휘 (실제 텍스트가 일치 함) 인
t.value
- 현재 라인 번호 인
t.lineno
(이것은 렉서가 라인 번호를 전혀t.lineno
때문에 자동으로 업데이트되지 않는다).t_newline
이라는 함수를 사용하여 lineno를 업데이트t_newline
.
def t_newline(t): r'\n+' t.lexer.lineno += len(t.value)
-
t.lexpos
는 입력 텍스트의 시작 부분에 상대적인 토큰의 위치입니다.
-
regex 규칙 함수에서 아무 것도 반환되지 않으면 토큰이 삭제됩니다. 토큰을 버리려면 동일한 규칙에 대한 함수를 정의하는 대신 t_ignore_ 접두사를 정규식 규칙 변수에 추가 할 수 있습니다.
def t_COMMENT(t): r'\#.*' pass # No return value. Token discarded
...와 같다:
t_ignore_COMMENT = r'\#.*'
댓글을 볼 때 어떤 조치를 취하는 경우 이는 물론 잘못된 것입니다. 어떤 경우에는 함수를 사용하여 정규식 규칙을 정의하십시오.
일부 문자에 대한 토큰을 정의하지 않았지만 여전히 무시하려면
t_ignore = "<characters to ignore>"
(이 접두어는 필수입니다).t_ignore_COMMENT = r'\#.*' t_ignore = ' \t' # ignores spaces and tabs
마스터 정규 표현식을 만들 때, lex는 다음과 같이 파일에 지정된 정규 표현식을 추가합니다.
- 함수로 정의 된 토큰은 파일에 표시된 것과 동일한 순서로 추가됩니다.
- 문자열로 정의 된 토큰은 해당 토큰의 정규식을 정의하는 문자열의 문자열 길이가 감소하는 순서로 추가됩니다.
동일한 파일에서
==
및=
과 일치하는==
규칙을 활용하십시오.
리터럴은 반환되는 토큰입니다.
t.type
과t.value
모두 문자 자체로 설정됩니다. 다음과 같이 리터럴 목록을 정의하십시오.literals = [ '+', '-', '*', '/' ]
또는,
literals = "+-*/"
리터럴이 일치 할 때 추가 작업을 수행하는 토큰 함수를 작성할 수 있습니다. 그러나 토큰 유형을 적절하게 설정해야합니다. 예 :
literals = [ '{', '}' ] def t_lbrace(t): r'\{' t.type = '{' # Set token type to the expected literal (ABSOLUTE MUST if this is a literal) return t
t_error 함수로 오류를 처리하십시오.
# Error handling rule def t_error(t): print("Illegal character '%s'" % t.value[0]) t.lexer.skip(1) # skip the illegal token (don't process it)
일반적으로
t.lexer.skip(n)
은 입력 문자열에서 n 문자를 건너 뜁니다.
최종 준비 :
lexer = lex.lex()
사용하여lexer = lex.lex()
빌드하십시오.모든 것을 클래스 안에 넣고 클래스의 인스턴스를 호출하여 렉서를 정의 할 수도 있습니다. 예 :
import ply.lex as lex class MyLexer(object): ... # everything relating to token rules and error handling comes here as usual # Build the lexer def build(self, **kwargs): self.lexer = lex.lex(module=self, **kwargs) def test(self, data): self.lexer.input(data) for token in self.lexer.token(): print(token) # Build the lexer and try it out m = MyLexer() m.build() # Build the lexer m.test("3 + 4") #
데이터가 문자열 인
lexer.input(data)
사용하여 입력 제공토큰을 얻으려면 일치하는 토큰을 반환하는
lexer.token()
을 사용lexer.token()
. 다음과 같이 루프에서 렉서를 반복 할 수 있습니다.for i in lexer: print(i)
2 부 : Yacc로 토큰 화 된 입력 분석
이 섹션에서는 Part 1의 토큰 화 된 입력을 처리하는 방법을 설명합니다. 이는 컨텍스트 프리 그래머 (CFG)를 사용하여 수행됩니다. 문법을 지정해야하며 토큰은 문법에 따라 처리됩니다. 후드 아래에서 파서는 LALR 파서를 사용합니다.
# Yacc example
import ply.yacc as yacc
# Get the token map from the lexer. This is required.
from calclex import tokens
def p_expression_plus(p):
'expression : expression PLUS term'
p[0] = p[1] + p[3]
def p_expression_minus(p):
'expression : expression MINUS term'
p[0] = p[1] - p[3]
def p_expression_term(p):
'expression : term'
p[0] = p[1]
def p_term_times(p):
'term : term TIMES factor'
p[0] = p[1] * p[3]
def p_term_div(p):
'term : term DIVIDE factor'
p[0] = p[1] / p[3]
def p_term_factor(p):
'term : factor'
p[0] = p[1]
def p_factor_num(p):
'factor : NUMBER'
p[0] = p[1]
def p_factor_expr(p):
'factor : LPAREN expression RPAREN'
p[0] = p[2]
# Error rule for syntax errors
def p_error(p):
print("Syntax error in input!")
# Build the parser
parser = yacc.yacc()
while True:
try:
s = raw_input('calc > ')
except EOFError:
break
if not s: continue
result = parser.parse(s)
print(result)
고장
각 문법 규칙은 해당 함수에 대한 docstring이 적절한 문맥 - 자유 문법 명세를 포함하는 함수에 의해 정의된다. 함수 본문을 구성하는 명령문은 규칙의 의미 론적 작업을 구현합니다. 각 함수는 해당 규칙에서 각 문법 기호의 값을 포함하는 시퀀스 인 단일 인수 p를 허용합니다.
p[i]
의 값은 다음과 같이 문법 기호에 매핑됩니다.def p_expression_plus(p): 'expression : expression PLUS term' # ^ ^ ^ ^ # p[0] p[1] p[2] p[3] p[0] = p[1] + p[3]
토큰의 경우 해당
p[i]
의 "값"은 렉서 모듈에 할당 된p.value
속성과 동일합니다. 그래서PLUS
는+
값을 갖습니다.비 터미널의 경우, 값은
p[0]
있는 값에 의해 결정됩니다. 아무 것도 배치되지 않으면 값은 없음입니다. 또한,p[-1]
과 동일하지p[3]
이후,p
(a 단순리스트 아니다p[-1]
매립 작업을 지정할 수있다 (생략)이 여기에 설명).
이 함수는 p_
가 앞에 오는 한 어떤 이름이라도 가질 수 있습니다.
p_error(p)
규칙은 구문 오류를 catch하도록 정의됩니다 (yacc / bison의yyerror
와 동일).여러 문법 규칙을 하나의 함수로 결합 할 수 있습니다. 프로덕션이 비슷한 구조를 갖는다면 좋은 아이디어입니다.
def p_binary_operators(p): '''expression : expression PLUS term | expression MINUS term term : term TIMES factor | term DIVIDE factor''' if p[2] == '+': p[0] = p[1] + p[3] elif p[2] == '-': p[0] = p[1] - p[3] elif p[2] == '*': p[0] = p[1] * p[3] elif p[2] == '/': p[0] = p[1] / p[3]
토큰 대신 문자 리터럴을 사용할 수 있습니다.
def p_binary_operators(p): '''expression : expression '+' term | expression '-' term term : term '*' factor | term '/' factor''' if p[2] == '+': p[0] = p[1] + p[3] elif p[2] == '-': p[0] = p[1] - p[3] elif p[2] == '*': p[0] = p[1] * p[3] elif p[2] == '/': p[0] = p[1] / p[3]
물론, 리터럴은 렉서 모듈에서 지정되어야합니다.
빈 프로덕션은
'''symbol : '''
형식을 가지고 있습니다'''symbol : '''
시작 심볼을 명시 적으로 설정하려면
start = 'foo'
. 여기서foo
는 일부 비 터미널입니다.우선 순위 및 연관성 설정은 우선 순위 변수를 사용하여 수행 할 수 있습니다.
precedence = ( ('nonassoc', 'LESSTHAN', 'GREATERTHAN'), # Nonassociative operators ('left', 'PLUS', 'MINUS'), ('left', 'TIMES', 'DIVIDE'), ('right', 'UMINUS'), # Unary minus operator )
토큰은 우선 순위가 낮거나 높은 순서로 정렬됩니다.
nonassoc
은 해당 토큰이 연결되지 않음을 의미합니다. 즉a < b < c
와 같은 것은 불법이며a < b
는 여전히 합법적입니다.parser.out
은 yacc 프로그램이 처음 실행될 때 작성되는 디버깅 파일입니다. 시프트 / 감소 충돌이 발생할 때마다 파서가 항상 시프트됩니다.