Примечание : Это не индоссамент. Скорее всего, существует множество других пакетов для разбора Python, которые предлагают аналогичную функциональность. Он представлен просто как возможный механизм для достижения (предполагаемой) цели.
Пакет синтаксического анализа Ply содержит генератор лексеров, но вы не обязаны его использовать; Вы можете использовать любую функцию, которую хотите, для предоставления лексических токенов. Токены - это просто типы объектов, которые включают атрибут value
. Однако Ply не делает никаких предположений об объекте value
, кроме того, что требует его существования.
Ссылаясь на руководство здесь и также здесь :
Когда токены возвращаются lex, они имеют значение, которое хранится в атрибуте value
. Обычно значением является текст, который был сопоставлен. Однако значение может быть присвоено любому объекту Python.
…
Если вы собираетесь создать рукописный лексер и планируете использовать его с ya cc .py, он должен соответствовать только следующие требования:
- Он должен предусматривать метод
token()
, который возвращает следующий токен, или None
, если токенов больше нет. token()
Метод должен возвращать объект tok
, имеющий атрибуты type
и value
. Если используется отслеживание номера строки, токен также должен определить атрибут lineno
.
Для передачи нетерминалов в анализатор вам потребуется чтобы они выглядели как терминалы. Самый простой способ сделать это - создать псевдотерминал для каждого нетерминала и преобразовать псевдотерминал в единицу продукции. Например, если предположить, что псевдо-терминалам даны имена, оканчивающиеся на _
(что облегчает их генерацию программным способом, вы можете изменить правило
B : b | a B
на
B : b
| a B
| B_
Если мы предполагаем, что вы вводите правильное значение AST в объект токена, возвращенный с помощью терминала B_
, все, что вам нужно, это добавить одну функцию в вашу грамматику:
def p_pseudo_terminals(p):
'''A : A_
B : B_
'''
p[0] = p[1]
Вот полный исполняемый пример с использованием грамматики в вопросе:
file: inject.py
from ply import yacc
from collections import namedtuple
Token = namedtuple('Token', ['type', 'value'])
Node = namedtuple('Node', ['type', 'children'])
tokens = ['a', 'b', 'A_', 'B_']
def p_S(p):
'''S : A B'''
p[0] = Node('S', [ p[1], p[2] ])
def p_A(p):
'''A : a'''
p[0] = Node('A', [ p[1] ])
def p_Al(p):
'''A : a A'''
p[0] = Node('A', [ p[1], p[2] ])
def p_B(p):
'''B : b'''
p[0] = Node('B', [ p[1] ])
def p_Bl(p):
'''B : a B'''
p[0] = Node('B', [ p[1], p[2] ])
def p_pseudo(p):
'''A : A_
B : B_
'''
p[0] = p[1]
class Lexer(object):
def __init__(self, iter):
self.iter = iter.__iter__()
def token(self):
try:
retval = next(self.iter)
if type(retval) == Token:
# Input is a token, just pass it through
return retval
else:
# Input is an AST node; fabricate a pseudo-token
return Token(retval.type + '_', retval)
except StopIteration:
return None
parser = yacc.yacc()
И примерный прогон:
$ python3
Python 3.6.9 (default, Nov 7 2019, 10:44:02)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from inject import *
>>> token_stream = [Token('a', 'a')] * 3 + [Node('B', ['a', Node('B', children=['a', Node('B', children=['b'])])])]
>>> lexer = Lexer(token_stream)
>>> print(parser.parse(None, lexer=lexer))
Node(type='S', children=[Node(type='A', children=['a', Node(type='A', children=['a', Node(type='A', children=['a'])])]), Node(type='B', children=['a', Node(type='B', children=['a', Node(type='B', children=['b'])])])])
В настоящая грамматика, может быть утомительно вводить все эти псевдо-токены и названия юнитов. Вы можете воспользоваться тем фактом, что вы можете генерировать строку документации самостоятельно, пока она существует, прежде чем создавать синтаксический анализатор, вызвав * 1063 Вам также нужно добавить псевдотокен в список имен токенов, чтобы Ply знал, что B_
- токен.