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.