貧道採用 Rust 法咒來撰寫零版編譯器,以下展示其分詞器實作。
首先,寫出合適的類型來表達詞的種類:
// Rust 慣以駝峰式命名類型
// 漢語無大小寫,本作慣例以全形英文字母O來當類型的開頭
// Rust 管制識別符的字元組成,不允許 ◉、⦿、☯︎ 等等萬國碼,故採用常見的全形O來代替。
#[derive(Debug)]
enum O運算子 {
加,
減,
乘,
除,
}
#[derive(Debug)]
pub enum O詞 {
元,
左括號,
右括號,
運算子(O運算子),
等,
音界,
數字(i64),
變數(String),
}
接下來,就是把上一篇文定義好的狀態機刻出來了,觀察狀態轉移的出邊,字符能分為四類,為此貧道寫了以下輔助函數備用:
enum O字類 {
特殊符號,
數字,
元,
其他, // 也就是 x
}
fn 字類(字: &char) -> O字類 {
match 字 {
'+' | '-' | '*' | '/' | '=' | '(' | ')' | '・' | '\n' => {
O字類::特殊符號
}
'元' => O字類::元,
'1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '0' => {
O字類::數字
}
_ => O字類::其他,
}
}
想模擬狀態機,最簡單的方法是:定義一個狀態變數,建好狀態轉移表。狀態機啟動時將狀態變數設為初始值,接下來根據接收到的字元,以及建好的表進行狀態轉移直到字元流結束。
貧道在此用了稍微不同的寫法,將每個狀態都以一個函式來表達(除了單字詞的情況太簡單,跟起點態的函數寫在一起),函式根據當前字元呼叫下個狀態函式。
這種寫法不用額外宣告一個變數當狀態,當下呼叫到哪個函式,狀態機的狀態就是該函式對應的狀態,也可以說狀態其實藏在函式調用棧裡。
pub struct O分詞器 {
字流: VecDeque<char>,
}
impl O分詞器 {
pub fn new(源碼: String) -> Self {
O分詞器 {
字流: 源碼.chars().collect(),
}
}
fn 起點態(&mut self) -> Option<O詞> {
let 字 = self.字流.pop_front()?;
match 字 {
'+' => Some(O詞::運算子(O運算子::加)),
'-' => Some(O詞::運算子(O運算子::減)),
'*' => Some(O詞::運算子(O運算子::乘)),
'/' => Some(O詞::運算子(O運算子::除)),
'=' => Some(O詞::等),
'(' => Some(O詞::左括號),
')' => Some(O詞::右括號),
'・' => Some(O詞::音界),
'\n' => Some(O詞::換行),
'元' => self.元態(),
'1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '0' => {
self.數字態(字.to_string())
}
_ => self.變數態(字.to_string()),
}
}
fn 元態(&mut self) -> Option<O詞> {
let 字 = self.字流.front()?;
match 字類(字) {
O字類::元 | O字類::數字 | O字類::其他 => {
self.變數態("元".to_string())
}
_ => Some(O詞::元),
}
}
fn 數字態(&mut self, mut 前綴: String) -> Option<O詞> {
let 字 = self.字流.front()?;
match 字類(字) {
O字類::數字 => {
前綴.push(self.字流.pop_front()?);
self.數字態(前綴)
}
O字類::其他 => {
前綴.push(self.字流.pop_front()?);
self.變數態(前綴)
}
_ => {
let 數 = crate::全形處理::數字::字串轉整數(&前綴);
Some(O詞::數字(數))
}
}
}
fn 變數態(&mut self, mut 前綴: String) -> Option<O詞> {
let 字 = self.字流.front()?;
match 字類(字) {
O字類::元 | O字類::數字 | O字類::其他 => {
前綴.push(self.字流.pop_front()?);
self.變數態(前綴)
}
_ => Some(O詞::變數(前綴)),
}
}
pub fn 分詞(mut self) -> Vec<O詞> {
let mut 詞列: Vec<O詞> = Vec::new();
while self.字流.front().is_some() {
match self.起點態() {
Some(詞) => {
詞列.push(詞);
}
None => {
panic!("分詞錯誤");
}
}
}
詞列
}
}