Skip to content

Commit

Permalink
零・一版分詞實作
Browse files Browse the repository at this point in the history
  • Loading branch information
MROS committed Sep 25, 2024
1 parent 5b6f9db commit 4356d90
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 38 deletions.
Binary file modified book/image/零・一版分詞狀態機.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
139 changes: 101 additions & 38 deletions book/零.一版/分詞.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@

但無妨,早已有成熟的演算法能應對這類複雜狀況,在零・一版的簡單狀況,倒也不必構思出通用算法才能分詞,只要仔細分析所有狀況就可以了。

為了方便後續表達,先令 x 代表除了零字詞以及[0-9]的所有字元集合
為了方便後續表達,先令 x 代表除了單字詞、[0-9]之外,所有的字元集合

當目前讀取到的字串是...

Expand All @@ -78,17 +78,15 @@
- x
- 必是變數

上述分析以狀態機可表達為下圖
下圖把上述分析畫成了狀態機,但該圖並沒有畫出所有單字詞,僅以加減為例,其餘單字詞請道友自行想像。

![零・一版分詞狀態機](../image/零・一版分詞狀態機.png)

其中每個狀態向外的虛線表示,若下個字元匹配不到向外的實線,就走回原點,並以當下狀態分詞。

該圖並沒有畫出所有單字詞,僅以加減為例,其餘單字詞請道友自行想像。
分詞器會不斷接收到字元,分詞器根據接收的字元維護自身狀態。初始狀態是「起點」,每接收到一個字元,就嘗試匹配實線,若無法匹配,就精油虛線回到原點,並且根據當下狀態分詞。

畫出狀態機之後,以法咒(程式語言)依樣畫葫蘆實作,就是件很容易的事了。

貧道採用 Rust 法咒來撰寫零版編譯器。
貧道採用 Rust 法咒來撰寫零版編譯器,以下展示實作

### 類型定義

Expand Down Expand Up @@ -117,55 +115,120 @@ pub enum O詞 {
數字(i64),
變數(String),
}
```

// TODO: 要實作的分詞術
pub fn 分詞(源碼: String) -> Vec<O詞> {
unimplemented!();
接下來,就是把上圖的狀態機刻出來了,觀察狀態轉移的出邊,字符能分為四類,為此貧道寫了以下輔助函數備用:

```rust
enum O字類 {
特殊符號,
數字,
元,
其他, // 也就是 x
}

fn 字類(字: &char) -> O字類 {
match 字 {
'+' | '-' | '*' | '/' | '=' | '(' | ')' | '・' | '\n' => {
O字類::特殊符號
}
'元' => O字類::元,
'1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '0' => {
O字類::數字
}
_ => O字類::其他,
}
}
```

### 處理單字詞
模擬狀態機最直觀的寫法,定義狀態轉移表,根據接收到的字元,以及建好的表進行狀態轉移。

見招拆招,很容易。
貧道在此用了稍微不同的寫法,將每個狀態都以一個函式來表達(除了單字詞的情況太簡單,跟起點態的函數寫在一起),函式根據當前字元呼叫另個狀態函式。

這種寫法不用額外宣告一個變數當狀態,當下呼叫到哪個函式,狀態機的狀態就是該函式對應的那個狀態,也可以說狀態其實藏在函式調用棧裡。

```rust
pub fn 分詞(源碼: String) -> Vec<O詞> {
let mut 詞列: Vec<O詞> = Vec::new();
forin 源碼.chars() {
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 字 {
'+' => {
詞列.push(O詞::運算子(O運算子::加));
}
'-' => {
詞列.push(O詞::運算子(O運算子::減));
}
'*' => {
詞列.push(O詞::運算子(O運算子::乘));
}
'/' => {
詞列.push(O詞::運算子(O運算子::除));
'+' => 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())
}
'=' => {
詞列.push(O詞::等);
_ => self.變數態(字.to_string()),
}
}
fn 元態(&mut self) -> Option<O詞> {
let= self.字流.front()?;
match 字類(字) {
O字類::| O字類::數字 | O字類::其他 => {
self.變數態("".to_string())
}
'(' => {
詞列.push(O詞::左括號);
_ => Some(O詞::元),
}
}
fn 數字態(&mut self, mut 前綴: String) -> Option<O詞> {
let= self.字流.front()?;
match 字類(字) {
O字類::數字 => {
前綴.push(self.字流.pop_front()?);
self.數字態(前綴)
}
')' => {
詞列.push(O詞::右括號);
O字類::其他 => {
前綴.push(self.字流.pop_front()?);
self.變數態(前綴)
}
'元' => {
詞列.push(O詞::元);
_ => {
let= crate::全形處理::數字::字串轉整數(&前綴);
Some(O詞::數字(數))
}
'・' => {
詞列.push(O詞::音界);
}
}
fn 變數態(&mut self, mut 前綴: String) -> Option<O詞> {
let= self.字流.front()?;
match 字類(字) {
O字類::| O字類::數字 | O字類::其他 => {
前綴.push(self.字流.pop_front()?);
self.變數態(前綴)
}
_ => {
// TODO: 處理多字詞
_ => 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!("分詞錯誤");
}
}
}
詞列
}
詞列
}
```

Expand Down

0 comments on commit 4356d90

Please sign in to comment.