Python 은 간단 한 재 귀 하강 분석 기 를 실현 합 니 다.
문법 규칙 에 따라 텍스트 를 해석 하고 명령 을 수행 하거나 입력 을 대표 하 는 추상 문법 트 리 를 만 들 고 싶 습 니 다.문법 이 매우 간단 하 다 면 프레임 워 크 를 사용 하지 않 고 스스로 이 해석 기 를 쓸 수 있다.
해결 방안
이 문제 에서 우 리 는 특수 문법 에 따라 텍스트 를 해석 하 는 문 제 를 집중 적 으로 토론 한다.이렇게 하기 위해 서 는 먼저 BNF 나 EBNF 형식 으로 표준 문법 을 지정 해 야 한다.예 를 들 어 간단 한 수학 표현 식 문법 은 다음 과 같 을 수 있 습 니 다.
expr ::= expr + term
| expr - term
| term
term ::= term * factor
| term / factor
| factor
factor ::= ( expr )
| NUM
또는 EBNF 형식 으로:
expr ::= term { (+|-) term }*
term ::= factor { (*|/) factor }*
factor ::= ( expr )
| NUM
EBNF 에서
{...}*
에 포 함 된 규칙 은 선택 할 수 있 습 니 다.*0 회 또는 여러 번 반복 되 는 것 을 의미 합 니 다.현재 BNF 의 작업 체제 에 대해 잘 모 르 면 좌우 기호 가 서로 바 꿀 수 있 는 규칙 으로 생각 하 세 요.일반적으로 해석 의 원 리 는 입력 텍스트 와 문법 규칙 에 맞 게 BNF 를 이용 하여 여러 개의 교체 와 확장 을 완성 하 는 것 이다.프레젠테이션 을 위해 서
3 + 4 * 5
와 같은 표현 식 을 해석 하고 있다 고 가정 하 십시오.이 표현 식 은 먼저 2.18 절 에서 소개 한 기술 을 사용 하여 토 큰 흐름 으로 분해 해 야 합 니 다.결 과 는 다음 과 같은 토 큰 시퀀스 일 수 있 습 니 다.NUM + NUM * NUM
이 를 바탕 으로 해석 동작 은 문법 을 바 꾸 어 토 큰 을 입력 하려 고 합 니 다.
expr
expr ::= term { (+|-) term }*
expr ::= factor { (*|/) factor }* { (+|-) term }*
expr ::= NUM { (*|/) factor }* { (+|-) term }*
expr ::= NUM { (+|-) term }*
expr ::= NUM + term { (+|-) term }*
expr ::= NUM + factor { (*|/) factor }* { (+|-) term }*
expr ::= NUM + NUM { (*|/) factor}* { (+|-) term }*
expr ::= NUM + NUM * factor { (*|/) factor }* { (+|-) term }*
expr ::= NUM + NUM * NUM { (*|/) factor }* { (+|-) term }*
expr ::= NUM + NUM * NUM { (+|-) term }*
expr ::= NUM + NUM * NUM
아래 의 모든 해석 절 차 는 시간 이 좀 걸 릴 수 있 지만,그 원 리 는 입력 을 찾 아 문법 규칙 에 맞 게 시도 하 는 것 이다.첫 번 째 입력 토 큰 은 NUM 이기 때문에 바 꾸 기 는 먼저 그 부분 과 일치 합 니 다.매 칭 에 성공 하면 다음 토 큰+에 들 어 갑 니 다.다음 영패 와 일치 하지 않 는 것 이 확인 되 었 을 때 오른쪽 부분(예 를 들 어{(*/)factor}*)이 삭 제 됩 니 다.성공 적 인 해석 에서 전체 오른쪽 부분 이 입력 토 큰 흐름 과 일치 하도록 완전히 펼 쳐 집 니 다.
앞의 지식 배경 이 있 습 니 다.다음 에 우 리 는 간단 한 예 를 들 어 재 귀적 하강 식 구 치 프로그램 을 어떻게 구축 하 는 지 보 여 드 리 겠 습 니 다.
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
Topic:
Desc :
"""
import re
import collections
# Token specification
NUM = r'(?P<NUM>\d+)'
PLUS = r'(?P<PLUS>\+)'
MINUS = r'(?P<MINUS>-)'
TIMES = r'(?P<TIMES>\*)'
DIVIDE = r'(?P<DIVIDE>/)'
LPAREN = r'(?P<LPAREN>\()'
RPAREN = r'(?P<RPAREN>\))'
WS = r'(?P<WS>\s+)'
master_pat = re.compile('|'.join([NUM, PLUS, MINUS, TIMES,
DIVIDE, LPAREN, RPAREN, WS]))
# Tokenizer
Token = collections.namedtuple('Token', ['type', 'value'])
def generate_tokens(text):
scanner = master_pat.scanner(text)
for m in iter(scanner.match, None):
tok = Token(m.lastgroup, m.group())
if tok.type != 'WS':
yield tok
# Parser
class ExpressionEvaluator:
'''
Implementation of a recursive descent parser. Each method
implements a single grammar rule. Use the ._accept() method
to test and accept the current lookahead token. Use the ._expect()
method to exactly match and discard the next token on on the input
(or raise a SyntaxError if it doesn't match).
'''
def parse(self, text):
self.tokens = generate_tokens(text)
self.tok = None # Last symbol consumed
self.nexttok = None # Next symbol tokenized
self._advance() # Load first lookahead token
return self.expr()
def _advance(self):
'Advance one token ahead'
self.tok, self.nexttok = self.nexttok, next(self.tokens, None)
def _accept(self, toktype):
'Test and consume the next token if it matches toktype'
if self.nexttok and self.nexttok.type == toktype:
self._advance()
return True
else:
return False
def _expect(self, toktype):
'Consume next token if it matches toktype or raise SyntaxError'
if not self._accept(toktype):
raise SyntaxError('Expected ' + toktype)
# Grammar rules follow
def expr(self):
"expression ::= term { ('+'|'-') term }*"
exprval = self.term()
while self._accept('PLUS') or self._accept('MINUS'):
op = self.tok.type
right = self.term()
if op == 'PLUS':
exprval += right
elif op == 'MINUS':
exprval -= right
return exprval
def term(self):
"term ::= factor { ('*'|'/') factor }*"
termval = self.factor()
while self._accept('TIMES') or self._accept('DIVIDE'):
op = self.tok.type
right = self.factor()
if op == 'TIMES':
termval *= right
elif op == 'DIVIDE':
termval /= right
return termval
def factor(self):
"factor ::= NUM | ( expr )"
if self._accept('NUM'):
return int(self.tok.value)
elif self._accept('LPAREN'):
exprval = self.expr()
self._expect('RPAREN')
return exprval
else:
raise SyntaxError('Expected NUMBER or LPAREN')
def descent_parser():
e = ExpressionEvaluator()
print(e.parse('2'))
print(e.parse('2 + 3'))
print(e.parse('2 + 3 * 4'))
print(e.parse('2 + (3 + 4) * 5'))
# print(e.parse('2 + (3 + * 4)'))
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# File "exprparse.py", line 40, in parse
# return self.expr()
# File "exprparse.py", line 67, in expr
# right = self.term()
# File "exprparse.py", line 77, in term
# termval = self.factor()
# File "exprparse.py", line 93, in factor
# exprval = self.expr()
# File "exprparse.py", line 67, in expr
# right = self.term()
# File "exprparse.py", line 77, in term
# termval = self.factor()
# File "exprparse.py", line 97, in factor
# raise SyntaxError("Expected NUMBER or LPAREN")
# SyntaxError: Expected NUMBER or LPAREN
if __name__ == '__main__':
descent_parser()
토론 하 다.텍스트 해석 은 매우 큰 주제 로 학생 들 이 컴 파일 과정 을 공부 할 때 처음 시작 하 는 3 주 를 차지한다.문법,해석 알고리즘 등 배경 지식 을 찾 고 있다 면 컴 파일 러 책 을 읽 어 봐 야 한다.이 방면 에 관 한 내용 이 너무 많아 서 여기 서 다 펼 칠 수 없 음 이 분명 하 다.
그럼 에 도 불구 하고 재 귀 하강 해석 기 를 만 드 는 전체적인 사 고 는 간단 하 다.시작 할 때,당신 은 먼저 모든 문법 규칙 을 얻 은 후에 그것 을 함수 나 방법 으로 바 꿉 니 다.그래서 만약 당신 의 문법 이 이와 비슷 하 다 면:
expr ::= term { ('+'|'-') term }*
term ::= factor { ('*'|'/') factor }*
factor ::= '(' expr ')'
| NUM
너 는 먼저 그것들 을 아래 와 같은 방법 으로 바 꿔 야 한다.
class ExpressionEvaluator:
...
def expr(self):
...
def term(self):
...
def factor(self):
...
모든 방법 이 완성 해 야 할 임 무 는 간단 합 니 다.-문법 규칙 의 모든 부분 을 왼쪽 에서 오른쪽으로 옮 겨 다 니 며 모든 토 큰 을 처리 해 야 합 니 다.어떤 의미 에서 볼 때 방법의 목적 은 문법 규칙 을 다 처리 하거나 문법 오류 가 발생 하 는 것 이다.이렇게 하기 위해 서 는 아래 의 이러한 실현 방법 을 채택 해 야 한다.factor ::= '('expr ')'
에서 expr 에 대한 호출)을 호출 할 수 있다.이것 이 바로 알고리즘 중의'재 귀'의 유래 이다.4567918) _expect()
방법 은 바로 이 단 계 를 하 는 데 쓰 인 다.4567918) _accept()
방법의 목적 이다.그것 은 에 해당 한다expect()방법의 약화 버 전 입 니 다.일치 하 는 버 전 을 찾 으 면 계속 되 지만 찾 지 못 하면 오류 가 발생 하지 않 고 스크롤 백(후속 검 사 를 계속 할 수 있 도록 합 니 다).::= term { ('+'|'-') term }*
에서 반복 동작 은 while 순환 을 통 해 이 루어 집 니 다.순환 주 체 는 다른 요소 가 없 을 때 까지 모든 중복 요 소 를 수집 하거나 처리 합 니 다.그 중의 한 계 는 왼쪽 재 귀 를 포함 하 는 문법 규칙 에 사용 되 지 못 한 다 는 것 이다.예 를 들 어 다음 규칙 을 번역 해 야 한다 면:
items ::= items ',' item
| item
이렇게 하기 위해 서 는 아래 와 같이items()
방법 을 사용 할 수 있 습 니 다.
def items(self):
itemsval = self.items()
if itemsval and self._accept(','):
itemsval.append(self.item())
else:
itemsval = [ self.item() ]
유일한 문 제 는 이 방법 이 근본적으로 일 을 할 수 없다 는 것 이다.사실상 그것 은 무한 한 귀속 오류 가 발생 할 수 있다.문법 규칙 자체 에 대해 서도 까다 로 운 문제 에 부 딪 힐 수 있다.예 를 들 어 다음 과 같은 간단 한 문법 표현 이 적절 한 지 알 고 싶 을 수도 있 습 니 다.
expr ::= factor { ('+'|'-'|'*'|'/') factor }*
factor ::= '(' expression ')'
| NUM
이 문법 은 보기 에는 아무런 문제 가 없 는 것 같 지만,표준 사 칙 연산 중의 연산 자 우선 순 위 를 알 아차 리 지 못 한다.예 를 들 어 표현 식'3 + 4 * 5"
은 기대 하 는 23 이 아 닌 35 를 얻 을 수 있 습 니 다.'expr'와'term'규칙 을 분리 해서 사용 하면 올 바른 작업 을 할 수 있 습 니 다.복잡 한 문법 에 대해 서 는 PyParsing 이나 PLY 같은 해석 도 구 를 선택 하 는 것 이 좋 습 니 다.다음은 PLY 를 사용 하여 표현 식 값 을 구 하 는 프로그램의 코드 를 다시 쓰 는 것 입 니 다.
from ply.lex import lex
from ply.yacc import yacc
# Token list
tokens = [ 'NUM', 'PLUS', 'MINUS', 'TIMES', 'DIVIDE', 'LPAREN', 'RPAREN' ]
# Ignored characters
t_ignore = ' \t
'
# Token specifications (as regexs)
t_PLUS = r'\+'
t_MINUS = r'-'
t_TIMES = r'\*'
t_DIVIDE = r'/'
t_LPAREN = r'\('
t_RPAREN = r'\)'
# Token processing functions
def t_NUM(t):
r'\d+'
t.value = int(t.value)
return t
# Error handler
def t_error(t):
print('Bad character: {!r}'.format(t.value[0]))
t.skip(1)
# Build the lexer
lexer = lex()
# Grammar rules and handler functions
def p_expr(p):
'''
expr : expr PLUS term
| expr MINUS term
'''
if p[2] == '+':
p[0] = p[1] + p[3]
elif p[2] == '-':
p[0] = p[1] - p[3]
def p_expr_term(p):
'''
expr : term
'''
p[0] = p[1]
def p_term(p):
'''
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]
def p_term_factor(p):
'''
term : factor
'''
p[0] = p[1]
def p_factor(p):
'''
factor : NUM
'''
p[0] = p[1]
def p_factor_group(p):
'''
factor : LPAREN expr RPAREN
'''
p[0] = p[2]
def p_error(p):
print('Syntax error')
parser = yacc()
이 프로그램 에서 모든 코드 는 비교적 높 은 단계 에 있다.영패 에 정규 표현 식 과 규칙 이 일치 할 때의 고급 처리 함수 만 쓰 면 됩 니 다.실제 실행 해상도 기,영 패 를 받 아들 이 는 등 바 텀 동작 은 라 이브 러 리 함수 에 의 해 이 루어 졌 다.다음은 분석 대상 을 어떻게 사용 하 는 지 예 입 니 다.
>>> parser.parse('2')
2
>>> parser.parse('2+3')
5
>>> parser.parse('2+(3+4)*5')
37
>>>
프로 그래 밍 과정 에서 도전 과 자극 을 주 고 싶다 면 해상도 와 컴 파일 러 를 만 드 는 것 이 좋 습 니 다.또한 컴 파일 러 의 책 에는 밑바닥 의 이론 지식 이 많이 담 겨 있다.하지만 좋 은 자원 도 인터넷 에서 찾 을 수 있다.Python 자체 ast 모듈 도 가 볼 만하 다.이상 은 Python 이 간단 한 재 귀 하강 분석 기 를 실현 하 는 상세 한 내용 입 니 다.Python 이 재 귀 하강 분석 기 를 실현 하 는 데 관 한 자 료 는 우리 의 다른 관련 글 을 주목 하 세 요!
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Python의 None과 NULL의 차이점 상세 정보그래서 대상 = 속성 + 방법 (사실 방법도 하나의 속성, 데이터 속성과 구별되는 호출 가능한 속성 같은 속성과 방법을 가진 대상을 클래스, 즉 Classl로 분류할 수 있다.클래스는 하나의 청사진과 같아서 하나의 ...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.