12.2. 扩展BNF范式

Backus-Naur 范式,简称 BNF,是一种精确描述规则的语言,被应用于多种技术规范。 例如,众多互联网协议的许多技术规范,称为 RFC,除了文字说明以外,都包含了以 BNF 编写的规则。

Boost.Spirit 支持扩展BNF范式(EBNF),可以用比 BNF 更简短的方式来指定规则。 EBNF 的主要优点就是简短,从而写法更简单。

请注意,EBNF 有几种不同的变体,它们的语法可能有些差异。 本章以及 Boost.Spirit 所使用的 EBNF 语法类似于正则表达式。

要使用 Boost.Spirit,你必须懂得 EBNF。 多数情况下,开发者已经知道 EBNF,因此才会选择 Boost.Spirit 来重用以前用 EBNF 表示的规则。 以下是对 EBNF 的一个简短介绍;如果需要对本章当中以及 Boost.Spirit 所使用的语法有一个快速的参考,请查看 W3C XML 规范,其中包含了一个 短摘要

digit="0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"

严格地讲,EBNF 以生成规则来表示规则。 可以将任意数量的生成规则组合起来,描述一个特定的格式。 以上格式只包含一个生成规则。 它定义了一个 digit 是由0至9之间的任一数字组成。

digit 这样的定义被称为非终结符号。 以上定义中的数字 0 到 9 则被称为终结符号。 这些符号不具有任意特定意义,而且很容易识别出来,因为它们是用双引号引起来的。

所有数字值是用竖直符相连的,其意义与 C++ 中的 || 操作符一样:多选一。

一句话,这个生成规则指明了0至9之间的任一数字都是一个 digit

integer=("+" | "-")? digit+

这个新的非终结符 integer 包含至少一个 digit,而且可选地以一个加号或减号开头。

integer 的定义用到了多个新的操作符。 圆括号用于创建一个子表达式,就象它在数学中的作用。 其它操作符可应用于这些子表达式。 问号表示这个子表达式只能出现一次或不出现。

digit 之后的加号表示相应的表达式必须出现至少一次。

这个新的生成规则定义了一个任意的正或负的整数。 一个 digit 正好是一个数字,而一个 integer 则可以由多个数字组成,且可以被标记为无符号的或有符号的。 因此 5 即是一个 digit 也是一个 integer,而 +5 则只是一个 integer。 同样地,169 或 -8 也只是 integer

通过定义和组合各种非终结符,可以创建越来越复杂的生成规则。

real=integer "." digit*

integer 的定义表示的是整数,而 real 的定义则表示了浮点数。 这个规则基于前面已定义的非终结符 integerdigit,以一个句点号分隔。 digit 之后的星类表示点号之后的数字是可选的:可以有任意多个数字或没有数字。

浮点数如 1.2, -16.99 甚至 3. 都符合 real 的定义。 但是,当前的定义不允许浮点数不带前导的零,如 .9。

正如本章开始的时候所提到的,接下来我们要用 Boost.Spirit 开发一个 JSON 格式的词法分析器。 为此,需要用 EBNF 来给出 JSON 格式的规则。

object="{" member ("," member) "}"
member=string ":" value
string='"' character '"'
value=string | number | object | array | "true" | "false" | "null"
number=integer | real
array="[" value ("," value)* "]"
character="a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z"

JSON 格式基于一些包含了键值和值的成对的对象,它们被花括号括起来。 其中键值是普通的字符串,而值可以是字符串、数字值、数组、其它对象或是字面值 true, falsenull。 字符串是由双引号引起来的连续字符。 数字值可以是整数或浮点数。 数组包含以逗号分隔的值,并以方括号括起来。

请注意,以上定义并不完整。 一方面,character 的定义缺少了大写字母以及其它字符;另一方面,JSON 还特别支持 Unicode 或控制字符。 这些现在都可以先忽略掉,因为 Boost.Spirit 定义了常用的非终结符号,如字母数字字符,以减少你打字的次数。 另外,稍后在代码中,字符串被定义为除双引号以外的任意字符的连续串。 由于双引号用于结束一个字符串,所以其它所有字符都在字符串中使用。 上述 EBNF 并不如此表示,因为 EBNF 要求定义一个包含除单个字符外的所有字符的非终结符号,应该定义一个例外来排除。

以下是使用了上述定义的 JSON 格式的一个例子。

  1. {
  2. "Boris Schäling" :
  3. {
  4. "Male": true,
  5. "Programming Languages": [ "C++", "Java", "C#" ],
  6. "Age": 31
  7. }
  8. }

整个对象由最外层的花括号给出,它包含了一个键-值对。 键值是 "Boris Schäling",值则是一个新的对象,包含多个键-值对。 其中所有键值均为字符串,而值则分别为字面值 true,一个包含几个字符串的数组,以及一个数字值。

以上所定义的 EBNF 规则现在就可用于通过 Boost.Spirit 开发一个可以读取以上 JSON 格式的词法分析器。