简介
巴科斯范式(Backus Normal Form简称为BNF),又称为巴科斯-诺尔范式,是一种上下文无关的语言,广泛地使用于程序设计语言、指令集、通信协议的语法表示中。在各种文献中,还存在巴科斯范式的一些变体,如扩展巴科斯范式(ENBF)或扩充巴科斯范式。
上下文无关语言
我们假定您已了解正则语言——一种通过有限状态机或正则表达式表达的语言,这种语言是字符串集的子集。我们现在来介绍另一种语言——上下文无关语言。任何的正则语言都是一种上下文无关语言。
大部分程序语言可被定义为上下文无关语言。要定义上下文无关语言,我们需要使用上下文无关语法,上下文无关语法的一个例子是巴科斯范式。
巴科斯范式
巴科斯范式是一种上下文无关语法,它使用一系列符号和表达式来创建字符串生成规则。一个简单的BNF生成规则类似如下:
<digit>::=0|1|2|3|4|5|6|7|8|9
上述文法代表任意一个0到9的任意一个数字。这里的尖括号“<>”是非终结符,如果一个非终结符出现在生成规则的右边,这意味着会有另一条生成规则来解释代替它的位置。考虑如下生成规则:
<fullname>::=<title><name><name>
这里<fullname>由<title>、<name>和另一个<name>字符串构成。例如,我们可以定义如下生成规则来代替<title>:
<title>::=Mr|Mrs|Ms|Miss|Dr
在这条规则中,Mr、Mrs、Ms、Miss和Dr均是终结符,它们是title的实际值,符号“|”在这里代表元字符,含义是“或"。这条规则的具体含义<title>可被解释为Mr或Mrs或Ms或Miss或Dr中的一个。
当你在表达式右侧看到非终结符时,就一定有一条生成规则,该非终结符是在左侧的。这个一直持续到任一非终结符都出现在某一条生成规则的左侧为止。让我们来看另一组生成规则:
<addition> ::= <number>+<number>
<number> ::= <sign><integer>|<integer>
<integer> ::= <digit>|<digit><integer>
<digit>::=0|1|2|3|4|5|6|7|8|9
<sign> ::= +|-
上述生成规则表示由任意两个整数相加得到的表达式。
巴科斯范式生成规则中的递归
巴科斯范式使用递归来表示一个或任意多个符号,例如一个正整数由一个或多个数字构成。寻常的表达式无法表达任意位数字构成的正整数,但通过递归,我们可以有个优美的方案:
<number> ::= <digit>|<digit><number> (1)
<digit> ::= 0|1|2|3|4|5|6|7|8|9 (2)
通过上述文法,我们来判断315是number。首先从(2)知5是digit,再从(1)知digit是number,从而正整数5是number。由(2)知1是digit,而5是number,从而由(1)15可表达成<digit><number>知15也是number。再由3是digit,而15是number,知315可表达为<digit><number>,从而315也是number。上述递归生成规则(1)包含一个基本的生成规则<digit>和一个通用的生成规则<digit><number>,在通用的生成规则中我们使用了递归定义。
语法解析树
当需要判断一个字符串是否满足某个生成规则时,语法解析树非常有用,因为可以将字符串解析成一个个简单的部分。考虑如下一组生成规则
<addition> ::= <number>+<number>
<number> ::= <sign><integer>|<integer>
<integer> ::= <digit>|<digit><integer>
<digit>::=0|1|2|3|4|5|6|7|8|9
<sign> ::= +|-
我们来通过语法解析树来判断57+85是否是加法表达式。首先57+85包含一个终止符“+”,要满足上述加法表达式<addition> ::= <number>+<number>,57+85需要表达成
接下来要判断57和85都是数字(<number>),而数字的表达式是<number> ::= <sign><integer>|<integer>,只要57和85是正整数(<integer>)或者是正整数前面带上符号,那57和85就是数字(<number>)。由于57和85前面既没有“+”也没有“-”,因此接下来要判断的是57和85是否是正整数(<integer>)。
由于正整数(<integer>)的表达式是<integer> ::= <digit>|<digit><integer>,这是一个递归表达式,代表正整数(<integer>)要么是0-9的数字(<digit>),要么是0-9的数字加上一个正整数(<digit><integer>)。因为57和85都不是0-9的数字(<digit>),因此必定是0-9的数字加上一个正整数(<digit><integer>)
我们观察57,发现5是0-9中的数字,接下来要判断的是7是否是正整数,由于7确实也是0-9中的数字,因此7也是正整数,同理可判断85也是正整数。
由上述分析可知57+85确实是一个加法表达式。
上图即是表达式“57+85”的语法解析树。
语法图
除了巴科斯范式符号,您还可以通过语法图的形式来描述巴科斯范式语法。
让我们先来看一个简单表达式
<special> ::= @ | ? | & | *
其语法图如下:
这个语法图代表表达式可以是“@”、“?”、“&”或“*”中的任意一个符号。当您从语法图的开始节点出发,可以挑选任意一条路径,从而选择是“@”、“?”、“&”或“*”中的任意一个字符达到结束节点。
我们再来看一个较为复杂例子的语法图,表达式如下:
<date> ::= <day-name><month-name>|<day-name><month-name><year>
<day-name> ::= Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday
我们先看,表达式<date>的语法图,语法图总是从左到右开始,顺着箭头流动方向进行阅读的。我们看到<date>首先包含<day-name>,然后包含<month-name>。从month-name节点出来后,可以选择直接打到结束,或者选择下面这条经过year节点的路径。这根表达式的含义是一致的:
<date> ::= <day-name><month-name>|<day-name><month-name><year>
必须包含表达式<day-name>,接下来是表达式<month-name>,然后可以选择性地包含表达式<year>。
再看<day-name>的语法图,它的含义十分简明,就是代表周一到周日中的任意一天:Monday或Tuesday或Wednesday或Thursday或Friday或Saturday或Sunday。
最后,我们来看下递归表达式的语法图,因为在巴科斯范式中唯一能表达循环的 就是递归。
我们来看表达式
<number> ::= <digit>|<digit><number>
的语法图:
这表示,从开始节点出发,经过digit节点。在从digit节点出来后,可以选择直接流向结束节点,或者选择再次流向digit节点。简洁来说,就是从开始节点出发,可以经过digit节点若干次后到达结束节点。在语法图中,我们使用流向重新返回到已经历过节点的方式来表达递归。
巴科斯范式与正则表达式之间的关系
正则表达式和有限状态机常用来描述正则语言。BNF是一个上下文无关语法的例子,它被用来描述上下文无关语言。因为所有的正则语言都一定是上下文无关语言,因此你可以将任意的正则表达式转换成BNF文法规则(或者是一个规则,或者是一组规则),当然反过来往往不成立。他们之间的关系如下:
举一个是上下文无关语言但不是正则语言的例子。当你想描述一个左右括号匹配正确的表达式时,每当出现一个左(开)括号,就一定有一个右括号与之对应。由于有限状态机除了自身处于当前状态外无法记住任何状态,因此可以描述有有不能匹配的左括号的状态,因此可以描述到底是有一个左括号没有匹配,还是两个或者三个等等。但正如有限状态机含义描述的那样,它只有有限个状态,无法描述无限个不匹配的左括号。
因此只要有无限个要计算的字符串元素,就需要使用上下文无关语言来进行描述。
扩展阅读:
[1] 正则语言:https://brilliant.org/wiki/regular-languages/#:~:text=A%20regular%20language%20is%20a,alphabet%2C%20or%20set%20of%20symbols.
[2] 正则表达式:https://isaaccomputerscience.org/concepts/dsa_toc_regex?examBoard=all&stage=all
[3] 正则表达式可视化工具:http://tool.rbtree.cn/regtool/
[4] 巴科斯范式:https://isaaccomputerscience.org/concepts/dsa_toc_bnf?examBoard=aqa&stage=all
[5]绘图工具:https://www.processon.com/diagrams