不要使用 ISO/IEC 14977:1996 扩展巴科斯范式 (EBNF)

David A. Wheeler

2023-03-21 (原始版本 2019-03-02)

如果你需要定义一门语言(例如编程语言或复杂的数据结构),使用某种扩展巴科斯范式 (EBNF) 通常会很有帮助。 很多人会直接用 Google 搜索,发现存在一个 ISO/IEC 标准 (ISO/IEC 14977:1996),然后就直接使用它... 而没有意识到这个非常古老的 ISO/IEC 标准有很多问题,不应该使用。

在这篇文章中,我将简要地解释 ISO/IEC 14977:1996 规范的问题,以及为什么我认为你应该避免使用它。我将首先讨论该规范本身的许多技术缺陷,然后讨论为什么盲目地遵守 ISO 是不合适的(因为有些人可能会这样做)。 当我讨论它的缺陷时,我还会将 14977 规范与一个常见的替代方案进行比较,即 W3C 可扩展标记语言 (XML) 1.0 (第五版) 中的 EBNF 符号。 我还将简要提及 IETF 的 RFC 5234, "Augmented BNF for Syntax Specifications: ABNF"。 IETF 的规范不如 14977 那么糟糕,尽管我不建议在 RFC 规范之外使用 RFC 5234。 在本文中,我主要关注 14977 的问题。 使用 14977 不一定是一个 灾难,但是很多人在使用 14977 时,没有意识到它存在一些非常严重的问题,并且有更好的替代方案可用。 显然,这些是我的个人观点,你可能不同意...但我希望本文能帮助你理解我为什么持有这些观点,并可能说服你。

规范本身存在严重问题

EBNF 的 全部意义 在于使描述语法清晰、明确和简洁成为可能。 在这里,我研究了免费提供的规范。 以下是我认为它的一些关键弱点:

  1. 它无法指示国际/Unicode 字符、代码点或字节值。 ISO/IEC 14977:1996 仅支持 ISO/IEC 646:1991 字符。 14977:1996 规范确实有一个“? ... ?” 符号来非正式地描述一个字符,但这与拥有适当的支持不同。 因此,它不能直接表示在处理文本时 ISO/IEC 10646 / Unicode 允许的全部代码点范围,并且它也不足以描述二进制格式。 更糟糕的是,它无法按 指示代码点。 你可能会认为,在文本格式的情况下,你可以通过插入用单引号或双引号括起来的 Unicode 字符来悄悄地违反标准,但即使在这种情况下,该规范也不充分。 没有指定代码点能力,就无法替代。 想象一下,在没有代码点值的情况下,试图区分这些值:"-", "‐", "‑", "‒", "–", "—", "―", "−", "﹣", 和 "-"。 它们是 U+002D (‘HYPHEN-MINUS’)、U+2010 (‘HYPHEN’)、U+2011 (‘NON-BREAKING HYPHEN’)、U+2012 (‘FIGURE DASH’)、U+2013 (‘EN DASH’)、U+2014 (‘EM DASH’)、U+2015 (‘HORIZONTAL BAR’)、U+2212 (‘MINUS SIGN’)、U+FE63 (‘SMALL HYPHEN-MINUS’)、U+FF0D (‘FULL-WIDTH HYPHEN-MINUS’)。 由于该标准中没有明确指定代码点的方法,因此这是一个问题。 尝试表示二进制格式时,这种遗漏也是一个问题。 相比之下,W3C 的符号可以轻松支持任意代码点; 只需编写 #xN,其中 N 是一个十六进制数。 顺便说一句,从技术上讲,ISO/IEC 10646 和 Unicode 并不是完全相同的规范,因为它们来自不同的组织。 在大多数情况下,这些区别无关紧要; 字符代码和编码形式在 Unicode 和 ISO/IEC 10646 之间(有意地)同步,对此每个人都心存感激。 ISO/IEC 10646 规范是公开提供的,这可能是由于来自 Unicode 联盟的竞争。 毕竟,Unicode 联盟是一个现代标准组织,它公开发布其规范,如果 ISO/IEC 10646 不是公开提供的,人们可能会一直忽略它。 也就是说,通常应该使用 Unicode,而不是 ISO/IEC 10646,因为 “Unicode 标准对实现施加了额外的约束,以确保它们在不同的平台和应用程序中以一致的方式处理字符”。
  2. 它无法指示字符范围。 ISO/IEC 14977:1996 没有标准方法来指示字符范围,而字符范围在语法中很常见。 相比之下,W3C 的符号可以轻松支持任意范围,只需编写 “[range]”。 一个例子应该可以清楚地说明为什么这很重要。 说“这个字符必须是 ASCII 大写字母、小写字母或十进制数字”是很常见的。 在 W3C 的符号中,这表示为 [a-zA-Z0-9]。 以下是如何在 ISO/IEC 14977:1996 中执行此操作(除了最后一行之外的所有行都直接来自规范的 8.1 节,因此这确实是期望):
letter
= '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' |
| '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';
digit
= '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7'
| '8' | '9';
letter or digit = letter | digit;

显然,像 [a-zA-Z0-9] 这样的表达式更短更清晰。 范围也使异常更清晰,例如,如果你省略了字母 O,在范围中会很明显,但在长列表中则不明显。 范围还降低了出现错误的风险; 如果意外省略了一个选项,在长列表中可能不会注意到该省略。 3. 它需要大量的逗号,因此使用它会产生难以阅读的语法。 到目前为止,语法中最常见的操作之一是串联(也称为排序)。 ISO/IEC 14977:1996 要求对每个串联使用逗号,因此任何 N 个符号的序列都将具有 N-1 个逗号。 这意味着,每个,规则,贯穿,整个,语法,都,装饰,着,逗号。 这不会影响表示语法的能力,但它使语法非常难以阅读,尤其是 如果规则本身涉及逗号。 由于 EBNF 符号的 全部意义 在于创建易于阅读的语法定义,因此几乎使所需语法符号的数量增加一倍是一个严重的错误。 W3C 的符号使用空格,完全消除了该问题。 4. 它没有建立在广泛使用的正则表达式符号之上。 最容易学习和使用的语言是与你已经知道的语言非常相似的语言。 今天,绝大多数软件开发人员了解正则表达式。 正则表达式 (regexes) 已内置到许多编程语言的语法中,包括 JavaScript、Ruby 和 Perl。 从技术上讲,Python 编程语言没有将正则表达式内置到其语法中,但它具有专为正则表达式设计的特殊字符串语法,并且其内置库支持它们。 正则表达式被广泛用于输入验证和许多其他目的。 POSIX 标准化了扩展正则表达式 (ERE)。 在 POSIX ERE 和内置于许多编程语言的正则表达式中,原子后面可以跟一个计数(“*”表示 0 个或多个,“+”表示 1 个或多个,“?”表示 0 个或 1 个)。 然而,ISO 14977 和 IETF 的 EBNF 格式 (RFC 5234) 使用不同的语法,该语法与软件开发人员广泛使用的语法不兼容。 没有充分的理由使用与开发人员每天使用的语法不同的语法。 5. 它具有一种奇怪、难以理解且容易被误解的“一个或多个”符号。 另一个常见的操作是识别某事物发生“一次或多次”。 正则表达式(在计算社区中被广泛使用和了解)使用 + 符号来表示这一点,例如,POSIX 扩展正则表达式和 Perl 兼容正则表达式中的 z+ 表示“一个或多个 z”。 相比之下,ISO/IEC 14977:1996 将“一个或多个”表示为 { symbol }-,这意味着“0 个或多个符号,然后减去空集”。 空集根本不表示任何符号 (!)... 这使得很容易忽略正在发生的事情。 这种构造也容易出错。 如果该表达式与后面的内容串联,则需要一个逗号(如果你忘记了,则以下表达式将从前者中减去)... 但是由于逗号无处不在,因此很容易没有注意到逗号 没有 出现的位置。 大多数具有空集的系统都用一个符号来表示它,以便于注意... 但 14977 除外。 整个符号非常违反直觉,以至于在应该使用时经常不使用(也许他们害怕它会被误解,甚至不知道它的存在)。 因此,他们最终会重复自己以表示这种常见的构造(例如,“foo, bar, baz {foo, bar, baz}”)。 这种奇怪的构造还需要重新向每个人解释,因为知道正则表达式的人比知道这种古怪的 ISO/IEC 14977:1996 符号的人要多得多。 W3C 的符号以计算社区的标准方式支持“一个或多个”,大多数软件开发人员已经知道:只需添加一个“+”后缀。 6. 它具有挑战性,难以理解,并且许多关键术语未定义。 我认为很多人都觉得该规范难以理解,这不是规范的良好属性。 它是抽象的,考虑到主题,这可能是必要的。 但是它有许多术语和定义对我来说似乎不直观,并且没有定义关键的基本术语,如 character、sign 或 symbol。 将其文本与 W3C 规范进行比较,后者更容易理解。

如果必须使用 14977,至少要避免替代表示字符。 编写规范时,其中一个担心是有些计算机和打字机(!)没有一些字符,例如“{“ 和 “}”,因此定义了诸如 “(:” 和 “:)” 之类的替代方案。 今天没有理由使用这种无稽之谈。

但是我不应该盲目地服从 ISO 和 IEC 吗?

对于许多人来说,技术问题就足够了。 但是其他人可能认为他们应该服从 ISO 和 IEC 的任何规定。 可能会让你感到惊讶,但是 ISO 和 IEC 不是来自高高在上的神。 它们只是众多标准制定机构中的两个。 仅仅因为他们编写了一个规范并不意味着你应该使用它。

首先,仅仅因为 ISO 或 IEC 编写了一个文档并不意味着人们会使用它。 毕竟,ISO 在 20 世纪 80 年代开发并发布了所谓的开放系统互连 (OSI) 标准,作为连接网络的唯一正确方法,而 OSI 被 IETF 开发的 TCP/IP 套件彻底击败。 任何致力于 ISO 开发的 OSI 标准的人都浪费了很多钱!

在这种特定情况下,即使 ISO 本身也没有在其发布的所有语言标准中使用 14977。 既然即使 ISO 也不总是使用 14977,你也没有理由需要这样做。 Vadim Zaytsev 在 2011 年发表的论文“BNF was Here: What Have We Done About the Unnecessary Diversity of Notation for Syntactic Definitions” 明确指出,许多 ISO 规范不使用 14977,并认为 14977 是一个失败。 不幸的是,大多数 ISO 标准不是公开提供的(正如我稍后将讨论的那样),因此进行调查成本太高。 也就是说,这是一个你可以检查的具体示例:Ada 编程语言标准(作为国际标准 ISO/IEC 8652:2012 发布) 在第 1.1.4 节中定义了自己的 BNF 格式。 请注意,它不使用 14977 符号(例如,它不使用逗号进行串联)。

我当然不是反 ISO 或反标准,远非如此。 我试图说服你,你不应该仅仅因为它来自 ISO 就使用它。 如果你需要国际标准机构提供的某些东西,那么值得注意的是,W3C 和 IETF 也是国际标准机构,它们指定了_不同的_ EBNF 符号。 特别是,W3C 的一个是一个合理的替代方案,这将是我在这里关注的重点(作为一个比较点)。

ISO 和 IEC 的一个更广泛的问题是,与现代标准制定组织不同,它们通常对其发布的 IT 标准收费,而不是公开提供它们。 通过“公开提供”,我的意思是“免费”; 即使 ISO 也使用这个术语。 相比之下,IETF、W3C 和其他现代标准制定组织始终公开提供标准。 这些费用在今天是不合理的。 分发文档几乎不花钱,并且 ISO 和 IEC 不向其作者(或作者的雇主)付费,因此为这些标准支付的所有费用都是剥削性的。 这些费用也极大地阻碍了标准的使用; 现代系统至少需要数万个标准(从广义上讲),因此虽然为一个文档收费(即使作者没有得到任何费用)是不合理的,但即使他们想获得所有文档,也没有人负担得起。 这些费用对小型企业和业余爱好者尤其有害,而世界依赖于他们。 在历史背景下,这些费用是有意义的,因为它们是购买和使用印刷机所必需的。 但是今天,没有人想要那样; 他们想要电子文档,立即,免费。 我一点也不反对利润; 利润动机为社会做了伟大的事情。 我反对剥削; 在某些情况下,ISO 对工作收费,但又不向做这项工作的人付费,也不免费提供这项工作。

当然,许多其他人也提出了同样的看法。 在 2018 年 4 月 4 日,用户 mycl 观察到“ISO Prolog (ISO/IEC 13211) 没有免费标准,这极大地损害了 Prolog 语言。 在这种情况下,最后一个免费提供的草案与最终标准截然不同,这使得情况变得更糟,因为并非每个人都知道这一点。 我注意到很多 Prolog 程序员不知道标准中有什么,什么没有——你经常在 SO 上看到给出的答案是依赖于实现的,而它们很容易用严格符合 ISO Prolog 的方式表达。”

对此我感到难过; 我认为 ISO 是一个重要的组织,但它已经迷失了方向。 ISO 已经做了一些好的工作! 我将继续使用 ISO 规范,只要它们是好的,并且我将在适当的时候与 ISO 合作。 特别是,我很高兴与 ISO 合作,只要结果将是一个公开提供的标准。 更一般地说,我 确实 认为拥有国际标准很重要。 我认为 ISO 需要开发并鼓励使用国际标准,而不是专注于对他人完成的工作收费。 如果你能找到一种方法来鼓励 ISO 更新其做法并加入现代世界,那将是太好了。 我 希望 从长远来看看到一个成功的 ISO,并且我认为它目前的政策不适合现代世界。

在这种情况下,收取不合理费用的问题较少,尽管情况并不理想。 值得庆幸的是,ISO/IEC 14977:1996 是少数 ISO 公开提供的规范 之一(这意味着免费提供)。 我发现 ISO 认为它开发的任何标准 公开提供是可以接受的,这很奇怪! 另一方面,它并非没有摩擦; 当我上次尝试时,很容易忽略免费版本,你必须先同意许可才能下载它,你得到一个必须解压缩的 zip 文件,而不是简单地获得实际规范,而且它是一个 PDF 文件,无法正确缩放到不同的屏幕尺寸(而不是干净的响应式 HTML 或至少是回流 PDF)。 将这个复杂的多步骤过程与获得用于同一任务的更好的 W3C 规范的体验进行比较:单击此处并在任何设备上开始阅读

希望我已经说服你,盲目地服从 ISO 是完全不合适的。 但是在这种情况下,获得规范不是主要问题。 问题是使用它。 该规范很糟糕,并且有更好的选择可用。

结论

当你的规范的主要优点是可以用打字机编写时,也许不应该将其作为首选规范。 该规范的缺点远大于其优点。 它被广泛认为是一个失败,因为它经常不被使用(甚至不被创建它的组织使用),但是因为它仍然存在,人们偶尔会犯尝试使用它的错误。

当然,我并不是唯一一个注意到 14977 问题的人。 论文“BNF was Here: What Have We Done About the Unnecessary Diversity of Notation for Syntactic Definitions” by Vadim Zaytsev (也可从 ACM 获得) 有一些有趣的评论。 他认为,在规范和手册中重用语法知识的最重要问题之一是“语法符号的多样性:在不失一般性的情况下,我们可以说每个语言文档都使用自己的符号,这通常是(扩展的)巴科斯范式的一种方言。” 该论文通过分析“38 个编程语言标准(ANSI、ISO、IEEE、W3C 等)、23 个包含语法的其他类型出版物(未经认可的书籍、科学论文、手册)和 8 个派生语法源来支持这一点,总共展示了 42 个语法符号,同时定义了 77 个语法(从 Algol 和 C++ 到 SQL 和 XPath)。” 他指出,“1996 年曾尝试在 ISO 中标准化符号,但最终只是在混乱中又增加了三个方言。” 他指出了一些 14977 未被采用的原因,并尖锐地指出 ISO/IEC 14977 甚至没有在所有 ISO 语言标准中使用。 ISO/IEC 14977 无意中成为了 XKCD 漫画“标准” 的完美演示。

简而言之,虽然拥有一个单一的符号会带来很大的优势,但是编写语言规范的社区普遍拒绝了 ISO 14977,原因有很多。 在你承诺使用它之前,应该意识到这种拒绝。 是的,它是由 ISO/IEC 发布的,但这并不意味着每个人都使用它 - 甚至不意味着他们_应该_使用它。

我无意冒犯那些开发 ISO/IEC 14977:1996 的人。 但是,我认为 14977 有很多问题,并且有明显的 EBNF 替代方案通常应该使用。 这些替代规范之一位于 W3C 可扩展标记语言 (XML) 1.0 (第五版) 中。 W3C 规范与典型的正则表达式语法更相似,这使得今天的软件开发人员更容易理解,避免了 14977:1996 的关键问题,并且已经清楚地描述了它。 更一般地说,你应该避免使用 14977:1996。

欢迎访问我的主页 https://dwheeler.com。 你可能还想看看我的论文 Why OSS/FS? Look at the Numbers! 和我的关于 如何开发安全程序 的书。

(C) 版权所有 David A. Wheeler。 在 Creative Commons Attribution-ShareAlike version 3.0 或更高版本 (CC-BY-SA-3.0+) 下发布。