pyparsing 正则表达式作为单词

5 投票
1 回答
3326 浏览
提问于 2025-04-16 19:15

我正在创建一个语法解析器,用来对通过点号表示法识别的对象执行简单操作,类似这样的:

DISABLE ALL;
ENABLE A.1 B.1.1 C

但是在DISABLE ALL中,关键字ALL被匹配为3个Regex(r'[a-zA-Z]') => 'A', 'L', 'L',我用这个来匹配参数。

我该如何使用正则表达式来构建一个单词?据我所知,我无法通过单词获取A.1.1

请看下面的例子

import pyparsing as pp

def toggle_item_action(s, loc, tokens):
    'enable / disable a sequence of items'
    action = True if tokens[0].lower() == "enable" else False
    for token in tokens[1:]:
        print "it[%s].active = %s" % (token, action)

def toggle_all_items_action(s, loc, tokens):
    'enable / disable ALL items'
    action = True if tokens[0].lower() == "enable" else False
    print "it.enable_all(%s)" % action

expr_separator = pp.Suppress(';')

#match A
area = pp.Regex(r'[a-zA-Z]')
#match A.1
category = pp.Regex(r'[a-zA-Z]\.\d{1,2}')
#match A.1.1
criteria = pp.Regex(r'[a-zA-Z]\.\d{1,2}\.\d{1,2}')
#match any of the above
item = area ^ category ^ criteria
#keyword to perform action on ALL items
all_ = pp.CaselessLiteral("all")

#actions
enable = pp.CaselessKeyword('enable')
disable = pp.CaselessKeyword('disable')
toggle = enable | disable

#toggle item expression
toggle_item = (toggle + item + pp.ZeroOrMore(item)
    ).setParseAction(toggle_item_action)

#toggle ALL items expression
toggle_all_items = (toggle + all_).setParseAction(toggle_all_items_action)

#swapping order to `toggle_all_items ^ toggle_item` works
#but seems to weak to me and error prone for future maintenance
expr = toggle_item ^ toggle_all_items
#expr = toggle_all_items ^ toggle_item

more = expr + pp.ZeroOrMore(expr_separator + expr)

more.parseString("""
    ENABLE A.1 B.1.1;
    DISABLE ALL
    """, parseAll=True)

1 个回答

4

这是问题吗?

#match any of the above
item = area ^ category ^ criteria
#keyword to perform action on ALL items
all_ = pp.CaselessLiteral("all")

应该是:

#keyword to perform action on ALL items
all_ = pp.CaselessLiteral("all")
#match any of the above
item = area ^ category ^ criteria ^ all_

编辑 - 如果你感兴趣的话...

你的正则表达式(regex)看起来很相似,我想看看能不能把它们合并成一个。这里有一段代码,可以用一个正则表达式来解析你提到的三种带点的表示法,然后通过解析的动作来判断你得到了哪种类型:

import pyparsing as pp

dotted_notation = pp.Regex(r'[a-zA-Z](\.\d{1,2}(\.\d{1,2})?)?') 
def name_notation_type(tokens):
    name = {
        0 : "area",
        1 : "category",
        2 : "criteria"}[tokens[0].count('.')]
    # assign results name to results - 
    tokens[name] = tokens[0] 
dotted_notation.setParseAction(name_notation_type)

# test each individually
tests = "A A.1 A.2.2".split()
for t in tests:
    print t
    val = dotted_notation.parseString(t)
    print val.dump()
    print val[0], 'is a', val.getName()
    print

# test all at once
tests = "A A.1 A.2.2"
val = pp.OneOrMore(dotted_notation).parseString(tests)
print val.dump()

输出:

A
['A']
- area: A
A is a area

A.1
['A.1']
- category: A.1
A.1 is a category

A.2.2
['A.2.2']
- criteria: A.2.2
A.2.2 is a criteria

['A', 'A.1', 'A.2.2']
- area: A
- category: A.1
- criteria: A.2.2

编辑2 - 我明白原来的问题了...

让你困惑的是pyparsing的隐式空白跳过。pyparsing会跳过定义的标记之间的空白,但反过来就不一定了——pyparsing并不要求不同的解析表达式之间有空白。所以在你没有“_”的版本中,“ALL”看起来像是三个部分,“A”、“L”和“L”。这不仅适用于正则表达式,也适用于几乎所有的pyparsing类。看看pyparsing的WordEnd类是否能帮助你解决这个问题。

编辑3 - 那也许可以这样做...

toggle_item = (toggle + pp.OneOrMore(item)).setParseAction(toggle_item_action)
toggle_all = (toggle + all_).setParseAction(toggle_all_action)

toggle_directive = toggle_all | toggle_item

根据你命令的格式,解析器首先需要检查“ALL”是否被切换,然后再寻找单独的区域等等。如果你需要支持像“ENABLE A.1 ALL”这样的输入,那么可以使用负向前瞻来处理itemitem = ~all_ + (area ^ 等等...)。(另外,我还把item + pp.ZeroOrMore(item)替换成了pp.OneOrMore(item)。)

撰写回答