Python - лексический анализ и токенизация

Я обращаюсь к скорости вдоль своего процесса открытия здесь вполне немного, поскольку это - мое первое предприятие в мир лексического анализа. Возможно, это - даже неправильный путь. Во-первых, я опишу свою проблему:

У меня есть очень большие файлы свойств (в порядке 1 000 свойств), которые при дистилляции являются действительно примерно 15 важными свойствами, и остальные могут быть сгенерированы или редко когда-либо изменяться.

Так, например:

general {
  name = myname
  ip = 127.0.0.1
}

component1 {
   key = value
   foo = bar
}

Это - тип формата, который я хочу создать для маркирования чего-то как:

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 походит на излишество.

Я не знаком с методами компилятора, таким образом, указатели в правильном направлении и коде больше всего ценились бы.

Примечание: Я могу изменить формат ввода.

12
задан Philip Reynolds 1 March 2010 в 20:44
поделиться

5 ответов

На сайте 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

for:

========================== 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')
12
ответ дан 2 December 2019 в 19:31
поделиться

Каким бы простым ни был ваш формат, я думаю, что полноценный синтаксический анализатор / лексер будет излишним. Похоже, что комбинация регулярных выражений и манипуляции со строками поможет.

Другая идея - изменить файл на что-то вроде json или xml и использовать существующий пакет.

2
ответ дан 2 December 2019 в 19:31
поделиться

Для этого хорошо работает простой DFA. Вам нужно всего несколько состояний:

  1. Ищу $ {
  2. Просмотрено $ { ищу хотя бы один допустимый символ, образующий имя
  3. Просмотрен хотя бы один допустимый символ имени, ищите другие символы имени или } .

Если файл свойств не зависит от порядка, вам может потребоваться двухпроходный процессор для проверки правильности разрешения каждого имени.

Конечно, затем вам нужно написать код замены, но как только у вас есть список всех используемых имен, простейшей возможной реализацией будет поиск / замена на $ {name} с соответствующими значение.

2
ответ дан 2 December 2019 в 19:31
поделиться

Если вы можете изменить формат входных файлов, вы можете использовать синтаксический анализатор для существующего формата, такого как JSON.

Однако из вашей постановки проблемы похоже, что это не так. Поэтому, если вы хотите создать собственный лексический анализатор и анализатор, используйте PLY (Python Lex / Yacc). Он прост в использовании и работает так же, как lex / yacc.

Вот ссылка на пример калькулятора, построенного с использованием PLY. Обратите внимание, что все, что начинается с t_ , является правилом лексера, определяющим действительный токен, а все, что начинается с p_ , является правилом синтаксического анализатора, определяющим создание грамматики.

1
ответ дан 2 December 2019 в 19:31
поделиться

Предоставляемый вами синтаксис похож на механизм шаблонов Mako . Думаю, вы могли бы попробовать, это довольно простой API.

1
ответ дан 2 December 2019 в 19:31
поделиться
Другие вопросы по тегам:

Похожие вопросы: