Pyparsing DOT记录标签(递归)

Pyparsing DOT record label (recursion)

我正在尝试使用 pyparsing 来解析用于 DOT 记录的标签。语法是:

rlabel → '{' field ( '|' field )* '}'
field → boxLabel | rlabel
boxLabel → [ ’<’ string ’>’ ] [ string ]

我有两个问题。第一,我不确定如何处理 boxLabel,因为它必须是可选项。第二,我想我有一个左递归但是因为没有 "operator" 不知道如何处理它。 到目前为止我做了什么:

langle = pyparsing.Literal('<')
rangle = pyparsing.Literal('>')
string = pyparsing.Word('alphanums')
port_id = pyparsing.Group(langle + string + rangle)
port_name = pyparsing.Group(string)
box_label = pyparsing.Group(pyparsing.Optional(port_id) + pyparsing.Optional(port_name))
rlabel = pyparsing.Forward()
field = box_label | rlabel
rlabel_content = pyparsing.ZeroOrMore(field + "|") + field
rlabel << pyparsing.nestedExpr(opener='{', content=rlabel_content, closer='}')

解析进入无限递归。

此外,

>> print(box_label.parseString("<h> root"))
[[['<', 'h', '>']]]

而且我不知道为什么 port_name 解析没有出现在结果中。

恭喜您在 pyparsing 项目上取得了如此大的进步,递归语法在您第一次使用它们时很难理解。

首先,一个小错别字,但可能会让您感到困惑,因为它创建了一个有效的解析器,但却是一个奇怪的有限的解析器。

string = pyparsing.Word('alphanums')

应该是

string = pyparsing.Word(pyparsing.alphanums)

当您将字符串传递给 Word 时,它定义了解析此类字符组时要使用的有效字符列表。所以 Word("0123456789") 将解析一个整数,Word("AB") 将解析由字母 "A" 和 "B" 组成的任何单词。 Word('alphanums')会解析"alpha"、"nums"、"plums"、"haphalump"等很多词,只要是由[=58=中的字母组成的].我很确定您想要 pyparsing 定义的字符串 pyparsing.alphanums,其中包括字符 'A'-'Z'、'a'-'z' 和“0” -'9'.

让我们先解决 box_label 问题。通常,您可以将 "A and B or just A or just B" 实现为:

A + Optional(B) | B

或者如果你想更明确一点,你可以这样做:

A + B | A | B

但是,当您必须使用 2 个以上的表达式时,这就开始变得棘手了。幸运的是,您只有 2 个,我在下面为我提出的解决方案选择了第一种格式。

您在递归方面遇到的部分问题是您同时使用了 ForwardnestedExpr - 通常只使用一个或另一个就足够了。但在你的情况下,使用 Forward 有一个优势,所以我将使用该方法。

(我还将 pyparsing 导入为 pp,以减少所有额外的字符。)

import pyparsing as pp

# suppress these punctuation marks - useful during parsing, but just in the way afterward
langle = pp.Literal('<').suppress()
rangle = pp.Literal('>').suppress()
lbrace = pp.Literal('{').suppress()
rbrace = pp.Literal('}').suppress()

string = pp.Word(pp.alphanums)

port_id = langle + string("port_id") + rangle
port_name = string("port_name")

rlabel = pp.Forward()
box_label = pp.Group(port_id + pp.Optional(port_name) | port_name)
field = box_label | rlabel
# use delimitedList(expr) in place of expr + ZeroOrMore(delim + expr)
rlabel <<= pp.Group(lbrace + pp.delimitedList(field, delim='|') + rbrace)

自己创建一组测试字符串,然后用它们调用 runTests 以查看解析器的行为:

field.runTests("""\
    <h> root
    <h>
    root
    { <h> root | { <h2> sub | <h2>sub2 }}
    { <h> root | { <h2> sub | <h2>sub2 } | sub3 }
""")

runTests 将尝试解析每一行,然后转储结果或显示解析异常:

<h> root
[['h', 'root']]
[0]:
  ['h', 'root']
  - port_id: 'h'
  - port_name: 'root'


<h>
[['h']]
[0]:
  ['h']
  - port_id: 'h'


root
[['root']]
[0]:
  ['root']
  - port_name: 'root'


{ <h> root | { <h2> sub | <h2>sub2 }}
[[['h', 'root'], [['h2', 'sub'], ['h2', 'sub2']]]]
[0]:
  [['h', 'root'], [['h2', 'sub'], ['h2', 'sub2']]]
  [0]:
    ['h', 'root']
    - port_id: 'h'
    - port_name: 'root'
  [1]:
    [['h2', 'sub'], ['h2', 'sub2']]
    [0]:
      ['h2', 'sub']
      - port_id: 'h2'
      - port_name: 'sub'
    [1]:
      ['h2', 'sub2']
      - port_id: 'h2'
      - port_name: 'sub2'


{ <h> root | { <h2> sub | <h2>sub2 } | sub3 }
[[['h', 'root'], [['h2', 'sub'], ['h2', 'sub2']], ['sub3']]]
[0]:
  [['h', 'root'], [['h2', 'sub'], ['h2', 'sub2']], ['sub3']]
  [0]:
    ['h', 'root']
    - port_id: 'h'
    - port_name: 'root'
  [1]:
    [['h2', 'sub'], ['h2', 'sub2']]
    [0]:
      ['h2', 'sub']
      - port_id: 'h2'
      - port_name: 'sub'
    [1]:
      ['h2', 'sub2']
      - port_id: 'h2'
      - port_name: 'sub2'
  [2]:
    ['sub3']
    - port_name: 'sub3'