|
57 | 57 |
|
58 | 58 | `[0-9]+` 是正規表達式,其意思是,字串由一到多個0123456789組成。 |
59 | 59 |
|
60 | | -## 實作 |
| 60 | +## 狀態機 |
61 | 61 |
|
62 | 62 | 上一節中變數的詞法定義並不清楚,例如,變數不可以是「元」,但能不能是「元氣」呢?變數能不能包含數字,像是「2號機」?若允許這樣的寬鬆定義,分詞會較為困難,當讀取到「元」時,並無法確定現在正在讀取「元」關鍵字,同時也可能只讀到「元氣」變數的開頭而已。同理,當讀到[0-9]時,無法判定正在讀取數字,還是某個變數的開頭。 |
63 | 63 |
|
64 | 64 | 但無妨,早已有成熟的演算法能應對這類複雜狀況,在零・一版的簡單狀況,倒也不必構思出通用算法才能分詞,只要仔細分析所有狀況就可以了。 |
65 | 65 |
|
66 | | -為了方便後續表達,先令 x 代表除了零字詞以及[0-9]的所有字元集合。 |
| 66 | +為了方便後續表達,先令 x 代表除了單字詞、[0-9]之外,所有的字元集合。 |
67 | 67 |
|
68 | 68 | 當目前讀取到的字串是... |
69 | 69 |
|
|
78 | 78 | - x |
79 | 79 | - 必是變數 |
80 | 80 |
|
81 | | -上述分析以狀態機可表達為下圖 |
| 81 | +下圖把上述分析畫成了狀態機,但該圖並沒有畫出所有單字詞,僅以加減為例,其餘單字詞請道友自行想像。 |
82 | 82 |
|
83 | 83 |  |
84 | 84 |
|
85 | | -其中每個狀態向外的虛線表示,若下個字元匹配不到向外的實線,就走回原點,並以當下狀態分詞。 |
86 | | - |
87 | | -該圖並沒有畫出所有單字詞,僅以加減為例,其餘單字詞請道友自行想像。 |
| 85 | +分詞器會不斷接收到字元,分詞器根據接收的字元維護自身狀態。初始狀態是「起點」,每接收到一個字元,就嘗試匹配實線,若無法匹配,就精油虛線回到原點,並且根據當下狀態分詞。 |
88 | 86 |
|
89 | 87 | 畫出狀態機之後,以法咒(程式語言)依樣畫葫蘆實作,就是件很容易的事了。 |
90 | 88 |
|
91 | | -貧道採用 Rust 法咒來撰寫零版編譯器。 |
| 89 | +貧道採用 Rust 法咒來撰寫零版編譯器,以下展示其分詞器實作。 |
| 90 | + |
| 91 | +## 實作 |
92 | 92 |
|
93 | 93 | ### 類型定義 |
94 | 94 |
|
@@ -117,56 +117,119 @@ pub enum O詞 { |
117 | 117 | 數字(i64), |
118 | 118 | 變數(String), |
119 | 119 | } |
| 120 | +``` |
120 | 121 |
|
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 | + } |
124 | 143 | } |
125 | 144 | ``` |
126 | 145 |
|
127 | | -### 處理單字詞 |
| 146 | +想模擬狀態機,最簡單的方法是:定義一個狀態變數,建好狀態轉移表。狀態機啟動時將狀態變數設為初始值,接下來根據接收到的字元,以及建好的表進行狀態轉移直到字元流結束。 |
128 | 147 |
|
129 | | -見招拆招,很容易。 |
| 148 | +貧道在此用了稍微不同的寫法,將每個狀態都以一個函式來表達(除了單字詞的情況太簡單,跟起點態的函數寫在一起),函式根據當前字元呼叫下個狀態函式。 |
| 149 | + |
| 150 | +這種寫法不用額外宣告一個變數當狀態,當下呼叫到哪個函式,狀態機的狀態就是該函式對應的狀態,也可以說狀態其實藏在函式調用棧裡。 |
130 | 151 |
|
131 | 152 | ```rust |
132 | | -pub fn 分詞(源碼: String) -> Vec<O詞> { |
133 | | - let mut 詞列: Vec<O詞> = Vec::new(); |
134 | | - for 字 in 源碼.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()?; |
135 | 166 | 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()) |
147 | 179 | } |
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()) |
150 | 188 | } |
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.數字態(前綴) |
153 | 198 | } |
154 | | - ')' => { |
155 | | - 詞列.push(O詞::右括號); |
| 199 | + O字類::其他 => { |
| 200 | + 前綴.push(self.字流.pop_front()?); |
| 201 | + self.變數態(前綴) |
156 | 202 | } |
157 | | - '元' => { |
158 | | - 詞列.push(O詞::元); |
| 203 | + _ => { |
| 204 | + let 數 = crate::全形處理::數字::字串轉整數(&前綴); |
| 205 | + Some(O詞::數字(數)) |
159 | 206 | } |
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.變數態(前綴) |
162 | 215 | } |
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 | + } |
165 | 230 | } |
166 | 231 | } |
| 232 | + 詞列 |
167 | 233 | } |
168 | | - 詞列 |
169 | 234 | } |
170 | 235 | ``` |
171 | | - |
172 | | -### 處理多字詞 |
|
0 commit comments