如何使用pyparsing LineStart?

How to use pyparsing LineStart?

我正在尝试使用 pyparsing 从文档中的评论中解析 key:value 对。一个键从一行的开头开始,然后是一个值。值可以在以空格开头的多行上继续。

import pyparsing as pp

instring = """
-- This is (a) #%^& comment

/*
name1: val
name2: val2 with $*&#@) junk
name3: val3: with @)(*% multi-
       line: content
*/
"""

comment1 = pp.Literal("--") + pp.originalTextFor(pp.SkipTo(pp.LineEnd())).setDebug()
identifier = pp.Word(pp.alphanums + "_").setDebug()
meta1 = pp.LineStart() + identifier + pp.Literal(":") + pp.SkipTo(pp.LineEnd())
meta2 = pp.LineStart() + pp.White() + pp.SkipTo(pp.LineEnd())
metaval = meta1 + pp.ZeroOrMore(meta2)
metalist = pp.ZeroOrMore(comment1) + pp.Literal("/*") + pp.OneOrMore(metaval) + pp.Literal("*/")

if __name__ == "__main__":
    p = metalist.parseString(instring)
    print(p)

失败:

Matched {Empty SkipTo:(LineEnd) Empty} -> ['This is (a) #%^& comment']

File "C:\Users\user\py3\lib\site-packages\pyparsing.py", line 2305, in parseImpl
raise ParseException(instring, loc, self.errmsg, self)
pyparsing.ParseException: Expected start of line (at char 32), (line:4, col:1)

pyparsing whitespace match issues 的答案是

LineStart has always been difficult to work with, but ...

如果解析器位于第 4 行第 1 列(第一个 key:value 对),那么为什么找不到行首?识别以无空格开头的行和以空格开头的行的正确 pyparsing 语法是什么?

我认为我对 LineStart 的困惑是,对于 LineEnd,我可以寻找一个 '\n' 字符,但是 [=13= 没有单独的字符].所以在 LineStart 中,我查看当前解析器位置是否位于 '\n' 之后;或者如果它当前 on a '\n',移动过去并继续。不幸的是,我在一个弄乱了报告位置的地方实现了这个,所以你会得到那些读起来像 "failed to find a start of line on line X col 1," 的奇怪错误,这听起来确实应该是一个成功匹配的行首。另外,我想我需要重新审视这个隐式的换行符跳过,或者就此而言,LineStart 的所有空格跳过。

目前,我已经通过稍微扩展您的行起始表达式使您的代码正常工作,如:

LS = pp.Optional(pp.LineEnd()) + pp.LineStart()

并将 meta1 和 meta2 中的 LineStart 引用替换为 LS:

comment1 = pp.Literal("--") + pp.originalTextFor(pp.SkipTo(pp.LineEnd())).setDebug()
identifier = pp.Word(pp.alphanums + "_").setDebug()
meta1 = LS + identifier + pp.Literal(":") + pp.SkipTo(pp.LineEnd())
meta2 = LS + pp.White() + pp.SkipTo(pp.LineEnd())
metaval = meta1 + pp.ZeroOrMore(meta2)
metalist = pp.ZeroOrMore(comment1) + pp.Literal("/*") + pp.OneOrMore(metaval) + pp.Literal("*/")

如果 LineStart 的这种情况让您感到不舒服,您可以尝试另一种策略:使用解析时条件仅接受从第 1 列开始的标识符:

comment1 = pp.Literal("--") + pp.originalTextFor(pp.SkipTo(pp.LineEnd())).setDebug()

identifier = pp.Word(pp.alphanums + "_").setName("identifier")
identifier.addCondition(lambda instring,loc,toks: pp.col(loc,instring) == 1)

meta1 = identifier + pp.Literal(":") + pp.SkipTo(pp.LineEnd()).setDebug()
meta2 = pp.White().setDebug() + pp.SkipTo(pp.LineEnd()).setDebug()
metaval = meta1 + pp.ZeroOrMore(meta2, stopOn=pp.Literal('*/'))
metalist = pp.ZeroOrMore(comment1) + pp.Literal("/*") + pp.LineEnd() + pp.OneOrMore(metaval) + pp.Literal("*/")

此代码完全取消了 LineStart,同时我弄清楚了我希望这个特定令牌执行的操作。我还必须修改 metaval 中的 ZeroOrMore 重复,这样 */ 就不会被意外处理为继续的评论内容。

感谢您对此的耐心等待 - 我不想快速推出补丁 LineStart 更改,然后发现我忽略了其他兼容性或其他边缘情况,这些情况只会让我回到当前的 le​​ss - 在这个 class 上的状态非常好。但在发布 2.1.10 之前,我会努力澄清这种行为。