rust 中的 EBNF简介举例
<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>1. 什么是 EBNF?</li><li>2. 核心概念</li><li>3. EBNF 语法符号详解</li><li>4. 如何阅读 EBNF 规则</li><li>5. 示例</li><ul class="second_class_ul"><li>示例 1:简单的电子邮件地址</li><li>示例 2:算术表达式(简化版)</li></ul><li>6. 优点与局限</li><ul class="second_class_ul"></ul></ul></div><p>在 rust 参考手册中,有大量类似:</p><div class="jb51code"><pre class="brush:plain;">句法
MacroInvocation :
SimplePath ! DelimTokenTree
DelimTokenTree :
( TokenTree* )
| [ TokenTree* ]
| { TokenTree* }
TokenTree :
Token排除 定界符(delimiters) | DelimTokenTree
MacroInvocationSemi :
SimplePath ! ( TokenTree* ) ;
| SimplePath ! [ TokenTree* ] ;
| SimplePath ! { TokenTree* }</pre></div>
<p>这样抽象的玩意儿(宏 - Rust 参考手册)。这种阅读体验差的要死的东西,一看就是学术界搞出来的东西,这篇文章就是讲这种东西应该怎么读。</p>
<p class="maodian"></p><h2>1. 什么是 EBNF?</h2>
<p>在计算机科学中,当我们描述一种语言(比如编程语言、数据格式或配置文件)的结构时,需要一种精确、无歧义的方式。<strong>扩展巴科斯范式 (Extended Backus-Naur Form, EBNF)</strong> 就是这样一种元语言(描述其他语言的语言)标记法。它通过一系列严格定义的规则,清晰地表达一种语言的语法。</p>
<p>EBNF 源自并扩展了<strong>巴科斯范式 (Backus-Naur Form, BNF)</strong>。BNF 最初由约翰·巴科斯 (John Backus) 和彼得·诺尔 (Peter Naur) 为描述 ALGOL 60 编程语言的语法而设计。EBNF 在 BNF 的基础上增加了一些方便的符号,使得语法描述更为简洁和易读。</p>
<p class="maodian"></p><h2>2. 核心概念</h2>
<p>在学习 EBNF 之前,我们需要了解几个基本概念:</p>
<ul><li>**规则 (Rule / Production):**EBNF 的核心是规则。每条规则定义了一个特定的语法结构是如何由更小的部分组成的。**非终结符 (Non-terminal Symbol):**代表一个语法概念或一个可以被进一步分解的结构。它通常是其他规则的名称,表示“这里可以放置一个符合某某规则定义的结构”。</li><li>在 EBNF 中,非终结符通常用描述性的名称表示,例如<code>表达式</code>, <code>语句</code>, <code>数字</code>。在一些传统 BNF 中,非终结符会被尖括号 <code>< ></code> 包围,如 <code><expression></code>,但在现代 EBNF 中,直接使用名称更为常见。</li><li>**终结符 (Terminal Symbol):**代表语言中最小的、不可再分的词法单元或字面值。它们是语法结构最终落实到的具体字符或字符串。例如,编程语言中的关键字 (<code>if</code>, <code>while</code>)、操作符 (<code>+</code>, <code>-</code>, <code>*</code>, <code>/</code>)、数字字面量 (<code>123</code>, <code>3.14</code>)、字符串字面量 (<code>"hello"</code>) 等都是终结符。在 EBNF 中,终结符通常用引号 <code>"</code> 或 <code>'</code> 包围,或者直接写出(如果不会引起歧义)。</li></ul>
<p class="maodian"></p><h2>3. EBNF 语法符号详解</h2>
<p>EBNF 通过一些特殊符号来组织规则、非终结符和终结符:</p>
<ul><li><code>::=</code> 或 <code>=</code>(定义为):
<ul><li>这是规则定义的核心操作符,读作“被定义为”或“由…组成”。它将左侧的非终结符(要定义的结构)与右侧的该结构的具体构成联系起来。</li><li>示例:<code>数字 ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"</code></li><li>这条规则定义了“数字”可以是什么。</li></ul></li><li><code>|</code>(或 / 选择):<ul><li>竖线表示“或者”,用于在规则定义的右侧提供多个可能的选择。被 <code>|</code> 分隔的各项是互斥的选项。</li><li>示例:<code>布尔值 ::= "true" | "false"</code> “布尔值”可以是 “true” 或者 “false”。</li></ul></li><li>并列(顺序连接):<ul><li>当规则定义的右侧有多个符号(终结符或非终结符)依次排列时,它们表示这些部分必须按照给定的顺序出现。</li><li>示例:<code>赋值语句 ::= 标识符 "=" 表达式</code> 一条“赋值语句”由一个“标识符”,后跟一个等号终结符,再后跟一个“表达式”组成。</li></ul></li><li><code>()</code>(分组):</li></ul>
<p>圆括号用于将一组符号括起来,形成一个逻辑单元。这使得可以将重复、可选等操作符应用于整个组。</p>
<p>示例:<code>函数调用 ::= 函数名 "(" (参数列表)? ")"</code> 这里 <code>(参数列表)?</code> 被括号括起来,表示可选的参数列表部分。</p>
<ul><li><code>?</code>(可选 / 零次或一次):
<ul><li>问号表示其紧邻的前一个符号或分组是可选的,即可以出现零次或一次。</li><li>示例:<code>整数 ::= ("+" | "-")? 数字序列</code> 一个“整数”可以有一个可选的正负号,后面跟着一个“数字序列”。</li></ul></li><li><code>*</code>(重复零次或多次):</li><li>星号表示其紧邻的前一个符号或分组可以出现零次、一次或多次。</li><li>示例:<code>标识符 ::= 字母 (字母 | 数字)*</code> 一个“标识符”由一个“字母”开头,后面可以跟零个或多个“字母”或“数字”。</li><li><code>+</code>(重复一次或多次):<ul><li>加号表示其紧邻的前一个符号或分组必须至少出现一次,也可以出现多次。</li><li>示例:<code>数字序列 ::= 数字+</code> 一个“数字序列”由至少一个“数字”组成。</li></ul></li><li><code>[...]</code>(可选分组 - 另一种常见表示):<ul><li>一些 EBNF 变体使用方括号 <code>[]</code> 来表示一个可选的部分,等同于 <code>(...)?</code>。</li><li>示例:<code>参数列表 ::= "[" 参数 ("," 参数)* "]"</code> (这里假设 <code>[</code> 和 <code>]</code> 是可选参数列表的定界符) 或者:<code>整数 ::= ["+" | "-"] 数字序列</code> (等同于上面的 <code>("+" | "-")?</code>)</li></ul></li><li><code>{...}</code>(重复分组 - 另一种常见表示):<ul><li>一些 EBNF 变体使用花括号 <code>{}</code> 来表示一个可以重复零次或多次的部分,等同于 <code>(...)*</code>。</li><li>示例:<code>注释 ::= "/*" {任意字符} "*/"</code> 一个“注释”由 <code>/*</code> 开始,中间可以有零个或多个“任意字符”,并以 <code>*/</code> 结束。</li></ul></li><li>终结符的表示:<ul><li>如前所述,终结符通常用引号包围,例如 <code>"if"</code>, <code>'+'</code>。</li><li>如果终结符本身就是一个不会引起歧义的字符序列(例如不包含 EBNF 特殊符号),有时也可以直接写出。</li></ul></li><li>注释:<ul><li>不同的 EBNF 工具或规范可能有不同的注释方式,常见的有 <code>(* 这是一个注释 *)</code> 或类似 C 语言的 <code>//</code>。本教程主要关注语法规则本身。</li></ul></li></ul>
<p class="maodian"></p><h2>4. 如何阅读 EBNF 规则</h2>
<ul><li><strong>找到规则定义:</strong> 每条规则通常以一个非终结符开始,后跟 <code>::=</code> 或 <code>=</code>。</li><li><strong>从左到右分析:</strong> 阅读定义右侧的符号序列。</li><li><strong>理解选择:</strong> 遇到 <code>|</code> 时,知道这代表多个选项中的一个。</li><li><strong>注意顺序:</strong> 并列的符号表示它们必须按顺序出现。</li><li><strong>识别重复和可选:</strong> 留意 <code>*</code>, <code>+</code>, <code>?</code> (或 <code>[]</code>, <code>{}</code>) 的含义。</li><li><strong>处理分组:</strong> <code>()</code> (以及有时 <code>[]</code> 或 <code>{}</code>) 会将一部分符号视为一个整体。</li><li><strong>递归:</strong> 规则的右侧可以包含规则本身或其他非终结符,这种递归是定义复杂结构(如嵌套表达式)的关键。</li><li><strong>区分终结符与非终结符:</strong> 终结符是语言的“原子”,非终结符是需要进一步展开的“概念”。</li></ul>
<p class="maodian"></p><h2>5. 示例</h2>
<p>让我们看一些使用 EBNF 定义的例子:</p>
<p class="maodian"></p><h3>示例 1:简单的电子邮件地址</h3>
<p>EBNF</p>
<div class="jb51code"><pre class="brush:plain;">邮箱地址 ::= 用户名 "@" 域名
用户名 ::= 字符+
域名 ::= (子域名 ".")*顶级域名
子域名 ::= 字符+
顶级域名 ::= 字符+
字符 ::= 字母 | 数字 | "_" | "-"
字母 ::= "a" | ... | "z" | "A" | ... | "Z" // 省略所有字母
数字 ::= "0" | ... | "9" // 省略所有数字</pre></div>
<ul><li>这个例子定义了“邮箱地址”的结构。</li><li><code>用户名</code> 和 <code>字符</code> 用了 <code>+</code> 表示至少一个。</li><li><code>域名</code> 中的 <code>(子域名 ".")*</code> 表示可以有零个或多个由点分隔的子域名。</li></ul>
<p class="maodian"></p><h3>示例 2:算术表达式(简化版)</h3>
<p>EBNF</p>
<div class="jb51code"><pre class="brush:plain;">表达式 ::= 项 (("+" | "-") 项)*
项 ::= 因子 (("*" | "/") 因子)*
因子 ::= 数字 | "(" 表达式 ")"
数字 ::= ("0"|"1"|"2"|"3"|"4"|"5"|"6"|"7"|"8"|"9")+</pre></div>
<ul><li>这个例子展示了如何定义包含运算符优先级和括号的算术表达式。</li><li><code>表达式</code> 和 <code>项</code> 的定义是递归的(<code>因子</code>中包含 <code>表达式</code>),这允许嵌套。</li><li><code>(("+" | "-") 项)*</code> 表示可以有零个或多个由加号或减号连接的“项”。</li></ul>
<p>示例 3:一个简单列表结构</p>
<p>EBNF</p>
<div class="jb51code"><pre class="brush:plain;">列表 ::= "[" [元素 ("," 元素)*] "]"
元素 ::= 标识符 | 数字
标识符 ::= 字母 (字母 | 数字)*
// 字母和数字的定义同上</pre></div>
<ul><li><code>列表</code>由方括号包围。</li><li><code>[元素 ("," 元素)*]</code> 表示方括号内的内容是可选的(因为整个被 <code>[]</code> 包裹,这里 <code>[]</code> 是 EBNF 的可选符号,而非列表的定界符——为了清晰,我们也可以写成 <code>(元素 ("," 元素)*)?</code>)。</li><li>如果列表非空,则至少有一个“元素”,后续可以有零个或多个由逗号分隔的“元素”。</li></ul>
<p class="maodian"></p><h2>6. 优点与局限</h2>
<ul><li><strong>优点:</strong>
<ul><li><strong>精确性:</strong> 能够无歧义地描述复杂的语法结构。</li><li><strong>可读性:</strong> 相较于其他形式化方法,EBNF 相对容易阅读和理解(尤其是带有扩展符号时)。</li><li><strong>标准化:</strong> 虽然存在一些变体,但核心概念是共通的。</li><li><strong>工具支持:</strong> 有许多工具(如解析器生成器 ANTLR, YACC/Bison)可以直接使用 EBNF 或类似的语法定义来自动生成解析代码。</li></ul></li><li><strong>局限:</strong><ul><li><strong>上下文无关:</strong> EBNF 主要描述上下文无关文法。它通常不直接处理那些依赖于上下文的语义规则(例如,变量必须先声明后使用,类型匹配等)。这些通常需要额外的语义分析来处理。</li><li><strong>歧义性:</strong> 尽管目标是无歧义,但仍可能写出有歧义的 EBNF 规则(即一个输入串可以有多种解析方式)。消除歧义有时需要重写规则或依赖解析器的特定策略(如运算符优先级和结合性声明)。</li><li><strong>冗余和复杂性:</strong> 一旦里面定义的内容多了,难读得要死。</li></ul></li></ul>
<p>到此这篇关于rust 中的 EBNF 介绍的文章就介绍到这了,更多相关rust 中的 EBNF 介绍内容请搜索琼殿技术社区以前的文章或继续浏览下面的相关文章希望大家以后多多支持琼殿技术社区!</p>
<div class="art_xg">
</div>
</div>
<!--endmain-->
頁:
[1]