上下文无关语法

本博文主要介绍基于巴科斯-诺尔范式的上下文无关语法,以及在计算机编程语言和解析技术上的应用。

引言

编程语言,协议规范,查询语言,文件格式,模式语言,内存布局,形式语言,配置文件,标记语言,格式化语言和元语言构成了我们计算的方式。

那么,什么构成了语言呢?
是语法。
语法是语言的语言。
在每一种语言的背后,都有一个决定其结构的语法。

下面我们将要解释语法和语法的常用符号,例如Backus-Naur Form(BNF)(巴科斯-诺尔范式,是由John Backus和Peter Naur首次引入一种形式化符号来描述给定语言的语法),Extended Backus-Naur Form(EBNF,扩展巴科斯-诺尔范式),ABNF(增强巴科斯-诺尔范式)以及常规扩展BNF。

现在几乎每一位新编程语言书籍的作者都会使用巴科斯范式来定义编程语言的语法规则
巴科斯-诺尔范式的内容大概是这样的:
(1) 在双引号中的字"word"代表着这些字符本身。double_quota 用来代表双引号
(2) 在双引号外的字(有可能是下划线)代表着语法部分
(3) 尖括号(<>)内包含的为必选项
(4) 方括号([])内包含的是可选项
(5) 大括号({})内包含的是可重复0到无数次的项
(6) 竖线(|)表示在其左右两边任选一项,相当于OR的意思
(7) ::= 是"被定义为"的意思。

下面是使用巴科斯范式定义的java语言中的for语句的实例:

FOR_STATEMENT ::= 
"for" "(" ( variable_declaration | (expression ";")| ";")
[ expression ] ";"
[ expression ]
")" statement

通过本次学习,您将能够识别和解释所有常用的语法符号。

语法定义一种语言

在计算机科学中,最常见的语法类型是上下文无关语法,而这些语法将作为本文的重点进行讲解。
上下文无关语法很丰富,可以用来描述很多(尽管不是全部)语言的递归句法结构,至于上下文无关语法之外的语法我们稍后再讨论。

上下文无关语法的组件:
一套规则是语法的核心组成部分。
每个规则有两个部分:(1)名称(2)名称的扩展

例如,我们正在创建一个语法来处理英文文本,我们可能会添加一条规则。如:
名词短语可以扩展为文章名词。
由此我们可以最终推断出“the dog” 是一个名词短语。

或者,如果我们正在描述一种编程语言,我们可以添加一个规则。如:
表达式可能扩展为表达式+表达式
如果我们将语法作为数学对象来使用,那么我们会把“可能扩展到”改为→
这样上面的例子就可以写成:
名词短语→ 文章名词
表达式→ 表达式+表达式

举个例子,考虑经典明确的表达式语法:

expr→term+expr
expr→term
term→term∗factor
term→factor
factor→(expr)
factor→const
const→integer
那么我们如何知道3*7 是一个有效的表达式呢?
因为:
expr 可以扩展为term
进而可以扩展为 term * factor
进而可以扩展为 factor * facor
进而可以扩展为 const * factor
进而可以扩展为 const * const
进而可以扩展为 3 * const
进而可以扩展为 3 * 7

巴科斯-诺尔范式(BNF)符号

当描述语言时,Backus-Naur范式(BNF)是用于编码语法的正式符号。
很多编程语言,协议和格式都在其规范中有一个BNF描述。
Backus-Naur范式的每一条规则都有如下结构:
name ::= 扩展
其中::= 表示为“可扩展为”,”可以替换为”
在一些文本中,名称也被称为非终端符号。

Backus-Naur范式中的每个名称都被尖括号<>包围,无论它出现在规则的左侧还是右侧。
扩展是一个包含终端符号和非终端符号的表达式,通过排列逻辑和选择逻辑连接在一起。
简单的并排表达式表示表达式排序关系。
终端符号是字面值类似于(”+” 或者”function”)或者字面量类型(如整数)
|符号表示选择逻辑。

举个例子说明一下,用BNF表示经典表达式语法如下:

<expr> ::= <term> "+" <expr>
         |  <term>
 <term> ::= <factor> "*" <term>
         |  <factor>
 <factor> ::= "(" <expr> ")"
           |  <const>
 <const> ::= integer

自然,我们可以为BNF的规则定义一个语法如下:

rule —> name ::= expansion
name —> <identifier>
expansion —> expansion expansion
expansion —> expansion | expansion
expansion —> name
expansion —> terminal
我们可以使用正则表达式[A-Za-z_0-9]+来定义identifier
terminal 可以是引号包围的字面量或者是字面量类型(如整数)。

扩展BNF(EBNF)符号

扩展Backus-Naur范式是BNF范式的扩展集合。EBNF不能说是BNF的超集,因为EBNF更改了部分规则定义关系。比如::= 改为了=,从非终端规则中删除了尖括号等。
EBNF范式中更为重要的差异是它扩展了附加操作。

取反操作
例如,规则:
 <term> :: = ["-"] <factor>
允许因素是相反数。

重复操作
在EBNF中,{}表示表达式可以重复零次或多次。
例如,规则:
 <args> :: = <arg> {"," <arg>}
定义了一个常规的逗号分隔参数列表。

分组操作
为了表达优先级,EBNF语法可以使用括号()来明确地定义扩展顺序。
例如,
 <expr> :: = <term>("+"|"-")<expr>
定义了一个允许加法和减法的表达式。

级联操作
在某些形式的EBNF中,运算符明确表示串联,而不是形式上的并列。

增强的BNF范式(ABNF)

协议规范通常使用增强的Backus-Naur范式(BNF)
ABNF原则上与EBNF相似,只不过其在选择操作,可选操作以及重复操作表达用的符号不同。
ABNF还提供了精确指定特定字节值的能力,这在协议中非常重要。
在ABNF中,
选择符号是/
选项符号是方括号[]
重复符号是前缀*
重复n次或者更多次符号是前缀n*
重复n到m次符号是前缀n*m
EBNF的 {expansion}在ABNF中使用*(expansion) 符号表示

以下是从RFC 5322中获取的日期和时间格式的定义。

date-time =[ day-of-week "," ] date time [CFWS]
day-of-week  =([FWS] day-name) / obs-day-of-week
day-name  ="Mon" / "Tue" / "Wed" / "Thu" /"Fri" / "Sat" / "Sun"
date=day month year
day =([FWS] 1*2DIGIT FWS) / obs-day
month  ="Jan" / "Feb" / "Mar" / "Apr" /"May" / "Jun" / "Jul" / "Aug" /"Sep" / "Oct" / "Nov" / "Dec"
year=(FWS 4*DIGIT FWS) / obs-year
time=time-of-day zone
time-of-day  =hour ":" minute [ ":" second ]
hour=2DIGIT / obs-hour
minute =2DIGIT / obs-minute
second =2DIGIT / obs-second
zone=(FWS ( "+" / "-" ) 4DIGIT) / obs-zone

常规扩展BNF范式

在语法中类似正则表达式的操作是很常见的。
例如,pyhon的语法规范使用它们。
在这些语法中:
postfix *表示"重复0次以上"
后缀+表示"重复1次以上"
后缀?意味着"0或1次"

Python中浮点文字的定义是一个很好的结合几个符号的例子:

floatnumber   ::=  pointfloat | exponentfloat
pointfloat    ::=  [intpart] fraction | intpart "."
exponentfloat ::=  (intpart | pointfloat) exponent
intpart       ::=  digit+
fraction      ::=  "." digit+
exponent      ::=  ("e" | "E") ["+" | "-"] digit+
它没有在名称周围使用的尖括号(如许多EBNF符号和ABNF),但使用:: =(如BNF)。 它将常规操作(如非空重复的+)与EBNF约定(如[])混合在一起进行选择。
整个Python语言的语法使用略有不同(但仍然是常规)的符号。

上下文无关语法之外的世界

以上我们讨论的都是上下文无关语法,虽然正则表达式在属于上下文无关语法:你可以将任何正则表达式重写为一个语法来表达这个正则表达式想要表达的内容,但是相反,并不是每个语法都可以转换成一个等价的正则表达式。

为了超越上下文无关语法的表达能力,需要在语法中允许一定程度的上下文敏感度。
上下文敏感性意味着终端符号可能出现在规则的左侧:
考虑下面的设计语法:
<top> :: = <a>")"
<a> :: ="("<exp>
"("<exp>")":: = 7
<top>可以展开成<a>")";
进而可能扩展为"("<exp>")";
进而可能扩展为7。

尽管设计上这种改变看起来很小,但是它使得语法等同于和它们描述的语言相关的图灵机。
通过限制规则使左侧的符号比右侧的所有扩展符号严格少,上下文相关的语法相当于(可确定的)线性有界自动机。

尽管有些语言是上下文敏感的,但是上下文敏感的语法很少用于描述计算机语言。
例如,由于C处理标识符和类型的方式的需要,C稍微有点上下文敏感性,但是这种上下文敏感性是通过特殊的约定来解决的,而不是通过在语法中引入上下文敏感性。

解析

解析是跟语法密切相关的一个话题。
解析需要一个语法和一个字符串,并回答两个问题:
1.字符串是否是文法的语言
2.字符串相对应的语法结构是什么

参考:

The language of languages

Parsing Techniques: A Practical Guide (Monographs in Computer Science)

猜你喜欢

转载自blog.csdn.net/u011489205/article/details/78991752