python:用 BNF 或 pyparsing 替换正则表达式

标签 python regex pyparsing ebnf

我正在解析一个相对简单的文本,其中每一行都描述了一个游戏单元。我对解析技术知之甚少,所以我使用了以下临时解决方案:

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

它工作正常,但我觉得我达到了正则表达式能力的极限。具体来说,在 Traits 的情况下,该值最终是一个字符串,我需要在稍后将其拆分并转换为列表:例如,obj.Traits 在此代码中将设置为“Motorized Armored”,但在后来函数更改为('Motorized','Armored')。

我正在考虑将此代码转换为使用 EBNF 或 pyparsing 语法或类似的东西。我的目标是:

  • 使这段代码更整洁,更不容易出错
  • 避免使用值列表对案例进行丑陋的处理(我需要先在正则表达式中进行替换,然后对结果进行后处理以将字符串转换为列表)

您对使用什么以及如何重写代码有何建议?

附言我跳过了部分代码以避免困惑;如果我在这个过程中引入了任何错误,抱歉 - 原始代码确实有效:)

最佳答案

我开始编写 pyparsing 的指导指南,但看看你的规则,它们很容易转化为 pyparsing 元素本身,无需处理 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 使用运算符重载从简单的解析器构建复杂的解析器:'+' 表示序列,'|'和 '^' 用于备选(第一场比赛或最长比赛),依此类推。

这是解析结果的样子 - 请注意,我添加了结果名称,就像您在正则表达式中使用命名组一样:

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 下载源代码分发版,则会有很多额外的文档。

关于python:用 BNF 或 pyparsing 替换正则表达式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3673388/

相关文章:

python - 如何使用python请求获取重定向url

Python 和 PyDev 未使用的导入 - 可以在包级别禁用吗?

r - 使用 ">"时,"[[:punct:]]"与 `stringr::str_replace_all` 不匹配?

javascript - 正则表达式 JavaScript : Symbol extraction after digit

python - Pyparsing 标识符被压入堆栈两次

python - BNF 可以处理远期消费吗?

python - scrapy 仅关注一种深度的外部链接

python - 使用颜色/样式格式时出现奇怪的终端输出

mysql - 在 mySql 中使用正则表达式选择和替换数据

python - 从组合(文字 ('@' )+ 'spec' )更改为关键字 ('@spec' )删除空格