使用 pyparsing 和递归

Using pyparsing and recursion

我正在尝试对包含以下一种或多种模式的字符串进行解析:

  1. -标志
  2. -标记对象
  3. -标志对象{嵌套}
  4. -标记{对象{嵌套}}

例如我将使用以下字符串:

-flag1 -flag2 object2 { -nested_flag1 nested_obj1 -nested_flag2 }

为了解析它,我使用:

exp = '-flag1 -flag2 object2 { -nested_flag1 nested_obj1 -nested_flag2 }'
only_flag = '-' + Word(printables, excludeChars='-').setResultsName("flag")
flag_w_obj = only_flag + Optional("{") + Word(printables, excludeChars='-').setResultsName("object")
flag_w_obj_w_nested = flag_w_obj + originalTextFor(nestedExpr("{", "}")).setResultsName("nested_expr")

parser = flag_w_obj_w_nested | flag_w_obj | only_flag
parsed = parser.searchString(exp)

如何通过相同的规则计算嵌套表达式以获得嵌套标志和对象?

我最终想要的结果是制作一个包含格式数据的字典:

{
  "flag1": null,
  "flag2": {
    "object1": {
      "nested_flag1": "nested_obj1",
      "nested_flag2": null
    }
  }
}

大多数时候,只要你有一个包含嵌套内容的表达式,你最终都会使用一个 pyparsing Forward 表达式。 Forward 允许您引用尚未完全定义的表达式 - 它是前向声明。在您的解析器中,-flag args... 形式本身可以包含嵌套在 {} 中的标志。一旦可以指定内容,就可以使用 <<= 运算符来完成 "assignment"。

我还强烈建议您在开始实际编写任何代码之前先编写一个简短的解析器设计(无论您打算使用什么解析库,都要这样做)。在解析中,BNF(Backus-Naur 格式)是要使用的典型格式。它不必非常严格,但要有足够的细节,以便您可以手动完成它以确保您的各个部分都是正确的,并且没有歧义。写出一些您希望解析器能够处理的示例字符串也很好。

以下带注释的代码显示了我为您的问题编写的 BNF,以及它在 pyparsing 解析器中的实现。

"""
BNF

flag_name := '-' alphanumeric...
flag_arg_word := alphanumeric...

flag_arg := flag_arg_word | '{' flag_expr... '}'
flag_expr := flag_name [flag_arg...]
"""

import pyparsing as pp

# define punctuation
LBRACE, RBRACE = map(pp.Suppress, "{}")

# recursive parser requires a forward declaraction
flag_expr = pp.Forward()

# implement BNF definitions
flag_name = pp.Word("-", pp.alphanums + "_")
flag_arg_word = pp.Word(pp.alphas, pp.alphanums + "_")
flag_arg = flag_arg_word | (LBRACE + flag_expr[...] + RBRACE)

# use '<<=' operator to define recursive expression
flag_expr <<= pp.Group(flag_name + pp.Group(flag_arg[...]))

# a command is 0 or more flag_exprs
cmd_expr = flag_expr[...]

# test it out
cmd = "-flag1 -flag2 object2 { -nested_flag1 nested_obj1 -nested_flag2 }"
parsed = cmd_expr.parseString(cmd)

# convert to a list and show with pprint
from pprint import pprint
pprint(parsed.asList(), width=12)

# runTests is very useful for running several test strings through a parser
cmd_expr.runTests("""\
-flag1 -flag2 object2 { -nested_flag1 nested_obj1 -nested_flag2 }
""")