使用 Pyparsing 为上下文相关元素编写语法规则
Writing grammar rules for context sensitive elements using Pyparsing
我正在尝试为一组 句子 编写语法并使用 Pyparsing 来解析它。这些 句子 告诉我们在文本文件中搜索什么以及如何搜索,我需要将它们转换成相应的正则表达式搜索代码。但是,有些元素并不是真正的上下文无关,因此,我发现很难为它们编写生产规则。基本上,我的目标是解析这些句子,然后为它们编写正则表达式。
在这些句子中找到的上下文相关元素的一些示例 -
LINE_CONTAINS phrase1 BEFORE {phrase2 AND phrase3}
表示在行中,phrase1
可以出现在 phrase2
和 phrase
之前的任何位置。 AFTER
同样
LINE_CONTAINS abc JOIN xyz
表示搜索 abc xyz
and abc-xyz
and abcxyz
LINE_CONTAINS abcd AND xyzw
表示该行应同时包含 abcd
和 xyzw
示例 - 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
所以我的问题是 -
如何编写可以处理此类上下文相关元素的语法规则,假设任何句子都可以有它们(虽然大多数句子不会有多个,但仍然如此)。 我是否应该为多种句子编写规则,每一种都包含这些不同类型的元素之一。或者我应该写一个包含所有这些元素的规则并使它们成为可选的。
我知道这些元素可能不完全是上下文相关的,但我的问题在于无法为 BEFORE
、JOIN
等元素编写独立的生产规则。如何我最好在生产规则中定义它们的功能?
编辑-词组可以多词
在我看来,这是一个非常简单的语法。我认为你是 "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 + y
或 x 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
具有可选的 lpar
和 rpar
参数来覆盖 (
和)
.
从这里,您可以查看其他 infixNotation
示例(例如 pyparsing wiki 示例页面上的 SimpleBool.py),将其转换为您各自的正则表达式生成代码。
我正在尝试为一组 句子 编写语法并使用 Pyparsing 来解析它。这些 句子 告诉我们在文本文件中搜索什么以及如何搜索,我需要将它们转换成相应的正则表达式搜索代码。但是,有些元素并不是真正的上下文无关,因此,我发现很难为它们编写生产规则。基本上,我的目标是解析这些句子,然后为它们编写正则表达式。
在这些句子中找到的上下文相关元素的一些示例 -
LINE_CONTAINS phrase1 BEFORE {phrase2 AND phrase3}
表示在行中,phrase1
可以出现在phrase2
和phrase
之前的任何位置。AFTER
同样
LINE_CONTAINS abc JOIN xyz
表示搜索abc xyz
andabc-xyz
andabcxyz
LINE_CONTAINS abcd AND xyzw
表示该行应同时包含abcd
和xyzw
示例 - 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
所以我的问题是 -
如何编写可以处理此类上下文相关元素的语法规则,假设任何句子都可以有它们(虽然大多数句子不会有多个,但仍然如此)。 我是否应该为多种句子编写规则,每一种都包含这些不同类型的元素之一。或者我应该写一个包含所有这些元素的规则并使它们成为可选的。
我知道这些元素可能不完全是上下文相关的,但我的问题在于无法为 BEFORE
、JOIN
等元素编写独立的生产规则。如何我最好在生产规则中定义它们的功能?
编辑-词组可以多词
在我看来,这是一个非常简单的语法。我认为你是 "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 + y
或 x 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
具有可选的 lpar
和 rpar
参数来覆盖 (
和)
.
从这里,您可以查看其他 infixNotation
示例(例如 pyparsing wiki 示例页面上的 SimpleBool.py),将其转换为您各自的正则表达式生成代码。