使用 Pyparsing 为上下文相关元素编写语法规则

Writing grammar rules for context sensitive elements using Pyparsing

我正在尝试为一组 句子 编写语法并使用 Pyparsing 来解析它。这些 句子 告诉我们在文本文件中搜索什么以及如何搜索,我需要将它们转换成相应的正则表达式搜索代码。但是,有些元素并不是真正的上下文无关,因此,我发现很难为它们编写生产规则。基本上,我的目标是解析这些句子,然后为它们编写正则表达式。

在这些句子中找到的上下文相关元素的一些示例 -

  1. LINE_CONTAINS phrase1 BEFORE {phrase2 AND phrase3} 表示在行中,phrase1 可以出现在 phrase2phrase 之前的任何位置。 AFTER

  2. 同样
  3. LINE_CONTAINS abc JOIN xyz 表示搜索 abc xyz and abc-xyz and abcxyz

  4. LINE_CONTAINS abcd AND xyzw 表示该行应同时包含 abcdxyzw

示例 - LINE_CONTAINS we transfected BEFORE {sirna} AND gene AND LINE_STARTSWITH Therefore

这应该转换为 re.search(r'(^!Therefore.*?we transfected.*?sirna)' and re.search(r'(gene))(我相信可以制作更好的正则表达式)


我已经开始为句子编写语法 -

Beginner = LINE_CONTAINS|LINE_STARTSWITH|other line beginners...
Phrase = word+
sentence = Beginner + phrase + AND + Beginner + phrase

这些 motifs/elements 中的任何一个都可以出现在任何行中,也可以组合使用。喜欢

LINE_CONTAINS {x AND y} BEFORE {a letter AND b letter} AND zoo people AND LINE_STARTSWITH dfg

所以我的问题是 -

如何编写可以处理此类上下文相关元素的语法规则,假设任何句子都可以有它们(虽然大多数句子不会有多个,但仍然如此)。 我是否应该为多种句子编写规则,每一种都包含这些不同类型的元素之一。或者我应该写一个包含所有这些元素的规则并使它们成为可选的。

我知道这些元素可能不完全是上下文相关的,但我的问题在于无法为 BEFOREJOIN 等元素编写独立的生产规则。如何我最好在生产规则中定义它们的功能?

编辑-词组可以多词

在我看来,这是一个非常简单的语法。我认为你是 "overthinking" 问题所在。

看着你的例子,我看到了这个:

a JOIN b
a BEFORE b

a AND b
a OR b

STARTSWITH a

这些只是运算符。一元运算符 (STARTSWITH) 类似于 python 中的 ~x-x。二元运算符(JOIN、BEFORE、AND、OR)就像 python 中的 x + yx in y

我不认为 CONTAINS 是一个运算符,更像是一个占位符。除了 STARTSWITH 之外,几乎所有内容都隐式包含。所以这有点像一元加运算符:定义、理解、允许,但无用。

无论如何,弄清楚运算符是什么(列一个列表)。弄清楚它们是一元的 (startswith) 还是二元的 (and)。然后弄清楚他们的 precedence and associativity 是什么。

一旦你知道了这些信息,你就可以构建你的解析器:你将知道关键词,并知道如何在语法中排列关键词。

对您的语法进行一些猜测,这是一个粗略的猜测。请注意我是如何从短语表达式中单独定义行表达式的:

from pyparsing import (CaselessKeyword, Word, alphas, MatchFirst, quotedString, 
    infixNotation, opAssoc, Suppress, Group)


LINE_CONTAINS, LINE_STARTSWITH, LINE_ENDSWITH = map(CaselessKeyword,
    """LINE_CONTAINS LINE_STARTSWITH LINE_ENDSWITH""".split())
NOT, AND, OR = map(CaselessKeyword, "NOT AND OR".split())
BEFORE, AFTER, JOIN = map(CaselessKeyword, "BEFORE AFTER JOIN".split())

keyword = MatchFirst([LINE_CONTAINS, LINE_STARTSWITH, LINE_ENDSWITH, NOT, AND, OR, 
                      BEFORE, AFTER, JOIN])
phrase_word = ~keyword + Word(alphas + '_')

phrase_term = phrase_word | quotedString

phrase_expr = infixNotation(phrase_term,
                            [
                             ((BEFORE | AFTER | JOIN), 2, opAssoc.LEFT,),
                             (NOT, 1, opAssoc.RIGHT,),
                             (AND, 2, opAssoc.LEFT,),
                             (OR, 2, opAssoc.LEFT),
                            ],
                            lpar=Suppress('{'), rpar=Suppress('}')
                            )

line_term = Group((LINE_CONTAINS | LINE_STARTSWITH | LINE_ENDSWITH)("line_directive") + 
                  Group(phrase_expr)("phrase"))
line_contents_expr = infixNotation(line_term,
                                   [(NOT, 1, opAssoc.RIGHT,),
                                    (AND, 2, opAssoc.LEFT,),
                                    (OR, 2, opAssoc.LEFT),
                                    ]
                                   )

sample = """
LINE_CONTAINS transfected BEFORE {sirna} AND gene AND LINE_STARTSWITH Therefore
"""

line_contents_expr.runTests(sample)

将您的样本解析为:

LINE_CONTAINS transfected BEFORE {sirna} AND gene AND LINE_STARTSWITH Therefore
[[['LINE_CONTAINS', [[['transfected', 'BEFORE', 'sirna'], 'AND', 'gene']]], 'AND', ['LINE_STARTSWITH', ['Therefore']]]]
[0]:
  [['LINE_CONTAINS', [[['transfected', 'BEFORE', 'sirna'], 'AND', 'gene']]], 'AND', ['LINE_STARTSWITH', ['Therefore']]]
  [0]:
    ['LINE_CONTAINS', [[['transfected', 'BEFORE', 'sirna'], 'AND', 'gene']]]
    - line_directive: 'LINE_CONTAINS'
    - phrase: [[['transfected', 'BEFORE', 'sirna'], 'AND', 'gene']]
      [0]:
        [['transfected', 'BEFORE', 'sirna'], 'AND', 'gene']
        [0]:
          ['transfected', 'BEFORE', 'sirna']
        [1]:
          AND
        [2]:
          gene
  [1]:
    AND
  [2]:
    ['LINE_STARTSWITH', ['Therefore']]
    - line_directive: 'LINE_STARTSWITH'
    - phrase: ['Therefore']

phrase_word 以否定前瞻开始,以避免意外地将 'LINE_STARTSWITH' 之类的字符串视为短语单词。我还添加了带引号的字符串作为有效的短语单词,因为您永远不知道您的搜索何时必须实际包含字符串 "LINE_STARTSWITH".

您使用 {}s 在短语表达式中进行分组,infixNotation 具有可选的 lparrpar 参数来覆盖 ().

从这里,您可以查看其他 infixNotation 示例(例如 pyparsing wiki 示例页面上的 SimpleBool.py),将其转换为您各自的正则表达式生成代码。