【发布时间】:2014-07-30 18:45:59
【问题描述】:
我有一个要解析的二进制有线协议配置文件。这用于允许低带宽链路两端的计算机以允许用户在现场配置它们的方式就哪些位表示哪些数据达成一致。
配置文件字符串如下所示:
abc:16 => 标识符 abc 有 16 位
abc:16 def:12 => 标识符 abc 有 16 位,标识符 def 有 12 位
abc:16:p => 标识符 abc 有 16 位和一个奇偶校验位
abc:16:ecc => 标识符 abc 有 16 位和两个用于 ecc 的位
我已经有了一个语法,我认为应该正确解析它,但我遇到了一个奇怪的问题:我只能有一个没有奇偶校验或 ecc 的标识符作为一行的最后一条语句。语法应该支持在行的任何地方有或没有奇偶校验的标识符,但无论出于何种原因,这都不会发生。
所以:
abc:16
本身是可以的,因为它之后没有任何东西
abc:16:p def:12
没关系,因为 abc:16:p 最后有一个奇偶校验
abc:16 def:12
不行,因为 abc:16 没有奇偶校验而且它不在末尾,但这应该没问题
abc:16 def:12:p
也不好,因为非奇偶校验语句不在末尾,但这也应该是完全可以的
这是程序:
from pyparsing import *
import re
abbr = Word(alphas, min=3, max=4)
#abbr = abbr.setDebug()
separator = Suppress(Literal(":"))
bits = Word(nums, min=1, max=2)
parity = Or([CaselessLiteral("P"), CaselessLiteral("ECC")])
bits_part = separator + bits
#bits_part = bits_part.setDebug()
parity_part = separator + parity
#parity_part = parity_part.setDebug()
statement = abbr + bits_part + Optional(parity_part)
#statement = statement.setDebug()
statement_list = StringStart() + statement + ZeroOrMore(Suppress(White()) + statement) + Optional(Suppress(White())) + StringEnd()
tests = (
"abc:16",
"abc:15:p",
"abc:15:p def:14:ecc",
"abc:17:p def:q ghi:21:", #this one should fail since "q" isn't parity and you shouldn't have a trailing colon with no parity after it
"abc:16:p def:12", #this passes so it's OK to have a trailing statement without parity
"abc:15 def:12:p", #this fails but shouldn't
"abc:16:p def:12 pqr:11", #this is also failing because anything but the last statement missing parity causes failure, but I don't think that's the right behavior
)
for t in tests:
try:
print t
print statement_list.parseString(t)
except Exception as e:
print e
当我在未打开调试的情况下运行它时,我看到以下结果。根据我的理解(以及上面的 cmets),只有第三个示例应该失败,因为它的“q”应该是奇偶校验的“p”。其他一切都应该通过,但由于我不明白的原因引发了异常。
abc:16
['abc', '16']
abc:15:p
['abc', '15', 'P']
abc:15:p def:14:ecc
['abc', '15', 'P', 'def', '14', 'ECC']
abc:17:p def:q ghi:21:
Expected end of text (at char 9), (line:1, col:10)
abc:16:p def:12
['abc', '16', 'P', 'def', '12']
abc:15 def:12:p
Expected end of text (at char 7), (line:1, col:8)
abc:16:p def:12 pqr:11
Expected end of text (at char 16), (line:1, col:17)
当我打开调试时(在上面的示例代码中都被注释掉了),我只看“abc:16 def:12”,这是输出:
abc:15 def:12:p
Match {W:(abcd...) {Suppress:(":") W:(0123...)} [{Suppress:(":") {'P' ^ 'ECC'}}]} at loc 0(1,1)
Match W:(abcd...) at loc 0(1,1)
Matched W:(abcd...) -> ['abc']
Match {Suppress:(":") W:(0123...)} at loc 3(1,4)
Matched {Suppress:(":") W:(0123...)} -> ['15']
Match {Suppress:(":") {'P' ^ 'ECC'}} at loc 7(1,8)
Exception raised:Expected ":" (at char 7), (line:1, col:8)
Matched {W:(abcd...) {Suppress:(":") W:(0123...)} [{Suppress:(":") {'P' ^ 'ECC'}}]} -> ['abc', '15']
Expected end of text (at char 7), (line:1, col:8)
在我看来,这证实了它正在尝试匹配 parity_part,这显然不存在。但是我已经设置好了 parity_part 是 Optional() 所以我不知道为什么它坚持要找到它。
此外,那里有一个空格字符(在 abc:16 和 def:12 之间),我认为它应该触发它继续前进,就像我在语法的 statement_list 部分中指定的那样。为此,我还在最后对锻炼者进行了“leaveWhitespace()”调用:
print statement_list.parseString(t).leaveWhitespace()
但这并没有改变任何东西(因为它没有开始以我期望的方式解析)所以我不认为问题在于它缺少空格。我当然不能完全打折。
我在这里变得非常困惑,因为我已经从我能想到的各个角度解决了这个问题,但我仍然没有得到我所期望的。我是否指定语法错误? pyparsing 做错了吗?我很确定自己在某个地方犯了错误,但我真的看不到。
编辑:
所以保罗指出我到处都有一堆愚蠢的空白,当他把所有这些都扔掉并简化时效果很好。空格的东西是故意放在那里的,因为我要尝试阻止人们做类似的事情:
“abc:10:ecc”
因为它看起来很糟糕,而不是因为它不包含正确的信息。
我不确定阻止人们在我认为不应该放置空间的地方放置空间对我来说是否值得,所以保罗的回答可能足以让我继续我的生活。
但我仍然很好奇为什么我制作的版本不起作用,而他所做的修改却起作用了。它们在功能上看起来与我相同。
【问题讨论】:
-
如果你想接管 pyparsing 中的空白处理,你应该做的第一件事是调用
ParserElement.setDefaultWhitespace(""),这将几乎禁用所有隐式空白跳过。然后你可以定义你的解析表达式,但记得在它们可能出现的地方添加White()表达式。 (我最初使用隐式空格跳过的理由是,我厌倦了看到人们实现强制用户在每个标记之间放置分隔符的解析器,例如“if (a != b) a += b - a”,但我也发现人们将空间放置在您意想不到的地方。 -
expr.leaveWhitespace不会按照您的假设进行。它只禁止在尝试解析expr之前发生的空白跳过。 -
请不要使用此表单
parity = Or([CaselessLiteral("P"), CaselessLiteral("ECC")])- 我发现parity = CaselessLiteral("P") ^ CaselessLiteral("ECC")更容易阅读。为什么在这里使用 Or ?匹配“P”与“ECC”不会有歧义。如果您正在匹配 P 或 PUT 或 PURPLE,那么您可能想要使用 Or(或者只是使用 MatchFirst 使用 '|' 运算符,注意以PURPLE | PUT | P顺序进行测试)。但是parity = CaselessLiteral("P") | CaselessLiteral("ECC")应该没有任何问题。