Насколько я знаю, PLY не реализует интерфейс синтаксического анализа, так как это наиболее легко решить эту проблему с бизоном. Тем не менее, очень легко ввести свою собственную lexer-оболочку, которая может обрабатывать очередь токенов-разделителей.
Минимальная реализация лексера должна реализовать метод , который возвращает объект с атрибутами type
и value
. (Вам также понадобится, если ваш парсер использует его, но я не буду беспокоиться об этом здесь.)
Теперь предположим, что базовый (PLY-сгенерированный) лексер производит токены NEWLINE
, значение которых является длиной ведущей пробел после новой строки. Если некоторые строки не участвуют в алгоритме INDENT/DEDENT, NEWLINE
должен быть подавлен для этих строк; мы не рассматриваем этот случай здесь. Упрощенная функция примера лексического (который работает только с пробелами, а не вкладки) может быть:
# This function doesn't handle tabs. Beware!
def t_NEWLINE(self, t):
r'\n(?:\s*(?:[#].*)?\n)*\s*'
t.value = len(t.value) - 1 - t.value.rfind('\n')
return t
Теперь оберните PLY сгенерированных лексер с оболочкой, которая имеет дело с абзацами:
# WARNING:
# This code hasn't been tested much and it also may be inefficient
# and/or inexact. It doesn't do python-style tab handling. Etc. etc.
from collections import namedtuple, deque
# These are the tokens. We only generate one of each here. If
# we used lineno or didn't trust the parser to not mess with the
# token, we could generate a new one each time.
IndentToken = namedtuple('Token', 'type value')
dedent = IndentToken('DEDENT', None)
indent = IndentToken('INDENT', None)
newline= IndentToken('NEWLINE', None)
class IndentWrapper(object):
def __init__(self, lexer):
"""Create a new wrapper given the lexer which is being wrapped"""
self.lexer = lexer
self.indent_stack = [0]
# A queue is overkill for this case, but it's simple.
self.token_queue = deque()
# This is just in case the ply-generated lexer cannot be called again
# after it returns None.
self.eof_reached = False
def token(self):
"""Return the next token, or None if end of input has been reached"""
# Do we have any queued tokens?
if self.token_queue:
return self.token_queue.popleft()
# Are we done?
if self.eof_reached:
return None
# Get a token
t = self.lexer.token()
if t is None:
# At end of input, we might need to send some dedents
self.eof_reached = True
if len(self.indent_stack) > 1:
t = dedent
for i in range(len(self.indent_stack) - 1):
self.token_queue.append(dedent)
self.indent_stack = [0]
elif t.type == "NEWLINE":
# The NEWLINE token includes the amount of leading whitespace.
# Fabricate indent or dedents as/if necessary and queue them.
if t.value > self.indent_stack[-1]:
self.indent_stack.append(t.value)
self.token_queue.append(indent)
else:
while t.value < self.indent_stack[-1]:
self.indent_stack.pop()
self.token_queue.append(dedent)
if t.value != self.indent_stack[-1]:
raise IndentError # Or however you indicate errors
return t