2010-09-09 21 views
1

Je suis en train d'analyser un texte relativement simple, où chaque ligne décrit une unité de jeu. J'ai peu de connaissances des techniques de l'analyse, donc je la solution ad hoc suivante:python: remplacer regex avec BNF ou 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 '' 

Il fonctionne très bien, mais je sens que j'atteint la limite du pouvoir regex. Plus précisément, dans le cas de Traits, la valeur finit par être une chaîne que je dois diviser et convertir en une liste à un point ultérieur: par exemple, obj.Traits serait mis à 'Motorized Armored' dans ce code, mais dans un la fonction ultérieure a été changée en ('Motorisé', 'Blindé').

Je pense à convertir ce code pour utiliser soit la grammaire EBNF ou pyparsing ou quelque chose comme ça. Mes objectifs sont les suivants:

  • rendent cette plus propre et moins de code sujettes à erreur
  • éviter le traitement laid de l'affaire avec une liste de valeurs (où je dois faire remplacer à l'intérieur du regex d'abord, et plus tard post-traitement le résultat pour convertir une chaîne en une liste)

Quelles seraient vos suggestions sur quoi utiliser, et comment réécrire le code?

P.S. J'ai sauté certaines parties du code pour éviter l'encombrement; si j'ai introduit des erreurs dans le processus, désolé - le code d'origine fonctionne :)

+0

Je suggère pour pyparsing. Facile à écrire du code propre. Regardez les exemples @ http://pyparsing.wikispaces.com/Examples. – asb

+0

Dois-je utiliser EBNF, puis le compiler en utilisant http://pyparsing.wikispaces.com/file/view/ebnf.py pour générer une grammaire de pyparsing? Ou devrais-je écrire la grammaire de pyparsing directement? – max

Répondre

4

J'ai commencé à écrire un guide de coaching pour pyparsing, mais en regardant vos règles, elles se traduisent assez facilement en éléments de pyparsing, sans traiter avec EBNF, donc je viens de cuire un échantillon rapide:

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)) 

J'inclus l'exemple Regex pour que vous puissiez voir comment pourrait être largué une expression régulière dans une grammaire pyparsing existante. La composition de rule en utilisant les opérateurs '&' signifie que les éléments individuels peuvent être trouvés dans n'importe quel ordre (donc la grammaire s'occupe de l'itération sur toutes les règles, au lieu de le faire dans votre propre code). Pyparsing utilise la surcharge de l'opérateur pour construire des analyseurs complexes à partir de simples: '+' pour la séquence, '|' et '^' pour les alternatives (first-match ou long-match), et ainsi de suite.

Voici comment les résultats analysés regarderaient - note que j'ai ajouté des noms de résultats, tout comme vous avez utilisé des groupes nommés dans votre 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) 

impressions:

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

S'il vous plaît arrêter par la wiki et voir les autres exemples. Vous pouvez easy_install installer pyparsing, mais si vous téléchargez la distribution source depuis SourceForge, il y a beaucoup de documentation supplémentaire.

+0

Merci! Je trouve le manuel de pyparsing un peu difficile à suivre, mais j'aime beaucoup le module. – max

+0

Les liens Documentation et Publications sur le wiki pyparsing peuvent vous amener à plus de ressources. (http://pyparsing.wikispaces.com/Documentation) – PaulMcG