BNF能否处理消费?

时间:2012-04-05 14:27:43

标签: python context-free-grammar bnf pyparsing

最近我发现了python模块pyparsing,这是一个很好的工具,可以通过编写语法来解析数据,而不是解析器。我对无上下文语法的想法不熟悉,所以请纠正这个问题中的任何错误假设。

Pyparsing可以实现BNF(Backus–Naur Form)无上下文语法。这个语法可以是递归的,但是它可以有前瞻性的前瞻吗?自从我偶然发现this question以来,我一直想知道答案。让我举一个具体的例子。考虑字符串:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

让语法看起来像:

<number> :: __<digit>__
<block>  :: <number>(x) (<number> <number> <number> .... x times)

即。读取第一个数字标记,将其另存为x,然后使用下一个x数字并将它们组合在一起。解析后的行应如下所示:

 [[1, 2], [3, 4, 5, 6], [7, 8, 9, 10, 11, 12, 13, 14], [15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]]

我使用pyparsing编写了一个简单的python MWE 而不是,所以我很清楚我正在尝试做什么:

A = range(1,31)

B, sub_b = [], []
consume  = 0

for a in A:
    if consume: 
        sub_b.append(a)
        consume -= 1
    else:
        if sub_b: B.append(sub_b)
        sub_b = [a,]
        consume = a
B.append(sub_b)
print B

两个(相关)问题:这可以通过BNF无上下文语法完成吗?如果是/否我怎样才能使用pyparsing

3 个答案:

答案 0 :(得分:3)

在无上下文语法中没有参数化大小 - 或者在常规语法中没有这样的东西。像consume这样的数值参数不是CFG模型的一部分,可以证明以不同的方式获得效果是不可能的。但是,您可以为任何固定长度编写作品,因此您可以为长度为1,长度为2,长度为3的块编写作品:

<block3> :: <number> <number> <number>

或类似地,将长度匹配为前缀或甚至作为后缀:

<block3a> :: 3 <number> <number> <number>
<block3b> :: <number> <number> <number> 3

等。为了做你想做的事,你需要一个包含那种规则的语法,对于你可能需要的所有N

给定的CFG将仅包含有限数量的这些作品。在数学上不可能编写可以处理无限参数化大小的CFG(以BNF或任何其他形式),无论是作为前缀还是作为后缀。实际上,您可以根据需要随时使用新产品更新您的CFG。例如,读取一个数字N并为您的语法创建一个规则blockN(如果它尚不存在)。但是没有单个 CFG可以捕获无限制的参数化大小。

编辑,因为您还询问了上下文相关的语法:它仍然不会这样做。问题是使用整数运算,而不是语法的类。 Chomsky层次结构中的任何形式语言都是根据有限数量的符号(标记)定义的,并且因为它们是无限多个整数,所以它们不能被赋予不同的含义(请注意,您的解析过程依赖于整数算术)。

如果您要将长度预处理为一系列恒星(* * * 4 7 10),则CFG解析器非常简单:

<group> :: * <group> <number>
<group> :: * <number>

这就是所谓的a^n b^n语言。您也可以使用符号“十”等符号。但是,如果没有预处理,唯一的解决方案(以及您的程序或图灵机在实践中所做的)是在您的语法中解释数字符号。例如,将“21”解析为十个十一。我怀疑这可以在CFG中完成(问题是处理任意长的数字而没有单独的规则,数百万,数十亿等),但我不太确定。无论哪种方式,它只是一个有趣的学术练习,因为使用真正的整数是如此容易。我相信人们已经用整数研究了形式语言的属性,但我不能说出任何相关的内容。

答案 1 :(得分:3)

Pyparsing包含帮助器countedArray,它完全符合您的要求。它需要一个参数expr,并将解析一个整数后跟&#39; n&#39; expr的实例。对于输入字符串,您可以写:

from pyparsing import *
from pprint import pprint

# make a long string of integers, starting at 0
source = ' '.join(map(str, range(50)))

# define an integer as a 'word' of numeric digits
integer = Word(nums)

# add a parse action to convert the parsed strings to integers, at parse time
integer.setParseAction(lambda t:int(t[0]))

# parse the source string into counted strings of integers, and print out
# with pprint
lists = OneOrMore(countedArray(integer)).parseString(source)
pprint(lists.asList())

打印出来:

[[],
 [2],
 [4, 5, 6],
 [8, 9, 10, 11, 12, 13, 14],
 [16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]]
  • 附加到integer的解析操作会转换数字字符串 返回整数(列表中的数字周围没有引号)。

  • 请注意 开头的空列表,由领先的&#39; 0&#39;的 源字符串。

  • 即使源字符串包含更多数字 过去30,对于另一个计数阵列来说还不够。

countedArray通过创建与前导整数匹配的表达式,然后是强制未定义的正向表达式来实现此技巧。附加到前导整数的解析操作会将一个expr*n表达式注入到强制转发中,然后解析以下内容:&#39; n&#39; exprs。您可以轻松编写countedArray(Word(alphas))并解析"4 the quick brown fox"以获取['the', 'quick', 'brown', 'fox']。正如@Aaron在他的回答中指出的那样,没有必要保留领先的计数器,因为你可以通过获取返回列表的len来轻松获得它。

pyparsing还支持使用FollowedBy和NotAny构造的更传统的前瞻(NotAny可以通过使用&#39;〜&#39;运算符缩写)。在这个字符串中,&#34;四分和七年前......&#34;你可以使用Word(alphas) + FollowedBy(Word('aeiou',alphas))挑选出以元音开头的单词后面的字符串,这些单词会匹配&#39;得分&#39;和&#39;年&#39 ;;或使用Word(alphas) + ~Literal('.')的句点后面没有句号的句子,这些句子会匹配每个单词,但前面有&#39;。在这种情况下,尝试通过输入字符串搜索匹配项,您可以使用searchStringscanString代替parseString

答案 2 :(得分:1)

不是。

拥有语法的重点在于它允许以人类可理解的方式定义数据。您的演示字符串是可读的,但为什么1不是3

您案例中的正确输入是:

[[2], [4, 5, 6], [8, 9, 10, 11, 12, 13, 14], [16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]]

,语法看起来像这样:

<model> :: <term>
<list>  :: <term> | <term> <opt-whitespace> <list>
<term>  :: '[' <list> ']'

然后,您可以通过查看列表的长度来恢复缺少的计数元素。

相关问题