Python - лексический анализ и токенизация
Я хочу немного ускорить процесс открытия, так как это мое первое предприятие в мире лексического анализа. Возможно, это даже неправильный путь. Во-первых, я опишу свою проблему:
У меня очень большие файлы свойств (порядка 1000 свойств), которые при дистилляции на самом деле составляют примерно 15 важных свойств, а остальные могут генерироваться или редко меняться.
Итак, например:
general {
name = myname
ip = 127.0.0.1
}
component1 {
key = value
foo = bar
}
Это тип формата, который я хочу создать для tokenize что-то вроде:
property.${general.name}blah.home.directory = /blah
property.${general.name}.ip = ${general.ip}
property.${component1}.ip = ${general.ip}
property.${component1}.foo = ${component1.foo}
в
property.mynameblah.home.directory = /blah
property.myname.ip = 127.0.0.1
property.component1.ip = 127.0.0.1
property.component1.foo = bar
Лексический анализ и токенизация звучат как мой лучший маршрут, но это очень простая форма. Это простая грамматика, простая подстановка, и я хотел бы убедиться, что я не приношу кувалду, чтобы постучать в гвоздь.
Я мог бы создать свой собственный лексер и токенизатор, или ANTlr - возможность, но мне не нравится повторно изобретать колесо, и ANTlr звучит как overkill.
Я не знаком с методами компилятора, поэтому наиболее ценятся указатели в правильном направлении и коде.
Примечание. Я могу изменить формат ввода.
Ответы
Ответ 1
Там есть отличная статья о Использование регулярных выражений для лексического анализа в effbot.org.
Адаптация токенизатора к вашей проблеме:
import re
token_pattern = r"""
(?P<identifier>[a-zA-Z_][a-zA-Z0-9_]*)
|(?P<integer>[0-9]+)
|(?P<dot>\.)
|(?P<open_variable>[$][{])
|(?P<open_curly>[{])
|(?P<close_curly>[}])
|(?P<newline>\n)
|(?P<whitespace>\s+)
|(?P<equals>[=])
|(?P<slash>[/])
"""
token_re = re.compile(token_pattern, re.VERBOSE)
class TokenizerException(Exception): pass
def tokenize(text):
pos = 0
while True:
m = token_re.match(text, pos)
if not m: break
pos = m.end()
tokname = m.lastgroup
tokvalue = m.group(tokname)
yield tokname, tokvalue
if pos != len(text):
raise TokenizerException('tokenizer stopped at pos %r of %r' % (
pos, len(text)))
Чтобы проверить это, мы делаем:
stuff = r'property.${general.name}.ip = ${general.ip}'
stuff2 = r'''
general {
name = myname
ip = 127.0.0.1
}
'''
print ' stuff '.center(60, '=')
for tok in tokenize(stuff):
print tok
print ' stuff2 '.center(60, '=')
for tok in tokenize(stuff2):
print tok
для
========================== stuff ===========================
('identifier', 'property')
('dot', '.')
('open_variable', '${')
('identifier', 'general')
('dot', '.')
('identifier', 'name')
('close_curly', '}')
('dot', '.')
('identifier', 'ip')
('whitespace', ' ')
('equals', '=')
('whitespace', ' ')
('open_variable', '${')
('identifier', 'general')
('dot', '.')
('identifier', 'ip')
('close_curly', '}')
========================== stuff2 ==========================
('newline', '\n')
('identifier', 'general')
('whitespace', ' ')
('open_curly', '{')
('newline', '\n')
('whitespace', ' ')
('identifier', 'name')
('whitespace', ' ')
('equals', '=')
('whitespace', ' ')
('identifier', 'myname')
('newline', '\n')
('whitespace', ' ')
('identifier', 'ip')
('whitespace', ' ')
('equals', '=')
('whitespace', ' ')
('integer', '127')
('dot', '.')
('integer', '0')
('dot', '.')
('integer', '0')
('dot', '.')
('integer', '1')
('newline', '\n')
('close_curly', '}')
('newline', '\n')
Ответ 2
Ибо так же, как кажется ваш формат, я думаю, что полный парсер/лексер будет излишне. Кажется, что сочетание регулярных выражений и манипуляций с строками сделают трюк.
Другая идея - изменить файл на что-то вроде json или xml и использовать существующий пакет.
Ответ 3
Простой DFA хорошо работает для этого. Вам нужно всего несколько состояний:
- Поиск
${
- Видно, что
${
ищет хотя бы один действительный символ, формирующий имя
- Виден хотя бы один действительный символ имени, ища больше именных символов или
}
.
Если файл свойств не согласован с порядком, вам может понадобиться двухпроцессорный процессор для проверки правильности разрешения каждого имени.
Конечно, вам тогда нужно написать код подстановки, но как только у вас есть список всех используемых имен, самой простой возможной реализацией является поиск/замена на ${name}
с его соответствующим значением.
Ответ 4
Если вы можете изменить формат входных файлов, вы можете использовать синтаксический анализатор для существующего формата, например JSON.
Однако из вашего заявления о проблемах это звучит так, как будто это не так. Поэтому, если вы хотите создать собственный лексер и парсер, используйте PLY (Python Lex/Yacc). Он прост в использовании и работает так же, как lex/yacc.
Вот ссылка на example калькулятора, построенного с использованием PLY. Обратите внимание, что все, начиная с t_
, является правилом lexer, определяющим действительный токен, и все, начиная с p_
, является правилом синтаксического анализатора, определяющим постановку грамматики.
Ответ 5
Синтаксис, который вы предоставляете, похож на Механизм шаблонов Mako. Я думаю, вы могли бы попробовать, это довольно простой API.