rust 中的 EBNF簡介舉例
在 rust 參考手冊中,有大量類似:
句法 MacroInvocation : SimplePath ! DelimTokenTree DelimTokenTree : ( TokenTree* ) | [ TokenTree* ] | { TokenTree* } TokenTree : Token排除 定界符(delimiters) | DelimTokenTree MacroInvocationSemi : SimplePath ! ( TokenTree* ) ; | SimplePath ! [ TokenTree* ] ; | SimplePath ! { TokenTree* }
這樣抽象的玩意兒(宏 - Rust 參考手冊)。這種閱讀體驗(yàn)差的要死的東西,一看就是學(xué)術(shù)界搞出來的東西,這篇文章就是講這種東西應(yīng)該怎么讀。
1. 什么是 EBNF?
在計(jì)算機(jī)科學(xué)中,當(dāng)我們描述一種語言(比如編程語言、數(shù)據(jù)格式或配置文件)的結(jié)構(gòu)時,需要一種精確、無歧義的方式。擴(kuò)展巴科斯范式 (Extended Backus-Naur Form, EBNF) 就是這樣一種元語言(描述其他語言的語言)標(biāo)記法。它通過一系列嚴(yán)格定義的規(guī)則,清晰地表達(dá)一種語言的語法。
EBNF 源自并擴(kuò)展了巴科斯范式 (Backus-Naur Form, BNF)。BNF 最初由約翰·巴科斯 (John Backus) 和彼得·諾爾 (Peter Naur) 為描述 ALGOL 60 編程語言的語法而設(shè)計(jì)。EBNF 在 BNF 的基礎(chǔ)上增加了一些方便的符號,使得語法描述更為簡潔和易讀。
2. 核心概念
在學(xué)習(xí) EBNF 之前,我們需要了解幾個基本概念:
- **規(guī)則 (Rule / Production):**EBNF 的核心是規(guī)則。每條規(guī)則定義了一個特定的語法結(jié)構(gòu)是如何由更小的部分組成的。**非終結(jié)符 (Non-terminal Symbol):**代表一個語法概念或一個可以被進(jìn)一步分解的結(jié)構(gòu)。它通常是其他規(guī)則的名稱,表示“這里可以放置一個符合某某規(guī)則定義的結(jié)構(gòu)”。
- 在 EBNF 中,非終結(jié)符通常用描述性的名稱表示,例如
表達(dá)式
,語句
,數(shù)字
。在一些傳統(tǒng) BNF 中,非終結(jié)符會被尖括號< >
包圍,如<expression>
,但在現(xiàn)代 EBNF 中,直接使用名稱更為常見。 - **終結(jié)符 (Terminal Symbol):**代表語言中最小的、不可再分的詞法單元或字面值。它們是語法結(jié)構(gòu)最終落實(shí)到的具體字符或字符串。例如,編程語言中的關(guān)鍵字 (
if
,while
)、操作符 (+
,-
,*
,/
)、數(shù)字字面量 (123
,3.14
)、字符串字面量 ("hello"
) 等都是終結(jié)符。在 EBNF 中,終結(jié)符通常用引號"
或'
包圍,或者直接寫出(如果不會引起歧義)。
3. EBNF 語法符號詳解
EBNF 通過一些特殊符號來組織規(guī)則、非終結(jié)符和終結(jié)符:
::=
或=
(定義為):- 這是規(guī)則定義的核心操作符,讀作“被定義為”或“由…組成”。它將左側(cè)的非終結(jié)符(要定義的結(jié)構(gòu))與右側(cè)的該結(jié)構(gòu)的具體構(gòu)成聯(lián)系起來。
- 示例:
數(shù)字 ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
- 這條規(guī)則定義了“數(shù)字”可以是什么。
|
(或 / 選擇):- 豎線表示“或者”,用于在規(guī)則定義的右側(cè)提供多個可能的選擇。被
|
分隔的各項(xiàng)是互斥的選項(xiàng)。 - 示例:
布爾值 ::= "true" | "false"
“布爾值”可以是 “true” 或者 “false”。
- 豎線表示“或者”,用于在規(guī)則定義的右側(cè)提供多個可能的選擇。被
- 并列(順序連接):
- 當(dāng)規(guī)則定義的右側(cè)有多個符號(終結(jié)符或非終結(jié)符)依次排列時,它們表示這些部分必須按照給定的順序出現(xiàn)。
- 示例:
賦值語句 ::= 標(biāo)識符 "=" 表達(dá)式
一條“賦值語句”由一個“標(biāo)識符”,后跟一個等號終結(jié)符,再后跟一個“表達(dá)式”組成。
()
(分組):
圓括號用于將一組符號括起來,形成一個邏輯單元。這使得可以將重復(fù)、可選等操作符應(yīng)用于整個組。
示例:函數(shù)調(diào)用 ::= 函數(shù)名 "(" (參數(shù)列表)? ")"
這里 (參數(shù)列表)?
被括號括起來,表示可選的參數(shù)列表部分。
?
(可選 / 零次或一次):- 問號表示其緊鄰的前一個符號或分組是可選的,即可以出現(xiàn)零次或一次。
- 示例:
整數(shù) ::= ("+" | "-")? 數(shù)字序列
一個“整數(shù)”可以有一個可選的正負(fù)號,后面跟著一個“數(shù)字序列”。
*
(重復(fù)零次或多次):- 星號表示其緊鄰的前一個符號或分組可以出現(xiàn)零次、一次或多次。
- 示例:
標(biāo)識符 ::= 字母 (字母 | 數(shù)字)*
一個“標(biāo)識符”由一個“字母”開頭,后面可以跟零個或多個“字母”或“數(shù)字”。 +
(重復(fù)一次或多次):- 加號表示其緊鄰的前一個符號或分組必須至少出現(xiàn)一次,也可以出現(xiàn)多次。
- 示例:
數(shù)字序列 ::= 數(shù)字+
一個“數(shù)字序列”由至少一個“數(shù)字”組成。
[...]
(可選分組 - 另一種常見表示):- 一些 EBNF 變體使用方括號
[]
來表示一個可選的部分,等同于(...)?
。 - 示例:
參數(shù)列表 ::= "[" 參數(shù) ("," 參數(shù))* "]"
(這里假設(shè)[
和]
是可選參數(shù)列表的定界符) 或者:整數(shù) ::= ["+" | "-"] 數(shù)字序列
(等同于上面的("+" | "-")?
)
- 一些 EBNF 變體使用方括號
{...}
(重復(fù)分組 - 另一種常見表示):- 一些 EBNF 變體使用花括號
{}
來表示一個可以重復(fù)零次或多次的部分,等同于(...)*
。 - 示例:
注釋 ::= "/*" {任意字符} "*/"
一個“注釋”由/*
開始,中間可以有零個或多個“任意字符”,并以*/
結(jié)束。
- 一些 EBNF 變體使用花括號
- 終結(jié)符的表示:
- 如前所述,終結(jié)符通常用引號包圍,例如
"if"
,'+'
。 - 如果終結(jié)符本身就是一個不會引起歧義的字符序列(例如不包含 EBNF 特殊符號),有時也可以直接寫出。
- 如前所述,終結(jié)符通常用引號包圍,例如
- 注釋:
- 不同的 EBNF 工具或規(guī)范可能有不同的注釋方式,常見的有
(* 這是一個注釋 *)
或類似 C 語言的//
。本教程主要關(guān)注語法規(guī)則本身。
- 不同的 EBNF 工具或規(guī)范可能有不同的注釋方式,常見的有
4. 如何閱讀 EBNF 規(guī)則
- 找到規(guī)則定義: 每條規(guī)則通常以一個非終結(jié)符開始,后跟
::=
或=
。 - 從左到右分析: 閱讀定義右側(cè)的符號序列。
- 理解選擇: 遇到
|
時,知道這代表多個選項(xiàng)中的一個。 - 注意順序: 并列的符號表示它們必須按順序出現(xiàn)。
- 識別重復(fù)和可選: 留意
*
,+
,?
(或[]
,{}
) 的含義。 - 處理分組:
()
(以及有時[]
或{}
) 會將一部分符號視為一個整體。 - 遞歸: 規(guī)則的右側(cè)可以包含規(guī)則本身或其他非終結(jié)符,這種遞歸是定義復(fù)雜結(jié)構(gòu)(如嵌套表達(dá)式)的關(guān)鍵。
- 區(qū)分終結(jié)符與非終結(jié)符: 終結(jié)符是語言的“原子”,非終結(jié)符是需要進(jìn)一步展開的“概念”。
5. 示例
讓我們看一些使用 EBNF 定義的例子:
示例 1:簡單的電子郵件地址
EBNF
郵箱地址 ::= 用戶名 "@" 域名 用戶名 ::= 字符+ 域名 ::= (子域名 ".")*頂級域名 子域名 ::= 字符+ 頂級域名 ::= 字符+ 字符 ::= 字母 | 數(shù)字 | "_" | "-" 字母 ::= "a" | ... | "z" | "A" | ... | "Z" // 省略所有字母 數(shù)字 ::= "0" | ... | "9" // 省略所有數(shù)字
- 這個例子定義了“郵箱地址”的結(jié)構(gòu)。
用戶名
和字符
用了+
表示至少一個。域名
中的(子域名 ".")*
表示可以有零個或多個由點(diǎn)分隔的子域名。
示例 2:算術(shù)表達(dá)式(簡化版)
EBNF
表達(dá)式 ::= 項(xiàng) (("+" | "-") 項(xiàng))* 項(xiàng) ::= 因子 (("*" | "/") 因子)* 因子 ::= 數(shù)字 | "(" 表達(dá)式 ")" 數(shù)字 ::= ("0"|"1"|"2"|"3"|"4"|"5"|"6"|"7"|"8"|"9")+
- 這個例子展示了如何定義包含運(yùn)算符優(yōu)先級和括號的算術(shù)表達(dá)式。
表達(dá)式
和項(xiàng)
的定義是遞歸的(因子
中包含表達(dá)式
),這允許嵌套。(("+" | "-") 項(xiàng))*
表示可以有零個或多個由加號或減號連接的“項(xiàng)”。
示例 3:一個簡單列表結(jié)構(gòu)
EBNF
列表 ::= "[" [元素 ("," 元素)*] "]" 元素 ::= 標(biāo)識符 | 數(shù)字 標(biāo)識符 ::= 字母 (字母 | 數(shù)字)* // 字母和數(shù)字的定義同上
列表
由方括號包圍。[元素 ("," 元素)*]
表示方括號內(nèi)的內(nèi)容是可選的(因?yàn)檎麄€被[]
包裹,這里[]
是 EBNF 的可選符號,而非列表的定界符——為了清晰,我們也可以寫成(元素 ("," 元素)*)?
)。- 如果列表非空,則至少有一個“元素”,后續(xù)可以有零個或多個由逗號分隔的“元素”。
6. 優(yōu)點(diǎn)與局限
- 優(yōu)點(diǎn):
- 精確性: 能夠無歧義地描述復(fù)雜的語法結(jié)構(gòu)。
- 可讀性: 相較于其他形式化方法,EBNF 相對容易閱讀和理解(尤其是帶有擴(kuò)展符號時)。
- 標(biāo)準(zhǔn)化: 雖然存在一些變體,但核心概念是共通的。
- 工具支持: 有許多工具(如解析器生成器 ANTLR, YACC/Bison)可以直接使用 EBNF 或類似的語法定義來自動生成解析代碼。
- 局限:
- 上下文無關(guān): EBNF 主要描述上下文無關(guān)文法。它通常不直接處理那些依賴于上下文的語義規(guī)則(例如,變量必須先聲明后使用,類型匹配等)。這些通常需要額外的語義分析來處理。
- 歧義性: 盡管目標(biāo)是無歧義,但仍可能寫出有歧義的 EBNF 規(guī)則(即一個輸入串可以有多種解析方式)。消除歧義有時需要重寫規(guī)則或依賴解析器的特定策略(如運(yùn)算符優(yōu)先級和結(jié)合性聲明)。
- 冗余和復(fù)雜性: 一旦里面定義的內(nèi)容多了,難讀得要死。
到此這篇關(guān)于rust 中的 EBNF 介紹的文章就介紹到這了,更多相關(guān)rust 中的 EBNF 介紹內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Rust使用csv crate構(gòu)建CSV文件讀取器的全過程
這篇文章主要學(xué)習(xí)如何基于Rust使用csv這個crate構(gòu)建一個CSV文件讀取器的過程,學(xué)習(xí)了csv相關(guān)的用法以及一些往期學(xué)過的crate的復(fù)習(xí),兼顧了實(shí)用性和Rust的學(xué)習(xí),需要的朋友可以參考下2024-05-05使用cargo install安裝Rust二進(jìn)制工具過程
cargoinstall是一個用于安裝包含可執(zhí)行目標(biāo)的Rust包的命令行工具,類似于系統(tǒng)軟件包管理器,但它為Rust開發(fā)者提供了一種簡潔的方式來安裝和管理命令行工具,安裝后,二進(jìn)制文件會存儲在$HOME/.cargo/bin目錄中,需要將該目錄添加到$PATH環(huán)境變量中才能在命令行中直接運(yùn)行2025-02-02Rust使用Sqlx連接Mysql的實(shí)現(xiàn)
數(shù)據(jù)庫在編程中是一個很重要的環(huán)節(jié),本文主要介紹了Rust使用Sqlx連接Mysql的實(shí)現(xiàn),記錄rust如何操作數(shù)據(jù)庫并以mysql為主的做簡單的使用說明,感興趣的可以了解一下2024-03-03Rust語言從入門到精通之Tokio的Channel深入理解
這篇文章主要為大家介紹了Rust語言從入門到精通之Tokio的Channel深入理解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05