Skip to content

Commit af45f6f

Browse files
committed
零・一版分詞實作
1 parent 5b6f9db commit af45f6f

2 files changed

Lines changed: 104 additions & 41 deletions

File tree

788 Bytes
Loading

book/零.一版/分詞.md

Lines changed: 104 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,13 @@
5757

5858
`[0-9]+` 是正規表達式,其意思是,字串由一到多個0123456789組成。
5959

60-
## 實作
60+
## 狀態機
6161

6262
上一節中變數的詞法定義並不清楚,例如,變數不可以是「元」,但能不能是「元氣」呢?變數能不能包含數字,像是「2號機」?若允許這樣的寬鬆定義,分詞會較為困難,當讀取到「元」時,並無法確定現在正在讀取「元」關鍵字,同時也可能只讀到「元氣」變數的開頭而已。同理,當讀到[0-9]時,無法判定正在讀取數字,還是某個變數的開頭。
6363

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

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

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

@@ -78,17 +78,17 @@
7878
- x
7979
- 必是變數
8080

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

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

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

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

91-
貧道採用 Rust 法咒來撰寫零版編譯器。
89+
貧道採用 Rust 法咒來撰寫零版編譯器,以下展示其分詞器實作。
90+
91+
## 實作
9292

9393
### 類型定義
9494

@@ -117,56 +117,119 @@ pub enum O詞 {
117117
數字(i64),
118118
變數(String),
119119
}
120+
```
120121

121-
// TODO: 要實作的分詞術
122-
pub fn 分詞(源碼: String) -> Vec<O詞> {
123-
unimplemented!();
122+
接下來,就是把上圖的狀態機刻出來了,觀察狀態轉移的出邊,字符能分為四類,為此貧道寫了以下輔助函數備用:
123+
124+
```rust
125+
enum O字類 {
126+
特殊符號,
127+
數字,
128+
元,
129+
其他, // 也就是 x
130+
}
131+
132+
fn 字類(字: &char) -> O字類 {
133+
match 字 {
134+
'+' | '-' | '*' | '/' | '=' | '(' | ')' | '・' | '\n' => {
135+
O字類::特殊符號
136+
}
137+
'元' => O字類::元,
138+
'1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '0' => {
139+
O字類::數字
140+
}
141+
_ => O字類::其他,
142+
}
124143
}
125144
```
126145

127-
### 處理單字詞
146+
想模擬狀態機,最簡單的方法是:定義一個狀態變數,建好狀態轉移表。狀態機啟動時將狀態變數設為初始值,接下來根據接收到的字元,以及建好的表進行狀態轉移直到字元流結束。
128147

129-
見招拆招,很容易。
148+
貧道在此用了稍微不同的寫法,將每個狀態都以一個函式來表達(除了單字詞的情況太簡單,跟起點態的函數寫在一起),函式根據當前字元呼叫下個狀態函式。
149+
150+
這種寫法不用額外宣告一個變數當狀態,當下呼叫到哪個函式,狀態機的狀態就是該函式對應的狀態,也可以說狀態其實藏在函式調用棧裡。
130151

131152
```rust
132-
pub fn 分詞(源碼: String) -> Vec<O詞> {
133-
let mut 詞列: Vec<O詞> = Vec::new();
134-
forin 源碼.chars() {
153+
pub struct O分詞器 {
154+
字流: VecDeque<char>,
155+
}
156+
157+
impl O分詞器 {
158+
pub fn new(源碼: String) -> Self {
159+
O分詞器 {
160+
字流: 源碼.chars().collect(),
161+
}
162+
}
163+
164+
fn 起點態(&mut self) -> Option<O詞> {
165+
let= self.字流.pop_front()?;
135166
match 字 {
136-
'+' => {
137-
詞列.push(O詞::運算子(O運算子::加));
138-
}
139-
'-' => {
140-
詞列.push(O詞::運算子(O運算子::減));
141-
}
142-
'*' => {
143-
詞列.push(O詞::運算子(O運算子::乘));
144-
}
145-
'/' => {
146-
詞列.push(O詞::運算子(O運算子::除));
167+
'+' => Some(O詞::運算子(O運算子::加)),
168+
'-' => Some(O詞::運算子(O運算子::減)),
169+
'*' => Some(O詞::運算子(O運算子::乘)),
170+
'/' => Some(O詞::運算子(O運算子::除)),
171+
'=' => Some(O詞::等),
172+
'(' => Some(O詞::左括號),
173+
')' => Some(O詞::右括號),
174+
'・' => Some(O詞::音界),
175+
'\n' => Some(O詞::換行),
176+
'元' => self.元態(),
177+
'1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '0' => {
178+
self.數字態(字.to_string())
147179
}
148-
'=' => {
149-
詞列.push(O詞::等);
180+
_ => self.變數態(字.to_string()),
181+
}
182+
}
183+
fn 元態(&mut self) -> Option<O詞> {
184+
let= self.字流.front()?;
185+
match 字類(字) {
186+
O字類::| O字類::數字 | O字類::其他 => {
187+
self.變數態("".to_string())
150188
}
151-
'(' => {
152-
詞列.push(O詞::左括號);
189+
_ => Some(O詞::元),
190+
}
191+
}
192+
fn 數字態(&mut self, mut 前綴: String) -> Option<O詞> {
193+
let= self.字流.front()?;
194+
match 字類(字) {
195+
O字類::數字 => {
196+
前綴.push(self.字流.pop_front()?);
197+
self.數字態(前綴)
153198
}
154-
')' => {
155-
詞列.push(O詞::右括號);
199+
O字類::其他 => {
200+
前綴.push(self.字流.pop_front()?);
201+
self.變數態(前綴)
156202
}
157-
'元' => {
158-
詞列.push(O詞::元);
203+
_ => {
204+
let= crate::全形處理::數字::字串轉整數(&前綴);
205+
Some(O詞::數字(數))
159206
}
160-
'・' => {
161-
詞列.push(O詞::音界);
207+
}
208+
}
209+
fn 變數態(&mut self, mut 前綴: String) -> Option<O詞> {
210+
let= self.字流.front()?;
211+
match 字類(字) {
212+
O字類::| O字類::數字 | O字類::其他 => {
213+
前綴.push(self.字流.pop_front()?);
214+
self.變數態(前綴)
162215
}
163-
_ => {
164-
// TODO: 處理多字詞
216+
_ => Some(O詞::變數(前綴)),
217+
}
218+
}
219+
220+
pub fn 分詞(mut self) -> Vec<O詞> {
221+
let mut 詞列: Vec<O詞> = Vec::new();
222+
while self.字流.front().is_some() {
223+
match self.起點態() {
224+
Some(詞) => {
225+
詞列.push(詞);
226+
}
227+
None => {
228+
panic!("分詞錯誤");
229+
}
165230
}
166231
}
232+
詞列
167233
}
168-
詞列
169234
}
170235
```
171-
172-
### 處理多字詞

0 commit comments

Comments
 (0)