Python - лексический анализ и токенизация - PullRequest
12 голосов
/ 01 марта 2010

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

У меня есть очень большие файлы свойств (порядка 1000 свойств), которые, будучи дистиллированными, на самом деле представляют собой всего лишь 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 звучит как излишнее.

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

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

Ответы [ 5 ]

12 голосов
/ 02 марта 2010

Прекрасная статья о Использование регулярных выражений для лексического анализа

Адаптация токенизатора к вашей проблеме:

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 голосов
/ 01 марта 2010

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

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

2 голосов
/ 02 марта 2010

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

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

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

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

1 голос
/ 02 марта 2010

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

1 голос
/ 01 марта 2010

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...