语法格式描述规范¶
“语法说明书”的说明书:BNF、EBNF和ABNF到底是什么?¶
我们学习各种语言或者配置的时候,经常会看到一些 BNF、EBNF 之类的语法格式描述规范,虽然我们只是想了解某个文件格式、或者学习编程语言是怎么被"教"给电脑的,但是不啃这块骨头可能没法尝尝里面骨髓的味道。这块的资料大部分都写的比较晦涩难懂,满屏的"范式"、"终结符"、"产生式"……会有点头大。
别担心,请把这份文档想象成一份关于 "如何写一本给机器看的语法说明书" 的说明书。我们要聊的 BNF、EBNF、ABNF,就是三种写这种说明书的主流"写法"或"格式"。
就像您可以用中文、英文或图示来写一份家具组装手册一样,BNF、EBNF 和 ABNF 也是写"计算机语言组装手册"的不同"语言",它们各有特点,用在不同的地方。
为什么电脑需要一本"语法说明书"?¶
想象一下,您发明了一种新的棋类游戏,需要教给全世界的人。您肯定不会只说"车马炮随便走",对吧?您需要一本详尽的规则书:
棋盘什么样? (格子、线条)
棋子有哪些? (车、马、炮这些基本单位)
每个棋子怎么走? (马走日,象飞田)
怎么算赢? (将死对方)
电脑理解一种新的"语言"(无论是编程语言,还是 JSON/YAML 这种数据格式,或者是 HTTP 网络协议),和人类学习新游戏规则,本质上是一回事。
它需要一本极度精确、毫无歧义的"规则书",告诉它:
这种语言里, 最基本的、不可分割的积木块 是什么?(比如数字 0-9 ,字母 a-z ,符号 + =)
这些积木块, 如何能像搭乐高一样,组合成有意义的句子 ?(比如 数字 + 数字 可以组成一个 加法表达式)
BNF、EBNF、ABNF,就是用来写这种"乐高搭建规则书"的三种主流"写作规范"。 它们的核心目标都是: 用一套标准、无歧义的话,描述出一种语言所有合法句子的长相。
现在,让我们一个个来认识它们。
第一种写法:BNF(巴科斯范式)—— "最基础的乐高搭建手册"¶
一句话比喻:BNF 就像一本用最基础词汇写成的、事无巨细的乐高说明书。每个步骤都写得清清楚楚,但读起来可能有点啰嗦。
它的由来: 早在 1959 年,两位计算机科学家(约翰·巴科斯和彼得·诺尔)为了描述一种叫 ALGOL 60 的新编程语言,发明了 BNF。它的出现,第一次让"语法"这个东西能从模糊的人类语言,变成精确的数学公式。
BNF 怎么写?—— 认识四个核心"零件"¶
写 BNF 说明书,只用四种"零件":
- 终结符:就是"最小的积木块"
是什么: 语言里 不能再拆分 的基本单位。它们是最终出现在代码或数据里的实际字符。
怎么表示: 通常用 双引号 包起来。
- 例子:
"if" (一个关键字)
"+" (一个加号)
"0" (一个数字字符)
通俗理解: 就像乐高套装里,一个 单独的、最小颗粒的蓝色 2x4 积木块。你不能再把它切成更小的乐高了。
- 非终结符:就是"半成品组件"或"分类标签"
是什么: 它 不是 最终出现在代码里的东西,而是一个 中间概念 ,用来指代 一组特定的积木组合方式 。它需要被进一步"展开"或"解释"。
怎么表示: 用 尖括号 < > 包起来。
- 例子:
<数字> (代表所有合法的数字)
<加法表达式> (代表所有合法的加法式子)
通俗理解: 就像乐高说明书里,画着一个用很多小积木拼好的 "车轮组件" 的图标。这个图标本身不是积木,但它代表了"一堆按特定方式拼好的积木"。或者像一个菜谱里的 "步骤 A:制作面糊" ,它本身不是食材,而是一系列操作的集合。
- 产生式(规则):就是"组装步骤"
是什么: 定义 一个非终结符(半成品) 具体是怎么由其他零件(终结符或其他非终结符)组成的 。这是一条核心的"组装指令"。
怎么表示: 用 ::= 符号(可以读作"定义为")。左边是非终结符,右边是它的组成方式。
- 例子: <数字> ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
这句话的意思是:一个 <数字> 可以是 "0" ,或者 "1" ,或者 "2" …… 一直到 "9" 中的任意一个。
通俗理解: 就是乐高说明书里具体的一步:"步骤 3:把‘车轮组件’(非终结符)安装到‘车轴’(终结符)上。"
- 选择符:就是"或者"
是什么: 一个竖线 | ,表示"或者"。
例子: 上面的 "0" | "1" | ... | "9" 就用到了。意思是"可以是 0,或者 1,或者...,或者 9"。
通俗理解: 就像菜谱说"可用白糖或蜂蜜",二选一。
看一个完整的 BNF 例子:教电脑理解"整数加法"¶
假设我们想定义一种只能做一位数整数加法的超简单语言,比如 3+5 。
用 BNF 可以这样写这本"说明书":
<表达式> ::= <数字> | <表达式> "+" <数字>
<数字> ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
我们来"读"一下这本说明书:
第一行规则 (`<表达式>`):
它说,一个 <表达式> 可以是两种东西之一( | ):
单纯的一个 <数字> 。 比如 3 本身也是一个合法的表达式。
2. 一个 <表达式> 后面跟着一个加号 "+" ,再跟着一个 <数字> 。 这听起来有点绕,它其实是一种 "递归" 的定义。它允许我们把简单的表达式组合成更复杂的。 - 举例:如果 3 是一个 <表达式> (根据第 1 种情况),那么 3 + 5 就符合" <表达式> "+" <数字> "这个模式,所以 3+5 也是一个新的 <表达式> 。 - 更进一步, 3+5+2 也可以,因为它可以看作是 (3+5) 这个 <表达式> 后面加上 + 2 。
第二行规则 (`<数字>`):
这很简单,定义了什么是 <数字> :就是 0 到 9 这十个字符中的任意一个。
BNF 的特点总结:
优点: 极其严谨、基础,像数学定义,是另外两种范式的"老祖宗"。
缺点: 写起来 非常啰嗦 。对于"可选"或"重复"的情况,必须引入新的非终结符和递归规则,让说明书变得很长,不易读。
比如,想表达"这里可以有一个空格,也可以没有",在 BNF 里就要多写一条规则,很麻烦。
---
第二种写法:EBNF(扩展巴科斯范式)—— "带便利贴和快捷符号的升级版手册"¶
一句话比喻:EBNF 就像一本现代化的乐高说明书,增加了"重复此步骤 3 次"、"此零件可选"等快捷图标,让手册更薄、更易读。
它的由来: 因为 BNF 太啰嗦了,后来的人们在它的基础上,增加了一些方便的"快捷写法",形成了 EBNF。它更像是一种工程上的改良,为了让语法说明书对人类读者更友好。
EBNF 多了哪些"快捷图标"?¶
EBNF 保留了 BNF 的核心,但增加了三个超级有用的符号:
- 可选符号 `[ ... ]` :表示"这个东西可以有,也可以没有"。
例子: ["-"] <数字> 表示一个数字前面可以有一个可选的负号。这可以描述 5 和 -5 。
BNF 的啰嗦写法: 需要写成 <有符号数字> ::= <数字> | "-" <数字> ,多了一个非终结符。
- 重复符号 `{ ... }` :表示"这个东西可以重复出现很多次,或者一次也不出现"。
例子: <多位数> ::= <数字> {<数字>} 表示一个多位数,先有一个数字,后面可以跟着零个或多个额外的数字。这就能描述 7 , 42 , 10086 了。
BNF 的啰嗦写法: 必须用递归: <多位数> ::= <数字> | <多位数> <数字> ,理解起来没那么直观。
- 分组符号 `( ... )` :用来明确分组,常和选择符 `|` 一起用。
例子: ("+" | "-") <数字> 表示先是一个加号或减号,然后跟一个数字。括号明确了选择的范围。
有时也会用 = 代替 ::= ,用 ; 表示一条规则结束,看起来更清爽。
看一个完整的 EBNF 例子:描述一个简单的变量赋值语句¶
这次我们描述一个像 age = 25; 这样的语句。
(* 一个简单的赋值语句 *)
程序 = { 赋值语句 } ;
赋值语句 = 标识符 "=" 表达式 ";" ;
表达式 = 项 { ("+" | "-") 项 } ;
项 = 因子 { ("*" | "/") 因子 } ;
因子 = 数字 | 标识符 | "(" 表达式 ")" ;
标识符 = 字母 { 字母 | 数字 } ;
数字 = 数字 { 数字 } ;
字母 = "A" | "B" | ... | "Z" | "a" | "b" | ... | "z" ;
数字 = "0" | "1" | ... | "9" ;
我们来解读一下:
`{ 赋值语句 }` :表示程序由 0 个或多个赋值语句组成。
`表达式` 的定义 :它说一个表达式首先是一个 项 ,然后后面可以跟零个或多个 (一个加号或减号,再加上另一个 `项` ) 。这完美地描述了 1 + 2 - 3 这样的运算顺序。
`标识符` 的定义 :它说标识符必须以一个 字母 开头,后面可以跟零个或多个 字母 或 数字 。这描述了像 age , total_sum , x1 这样的变量名。
看,用上了 { } 和 ( | ) 之后,是不是比纯 BNF 清晰紧凑多了?
EBNF 的特点总结:
优点: 极大地提升了可读性和简洁性 。现在绝大多数编程语言(如 Python、C++)的官方语法文档,都采用 EBNF 或类似它的格式来书写。
缺点: 它有很多"方言",不同地方用的具体符号可能有点小差异(比如注释用 (* *) 还是 /* */ )。但核心思想一致。
---
第三种写法:ABNF(增强巴科斯范式)—— "为网络电报定制的精准密码本"¶
一句话比喻:ABNF 就像一份为发送电报或设计密码本而写的超级精确的指令集,特别关心"大小写"、"空格"、"二进制数"这些通信细节。
它的由来: BNF 和 EBNF 更多用于描述编程语言。而当我们需要定义 互联网协议 (比如网页用的 HTTP、发邮件用的 SMTP、网址 URL)时,情况更特殊。这些协议需要在网络上精确传输,对大小写、空格、回车换行、甚至是二进制数据都有严格规定。IETF(互联网标准组织)就基于 BNF,量身打造了 ABNF。
ABNF 的独特之处在哪里?¶
ABNF 为网络世界做了很多"增强":
- 核心规则库: 它预定义了一组"核心积木",就像给了你一盒标好名字的标准零件:
ALPHA = 任何字母 (A-Z, a-z)
DIGIT = 任何数字 (0-9)
SP = 空格
CRLF = 回车换行 (Windows 系统的行尾,网络协议常用)
HEXDIG = 十六进制数字 (0-9, A-F)
- 精确的数值表示: 可以直接用不同进制定义一个字符!
%d65 或 %x41 :表示十进制 65(十六进制 41)对应的字符,也就是大写字母 A 。
%x30-39 :表示十六进制 30 到 39 的所有字符,即数字 0-9 。
这保证了协议在任何电脑上解析的结果都一模一样。
- 灵活的重度计数: 可以精确指定一个东西重复多少次。
* : 零次或多次(和 EBNF 的 { } 一样)。
m*n : 至少 m 次,至多 n 次。例如 1*5DIGIT 表示 1 到 5 位数字。
n : 刚好 n 次。例如 3DIGIT 表示必须是 3 位数字。
大小写不敏感: 默认情况下,ABNF 认为 A 和 a 是等价的。这很适合网络协议(网址、域名通常不区分大小写)。如果想区分,需要用 %x 明确指定。
不同的选择符: 它用斜杠 / 来表示"或者",而不是竖线 | 。
看一个 ABNF 实战例子:一个 HTTP 网址的片段¶
HTTP 协议就是用 ABNF 定义的。我们来看一个简化版的"网址路径"定义:
; 一个路径由多个路径段组成,以斜杠开头
路径 = "/" [ 路径段 *( "/" 路径段 ) ]
路径段 = *PCHAR ; PCHAR代表路径中允许的字符集合
PCHAR = 字母 / 数字 / "-" / "." / "_" / "~" / ":" / "@"
解读一下: - `[ 路径段 *( "/" 路径段 ) ]` :
最外层的 [ ] 表示整个这一块是 可选 的(所以路径可以只是一个单独的 / )。
里面是:一个 路径段 ,后面跟着零个或多个 (一个 `/` 加一个 `路径段` ) 。
这完美描述了像 / 、 /api 、 /api/users 、 /home/index.html 这样的路径。
`*PCHAR` : * 表示零个或多个 PCHAR 字符。所以一个路径段可以是空,也可以是多个允许字符的组合。
ABNF 的特点总结: - 优点: 为通信协议而生,极度精确,无歧义 。特别擅长处理二进制、字符编码、网络传输的细微差别。 - 缺点: 对非专业人士最不友好 ,语法看起来比较怪异。但如果您是做网络编程或看 RFC 文档的,必须掌握它。
---
三种"写法"大比拼:一张表看懂怎么选¶
比较项 |
BNF(基础版) |
EBNF(增强易读版) |
ABNF(网络精准版) |
|---|---|---|---|
核心目的 |
奠定理论基础,最严谨的定义方式 |
让语法描述对人类更友好、更紧凑 |
为互联网协议提供精确无歧义的定义 |
就好比 |
用最基础单词写的详细法律条文 |
带图标的现代化用户手册 |
摩尔斯电码密码本或工程图纸 |
主要用在哪 |
计算机科学教科书,语言理论 |
“实际编程语言的官方文档” (如Python参考手册),配置文件格式定义 |
“互联网协议标准” (RFC文档,如HTTP, URL, 电子邮件格式) |
关键特征 |
引入了 [ ]、{ }、( ) 等快捷符号,描述能力更强、更简洁 |
有`%x`等数值定义、核心规则(如`DIGIT`)、精确重复计数(m*n),默认大小写不敏感 |
|
您最可能在哪遇到 |
学习编译原理时 |
“查看某编程语言或数据格式(如XML)的语法规则时” |
“阅读网络协议(如HTTP请求格式)的规范时” |
给非技术朋友的建议 |
知道它是老祖宗,了解基本概念即可 |
“这是最有用的!如果想看懂语法规则,重点学这个。” |
除非接触网络协议开发,否则仅作了解 |
总结与行动建议¶
它们不是三门语言,而是一门手艺的三种工具: BNF、EBNF、ABNF 都是在做同一件事—— 形式化地描述语法 。就像斧头、锯子、电钻都是木工工具一样,选哪个取决于你要做什么活儿。
您的学习路径应该是:
第 1 步:理解核心思想。 理解"终结符/非终结符/产生式"这个核心比喻(积木块/半成品/组装说明)。这是看懂一切语法描述的基础。
第 2 步:重点掌握 EBNF。 因为它在实际技术文档(语言手册、格式标准)中出现频率最高,而且它的快捷符号( [ ] 、 { } )非常直观,学会了就能看懂大部分语法图。
第 3 步:按需了解 ABNF。 如果您的工作涉及到网络编程、协议分析,或者需要阅读 IETF 的 RFC 文档,那么 ABNF 就是必选项。
第 4 步:回顾 BNF。 如果您对计算机科学理论感兴趣,可以回头看看 BNF,理解一切简洁的背后,最初是如何用严谨但繁琐的方式构建起来的。
一个简单的测试: 下次当您在任何技术文档里看到类似 Syntax: name = value [; comment] 这样的描述时,您就能反应过来,这用的是类似 EBNF 的风格: name = value 是必须的,而 [; comment] 这个分号和注释是可选的。
希望这份超长的"说明书"能帮您驱散对这些术语的迷雾。记住,它们只是工具,目的是为了让机器(和人类)更准确地理解规则。您不需要成为制造工具的大师,只需要学会识别和使用它们,就能在技术的世界里更从容地阅读那些重要的"规则书"了。