Pyparsing DOT记录标签(递归)

时间:2016-12-29 20:20:55

标签: pyparsing

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

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

我有两个问题。一,我不知道如何处理boxLabel因为它有选项。二,我认为我有一个左递归,但因为没有“操作员”不知道如何处理它。 到目前为止我做了什么:

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 解析。

1 个答案:

答案 0 :(得分:0)

恭喜你在你的pyparsing项目上取得了这么大的进步,递归语法很难让你第一次做到这一点。

首先,这是一个小错字,但可能会让你感到困惑的是它会创建一个有效的解析器,但却是一个奇怪的解析器。

string = pyparsing.Word('alphanums')

应该是

string = pyparsing.Word(pyparsing.alphanums)

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

让我们先解决box_label个问题。通常,您可以将“A和B或仅A或仅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'