语法格式描述规范

“语法说明书”的说明书:BNF、EBNF和ABNF到底是什么?

我们学习各种语言或者配置的时候,经常会看到一些 BNF、EBNF 之类的语法格式描述规范,虽然我们只是想了解某个文件格式、或者学习编程语言是怎么被"教"给电脑的,但是不啃这块骨头可能没法尝尝里面骨髓的味道。这块的资料大部分都写的比较晦涩难懂,满屏的"范式"、"终结符"、"产生式"……会有点头大。

别担心,请把这份文档想象成一份关于 "如何写一本给机器看的语法说明书" 的说明书。我们要聊的 BNF、EBNF、ABNF,就是三种写这种说明书的主流"写法"或"格式"。

就像您可以用中文、英文或图示来写一份家具组装手册一样,BNF、EBNF 和 ABNF 也是写"计算机语言组装手册"的不同"语言",它们各有特点,用在不同的地方。

为什么电脑需要一本"语法说明书"?

想象一下,您发明了一种新的棋类游戏,需要教给全世界的人。您肯定不会只说"车马炮随便走",对吧?您需要一本详尽的规则书:

  • 棋盘什么样? (格子、线条)

  • 棋子有哪些? (车、马、炮这些基本单位)

  • 每个棋子怎么走? (马走日,象飞田)

  • 怎么算赢? (将死对方)

电脑理解一种新的"语言"(无论是编程语言,还是 JSON/YAML 这种数据格式,或者是 HTTP 网络协议),和人类学习新游戏规则,本质上是一回事。

它需要一本极度精确、毫无歧义的"规则书",告诉它:

  1. 这种语言里, 最基本的、不可分割的积木块 是什么?(比如数字 0-9 ,字母 a-z ,符号 + =

  2. 这些积木块, 如何能像搭乐高一样,组合成有意义的句子 ?(比如 数字 + 数字 可以组成一个 加法表达式

BNF、EBNF、ABNF,就是用来写这种"乐高搭建规则书"的三种主流"写作规范"。 它们的核心目标都是: 用一套标准、无歧义的话,描述出一种语言所有合法句子的长相。

现在,让我们一个个来认识它们。

第一种写法:BNF(巴科斯范式)—— "最基础的乐高搭建手册"

一句话比喻:BNF 就像一本用最基础词汇写成的、事无巨细的乐高说明书。每个步骤都写得清清楚楚,但读起来可能有点啰嗦。

它的由来: 早在 1959 年,两位计算机科学家(约翰·巴科斯和彼得·诺尔)为了描述一种叫 ALGOL 60 的新编程语言,发明了 BNF。它的出现,第一次让"语法"这个东西能从模糊的人类语言,变成精确的数学公式。

BNF 怎么写?—— 认识四个核心"零件"

写 BNF 说明书,只用四种"零件":

  1. 终结符:就是"最小的积木块"
    • 是什么: 语言里 不能再拆分 的基本单位。它们是最终出现在代码或数据里的实际字符。

    • 怎么表示: 通常用 双引号 包起来。

    • 例子:
      • "if" (一个关键字)

      • "+" (一个加号)

      • "0" (一个数字字符)

    • 通俗理解: 就像乐高套装里,一个 单独的、最小颗粒的蓝色 2x4 积木块。你不能再把它切成更小的乐高了。

  2. 非终结符:就是"半成品组件"或"分类标签"
    • 是什么:不是 最终出现在代码里的东西,而是一个 中间概念 ,用来指代 一组特定的积木组合方式 。它需要被进一步"展开"或"解释"。

    • 怎么表示:尖括号 < > 包起来。

    • 例子:
      • <数字> (代表所有合法的数字)

      • <加法表达式> (代表所有合法的加法式子)

    • 通俗理解: 就像乐高说明书里,画着一个用很多小积木拼好的 "车轮组件" 的图标。这个图标本身不是积木,但它代表了"一堆按特定方式拼好的积木"。或者像一个菜谱里的 "步骤 A:制作面糊" ,它本身不是食材,而是一系列操作的集合。

  3. 产生式(规则):就是"组装步骤"
    • 是什么: 定义 一个非终结符(半成品) 具体是怎么由其他零件(终结符或其他非终结符)组成的 。这是一条核心的"组装指令"。

    • 怎么表示:::= 符号(可以读作"定义为")。左边是非终结符,右边是它的组成方式。

    • 例子: <数字> ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
      • 这句话的意思是:一个 <数字> 可以是 "0" ,或者 "1" ,或者 "2" …… 一直到 "9" 中的任意一个。

    • 通俗理解: 就是乐高说明书里具体的一步:"步骤 3:把‘车轮组件’(非终结符)安装到‘车轴’(终结符)上。"

  4. 选择符:就是"或者"
    • 是什么: 一个竖线 | ,表示"或者"。

    • 例子: 上面的 "0" | "1" | ... | "9" 就用到了。意思是"可以是 0,或者 1,或者...,或者 9"。

    • 通俗理解: 就像菜谱说"可用白糖或蜂蜜",二选一。

看一个完整的 BNF 例子:教电脑理解"整数加法"

假设我们想定义一种只能做一位数整数加法的超简单语言,比如 3+5

用 BNF 可以这样写这本"说明书":

<表达式> ::= <数字> | <表达式> "+" <数字>
<数字> ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"

我们来"读"一下这本说明书:

  • 第一行规则 (`<表达式>`):

    • 它说,一个 <表达式> 可以是两种东西之一( | ):

      1. 单纯的一个 <数字> 。 比如 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 的核心,但增加了三个超级有用的符号:

  1. 可选符号 `[ ... ]` :表示"这个东西可以有,也可以没有"。
    • 例子: ["-"] <数字> 表示一个数字前面可以有一个可选的负号。这可以描述 5-5

    • BNF 的啰嗦写法: 需要写成 <有符号数字> ::= <数字> | "-" <数字> ,多了一个非终结符。

  2. 重复符号 `{ ... }` :表示"这个东西可以重复出现很多次,或者一次也不出现"。
    • 例子: <多位数> ::= <数字> {<数字>} 表示一个多位数,先有一个数字,后面可以跟着零个或多个额外的数字。这就能描述 7 , 42 , 10086 了。

    • BNF 的啰嗦写法: 必须用递归: <多位数> ::= <数字> | <多位数> <数字> ,理解起来没那么直观。

  3. 分组符号 `( ... )` :用来明确分组,常和选择符 `|` 一起用。
    • 例子: ("+" | "-") <数字> 表示先是一个加号或减号,然后跟一个数字。括号明确了选择的范围。

    • 有时也会用 = 代替 ::= ,用 ; 表示一条规则结束,看起来更清爽。

看一个完整的 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 为网络世界做了很多"增强":

  1. 核心规则库: 它预定义了一组"核心积木",就像给了你一盒标好名字的标准零件:
    • ALPHA = 任何字母 (A-Z, a-z)

    • DIGIT = 任何数字 (0-9)

    • SP = 空格

    • CRLF = 回车换行 (Windows 系统的行尾,网络协议常用)

    • HEXDIG = 十六进制数字 (0-9, A-F)

  2. 精确的数值表示: 可以直接用不同进制定义一个字符!
    • %d65%x41 :表示十进制 65(十六进制 41)对应的字符,也就是大写字母 A

    • %x30-39 :表示十六进制 30 到 39 的所有字符,即数字 0-9

    • 这保证了协议在任何电脑上解析的结果都一模一样。

  3. 灵活的重度计数: 可以精确指定一个东西重复多少次。
    • * : 零次或多次(和 EBNF 的 { } 一样)。

    • m*n : 至少 m 次,至多 n 次。例如 1*5DIGIT 表示 1 到 5 位数字。

    • n : 刚好 n 次。例如 3DIGIT 表示必须是 3 位数字。

  4. 大小写不敏感: 默认情况下,ABNF 认为 Aa 是等价的。这很适合网络协议(网址、域名通常不区分大小写)。如果想区分,需要用 %x 明确指定。

  5. 不同的选择符: 它用斜杠 / 来表示"或者",而不是竖线 |

看一个 ABNF 实战例子:一个 HTTP 网址的片段

HTTP 协议就是用 ABNF 定义的。我们来看一个简化版的"网址路径"定义:

; 一个路径由多个路径段组成,以斜杠开头
路径 = "/" [ 路径段 *( "/" 路径段 ) ]
路径段 = *PCHAR  ; PCHAR代表路径中允许的字符集合
PCHAR = 字母 / 数字 / "-" / "." / "_" / "~" / ":" / "@"

解读一下: - `[ 路径段 *( "/" 路径段 ) ]`

  • 最外层的 [ ] 表示整个这一块是 可选 的(所以路径可以只是一个单独的 / )。

  • 里面是:一个 路径段 ,后面跟着零个或多个 (一个 `/` 加一个 `路径段` )

  • 这完美描述了像 //api/api/users/home/index.html 这样的路径。

  • `*PCHAR`* 表示零个或多个 PCHAR 字符。所以一个路径段可以是空,也可以是多个允许字符的组合。

ABNF 的特点总结: - 优点: 为通信协议而生,极度精确,无歧义 。特别擅长处理二进制、字符编码、网络传输的细微差别。 - 缺点: 对非专业人士最不友好 ,语法看起来比较怪异。但如果您是做网络编程或看 RFC 文档的,必须掌握它。

---

三种"写法"大比拼:一张表看懂怎么选

BNF、EBNF、ABNF 快速选择指南

比较项

BNF(基础版)

EBNF(增强易读版)

ABNF(网络精准版)

核心目的

奠定理论基础,最严谨的定义方式

让语法描述对人类更友好、更紧凑

为互联网协议提供精确无歧义的定义

就好比

用最基础单词写的详细法律条文

带图标的现代化用户手册

摩尔斯电码密码本或工程图纸

主要用在哪

计算机科学教科书,语言理论

“实际编程语言的官方文档” (如Python参考手册),配置文件格式定义

“互联网协议标准” (RFC文档,如HTTP, URL, 电子邮件格式)

关键特征

只有`::=`、|< >`" "`这几个符号,描述重复/可选很啰嗦

引入了 [ ]{ }( ) 等快捷符号,描述能力更强、更简洁

有`%x`等数值定义、核心规则(如`DIGIT`)、精确重复计数(m*n),默认大小写不敏感

您最可能在哪遇到

学习编译原理时

“查看某编程语言或数据格式(如XML)的语法规则时”

“阅读网络协议(如HTTP请求格式)的规范时”

给非技术朋友的建议

知道它是老祖宗,了解基本概念即可

“这是最有用的!如果想看懂语法规则,重点学这个。”

除非接触网络协议开发,否则仅作了解

总结与行动建议

  1. 它们不是三门语言,而是一门手艺的三种工具: BNF、EBNF、ABNF 都是在做同一件事—— 形式化地描述语法 。就像斧头、锯子、电钻都是木工工具一样,选哪个取决于你要做什么活儿。

  2. 您的学习路径应该是:

    • 第 1 步:理解核心思想。 理解"终结符/非终结符/产生式"这个核心比喻(积木块/半成品/组装说明)。这是看懂一切语法描述的基础。

    • 第 2 步:重点掌握 EBNF。 因为它在实际技术文档(语言手册、格式标准)中出现频率最高,而且它的快捷符号( [ ]{ } )非常直观,学会了就能看懂大部分语法图。

    • 第 3 步:按需了解 ABNF。 如果您的工作涉及到网络编程、协议分析,或者需要阅读 IETF 的 RFC 文档,那么 ABNF 就是必选项。

    • 第 4 步:回顾 BNF。 如果您对计算机科学理论感兴趣,可以回头看看 BNF,理解一切简洁的背后,最初是如何用严谨但繁琐的方式构建起来的。

  3. 一个简单的测试: 下次当您在任何技术文档里看到类似 Syntax: name = value [; comment] 这样的描述时,您就能反应过来,这用的是类似 EBNF 的风格: name = value 是必须的,而 [; comment] 这个分号和注释是可选的。

希望这份超长的"说明书"能帮您驱散对这些术语的迷雾。记住,它们只是工具,目的是为了让机器(和人类)更准确地理解规则。您不需要成为制造工具的大师,只需要学会识别和使用它们,就能在技术的世界里更从容地阅读那些重要的"规则书"了。