PyParsing - 语法元素围绕其他元素拆分

PyParsing - Grammar Elements Split Around Other Elements

我正在移动一个工具(不是我写的)来使用 PyParsing。我正在更新语法以使其更有意义,但也希望向后兼容。语法包括被另一个元素拆分的元素,我需要“动词”(包装元素)和“值”(包装元素)。 (不,这些不是 glob,尽管它们看起来很像 - 这很令人困惑,这也是我要更改它的部分原因)。

*thing*  # verb: contains      value: thing
*thing   # verb: starts_with   value: thing
thing*   # verb: ends_with     value: thing
!*thing* # verb: not_contains  value: thing

我很难思考如何解析像 *thing* 这样的东西,其中“动词”包含在“值”周围。这些元素也在一个分隔列表中,尽管那部分我很满意。

我要解析的完整示例:

command *thing*, !*other_thing*, *third_thing

我尝试过的:

import pyparsing as pp

command = pp.Keyword("command").setResultsName("command")
value = pp.Word(pp.alphanums + "_").setResultsName("value", listAllMatches=True)

contains = ("*" + value + "*").setResultsName("verb", listAllMatches=True)
not_contains = ("!*" + value + "*").setResultsName("verb", listAllMatches=True)
starts_with = ("*" + value).setResultsName("verb", listAllMatches=True)

verbs_and_values = (
    contains
    | not_contains
    | starts_with
)

directive = pp.Group(command + pp.delimitedList(verbs_and_values, delim=","))

example = "command *thing*, !*other_thing*, *third_thing"

result = directive.parseString(example)
print result.dump()

这让我得到了所有的值,但动词才是全部(即 ['*', 'thing', '*'])。我尝试使用类似于此的 parseAction 调整动词:

def process_verb(tokens):
    if tokens[0] == '*' and tokens[-1] == '*':
        return "contains"
    # handle other verbs...

效果很好,但它破坏了值...

我看到您使用带有 listAllMatches=True 的结果名称来捕获 delimitedList 中的多个解析值。这对于简单的数据结构来说没问题,但是一旦你想为一个给定的值存储多个值,那么你将需要开始使用 Group 或 parse action classes.

作为一般做法,我避免在低级表达式上使用结果名称,而是在使用“+”和“|”组成高级表达式时添加它们运营商。我也主要使用 expr("name") 形式而不是 expr.setResultsName("name") 形式来设置结果名称。

这是使用群组修改后的代码版本:

command = pp.Keyword("command")
value = pp.Word(pp.alphanums + "_")

contains = pp.Group("*" + value("value") + "*")
not_contains = pp.Group("!*" + value("value") + "*")
starts_with = pp.Group("*" + value("value"))

我还在 directive 中添加了命令的结果名称和动词列表:

directive = pp.Group(command("command")
                     + pp.Group(pp.delimitedList(verbs_and_values, 
                                        delim=","))("verbs"))

现在这些表达式被包装在组中,没有必要使用 listAllMatches=True,因为现在每个值都保存在自己单独的组中。

解析后的结果现在看起来像这样:

[['command', ['*', 'thing', '*'], ['!*', 'other_thing', '*'], ['*', 'third_thing']]]
[0]:
  ['command', ['*', 'thing', '*'], ['!*', 'other_thing', '*'], ['*', 'third_thing']]
  - command: 'command'
  - verbs: [['*', 'thing', '*'], ['!*', 'other_thing', '*'], ['*', 'third_thing']]
    [0]:
      ['*', 'thing', '*']
      - value: 'thing'
    [1]:
      ['!*', 'other_thing', '*']
      - value: 'other_thing'
    [2]:
      ['*', 'third_thing']
      - value: 'third_thing'
  

您在使用解析操作添加有关动词类型的信息方面走在了正确的轨道上,但您不想返回该值,而是想将动词类型添加为另一个 命名结果。

def add_type_parse_action(verb_type):
    def pa(s, l, t):
        t[0]["type"] = verb_type
    return pa

contains.addParseAction(add_type_parse_action("contains"))
not_contains.addParseAction(add_type_parse_action("not_contains"))
starts_with.addParseAction(add_type_parse_action("starts_with"))

添加解析操作后,您会得到这些结果:

[['command', ['*', 'thing', '*'], ['!*', 'other_thing', '*'], ['*', 'third_thing']]]
[0]:
  ['command', ['*', 'thing', '*'], ['!*', 'other_thing', '*'], ['*', 'third_thing']]
  - command: 'command'
  - verbs: [['*', 'thing', '*'], ['!*', 'other_thing', '*'], ['*', 'third_thing']]
    [0]:
      ['*', 'thing', '*']
      - type: 'contains'
      - value: 'thing'
    [1]:
      ['!*', 'other_thing', '*']
      - type: 'not_contains'
      - value: 'other_thing'
    [2]:
      ['*', 'third_thing']
      - type: 'starts_with'
      - value: 'third_thing'
      

您还可以定义 classes 来为您的结果提供结构。由于 class 被“调用”,就好像它是一个解析操作一样,Python 将使用解析的标记构造一个 class 实例:

class VerbBase:
    def __init__(self, tokens):
        self.tokens = tokens[0]

    @property
    def value(self):
        return self.tokens.value
    
    def __repr__(self):
        return "{}(value={!r})".format(type(self).__name__, self.value)

class Contains(VerbBase): pass
class NotContains(VerbBase): pass
class StartsWith(VerbBase): pass

contains.addParseAction(Contains)
not_contains.addParseAction(NotContains)
starts_with.addParseAction(StartsWith)

result = directive.parseString(example)
print(result.dump())

现在结果在对象实例中,其类型表明使用了哪种动词:

[['command', [Contains(value='thing'), NotContains(value='other_thing'), StartsWith(value='third_thing')]]]
[0]:
  ['command', [Contains(value='thing'), NotContains(value='other_thing'), StartsWith(value='third_thing')]]
  - command: 'command'
  - verbs: [Contains(value='thing'), NotContains(value='other_thing'), StartsWith(value='third_thing')]

注意:在您的整个问题中,您将命令后的项目称为“动词”,我在此答案中保留了该名称,以便更容易与您最初的尝试进行比较。但通常,“动词”会指代一些动作,比如指令中的“命令”,而后面的项目更像是“限定符”或“参数”。名称在编码时很重要,不仅在与他人交流时,而且在形成您自己对代码的作用的心理概念时也是如此。对我来说,这里的“动词”,通常是一个句子中的动作,更像是命令,而后面的部分我称之为“限定词”、“参数”或“主题”。