2010-09-09 1 views
1

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

class Unit: 
    # rules is an ordered dictionary of tagged regex that is intended to be applied in the given order 
    # the group named V would correspond to the value (if any) for that particular tag 
    rules = (
     ('Level', r'Lv. (?P<V>\d+)'), 
     ('DPS', r'DPS: (?P<V>\d+)'), 
     ('Type', r'(?P<V>Tank|Infantry|Artillery'), 
     #the XXX will be expanded into a list of valid traits 
     #note: (XXX|)* wouldn't work; it will match the first space it finds, 
     #and stop at that if it's in front of something other than a trait 
     ('Traits', r'(?P<V>(XXX)(XXX|)*)'), 
     # flavor text, if any, ends with a dot 
     ('FlavorText', r'(?P<V>.*\."?$)'), 
     ) 
    rules = collections.OrderedDict(rules) 
    traits = '|'.join('All-Terrain', 'Armored', 'Anti-Aircraft', 'Motorized') 
    rules['Traits'] = re.sub('XXX', effects, rules['Traits']) 

    for x in rules: 
     rules[x] = re.sub('<V>', '<'+x+'>', rules[x]) 
     rules[x] = re.compile(rules[x]) 

    def __init__(self, data) 
     # data looks like this: 
     # Lv. 5 Tank DPS: 55 Motorized Armored 
     for field, regex in Item.rules.items(): 
      data = regex.sub(self.parse, data, 1) 
     if data: 
      raise ParserError('Could not parse part of the input: ' + data) 

    def parse(self, m): 
     if len(m.groupdict()) != 1: 
      Exception('Expected a single named group') 
     field, value = m.groupdict().popitem() 
     setattr(self, field, value) 
     return '' 

Это прекрасно работает, но я чувствую, что достиг предела регулярных выражений власти. В частности, в случае с чертами значение становится строкой, которую мне нужно разделить и преобразовать в список в более поздней точке: например, obj.Traits будет установлен в «Motorized Armored» в этом коде, но в позже функция изменилась на («Моторизованный», «Бронированный»).

Я собираюсь преобразовать этот код, чтобы использовать грамматику EBNF или pyparsing или что-то в этом роде. Мои цели:

  • делает этот код аккуратнее и менее подвержены ошибкам
  • избежать уродливых лечений случае со списком значений (где мне нужно сделать замену внутри регулярного выражения первой, а позже пост-процессом результат, чтобы преобразовать строку в список)

Каковы были бы ваши предложения по поводу того, что использовать и как переписать код?

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

+0

Я предлагаю для pyparsing. Легко писать чистый код. Посмотрите примеры @ http://pyparsing.wikispaces.com/Examples. – asb

+0

Должен ли я использовать EBNF, а затем скомпилировать его с помощью http://pyparsing.wikispaces.com/file/view/ebnf.py, чтобы создать грамматику pyparsing? Или я должен писать pyparsing грамматику напрямую? – max

ответ

4

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

from pyparsing import Word, nums, oneOf, Group, OneOrMore, Regex, Optional 

integer = Word(nums) 
level = "Lv." + integer("Level") 
dps = "DPS:" + integer("DPS") 
type_ = oneOf("Tank Infantry Artillery")("Type") 
traits = Group(OneOrMore(oneOf("All-Terrain Armored Anti-Aircraft Motorized")))("Traits") 
flavortext = Regex(r".*\.$")("FlavorText") 

rule = (Optional(level) & Optional(dps) & Optional(type_) & 
     Optional(traits) & Optional(flavortext)) 

Я включил Regex примера, чтобы вы могли видеть, как регулярное выражение может быть опущены к существующей грамматике Pyparsing. Состав rule с использованием операторов «&» означает, что отдельные элементы могут быть найдены в любом порядке (поэтому грамматика заботится об итерации по всем правилам, вместо того, чтобы делать это в своем собственном коде). Pyparsing использует перегрузку оператора для создания сложных парсеров из простых: '+' для последовательности, '|' и '^' для альтернатив (первое совпадение или самое длинное совпадение) и т. д.

Вот как проанализированные результаты будут выглядеть - обратите внимание, что я добавил имена результатов, так же, как вы использовали именованные группы в вашем regexen:

data = "Lv. 5 Tank DPS: 55 Motorized Armored" 

parsed_data = rule.parseString(data) 
print parsed_data.dump() 
print parsed_data.DPS 
print parsed_data.Type 
print ' '.join(parsed_data.Traits) 

принты:

['Lv.', '5', 'Tank', 'DPS:', '55', ['Motorized', 'Armored']] 
- DPS: 55 
- Level: 5 
- Traits: ['Motorized', 'Armored'] 
- Type: Tank 
55 
Tank 
Motorized Armored 

Пожалуйста, зайдите в wiki и посмотреть другие примеры. Вы можете easy_install установить pyparsing, но если вы загрузите исходный дистрибутив из SourceForge, есть много дополнительной документации.

+0

Спасибо! Я нахожу, что руководство к pyparsing немного трудно следовать, но мне нравится модуль много. – max

+0

Ссылки на документацию и публикации на вики-странице pyparsing могут привлечь вас к дополнительным ресурсам. (Http://pyparsing.wikispaces.com/Documentation) – PaulMcG