|
| 1 | +是什麼構成一篇漢語文章的呢?從小到大列舉,最小單位是「字」(通常狀況不會再去拆部首),「字」組成「詞」,「詞」再組成「句」,「句」組成「段落」,「段落」組成「文章」。 |
| 2 | + |
| 3 | +計算機要處理漢語時,第一步通常就是「分詞」,例如說 `嫉妒使我面目全非` ,可以拆解成「嫉妒」、「使」、「我」、「面目」、「全」、「非」。也許有人會把「面目全非」直接分一個詞,也不能說錯,自然語言本就沒必要只能唯一拆解。 |
| 4 | + |
| 5 | +法咒(程式語言)跟自然語言仍是有些共通之處,同樣能拆解成不同層級。 |
| 6 | + |
| 7 | +想像編譯器讀取音界咒文件時,它看見的是一個又一個的字元,而分詞器做的事情就很接近上述的漢語分詞器。但法咒(程式語言)不能有岐義,因此需要藉助特殊符號,如空白鍵或音界號來確定詞與詞之間的邊界。 |
| 8 | + |
| 9 | +來看個範例: |
| 10 | + |
| 11 | +```音界 |
| 12 | +元.人數=(11+3)*4 |
| 13 | +人數+1 |
| 14 | +``` |
| 15 | + |
| 16 | +第一行 `元.人數=(1+3)*4` 依序是分解為 |
| 17 | + |
| 18 | +1. `元`,關鍵字「元」 |
| 19 | +2. `.`,音界號 |
| 20 | +3. `人數`,這是個變數 |
| 21 | +4. `=`,等號 |
| 22 | +5. `(`,左括號 |
| 23 | +6. `11`,數字 |
| 24 | +7. `+`,運算子「加」 |
| 25 | +8. `3`,數字 |
| 26 | +9. `)`,右括號 |
| 27 | +10. `*`,運算子「乘」 |
| 28 | +11. `4`,數字 |
| 29 | +12. `\n`,換行 |
| 30 | + |
| 31 | +而第二行 `人數+1` 依序是 |
| 32 | + |
| 33 | +1. `人數`,這是個變數 |
| 34 | +2. `+`,運算子「加」 |
| 35 | +3. `1`,數字 |
| 36 | +4. `\n`,換行 |
| 37 | + |
| 38 | +## 定義 |
| 39 | + |
| 40 | +下表羅列了零・一版音界咒的所有詞: |
| 41 | +| 詞 | 種類 | 細分含義 | |
| 42 | +| ----------- | --- | ---- | |
| 43 | +| 元 | 關鍵字 | | |
| 44 | +| ( | 左括號 | | |
| 45 | +| ) | 右括號 | | |
| 46 | +| + | 運算子 | 加 | |
| 47 | +| − | 運算子 | 減 | |
| 48 | +| * | 運算子 | 乘 | |
| 49 | +| / | 運算子 | 除 | |
| 50 | +| = | 等號 | | |
| 51 | +| ・ | 音界號 | | |
| 52 | +| \n | 換行 | | |
| 53 | +| [0-9]+ | 數字 | | |
| 54 | +| 除以上詞之外的所有字串 | 變數 | | |
| 55 | + |
| 56 | +前幾項全是單字詞,要分出它們是再簡單不過,但最後兩種詞「數字」、「變數」就可能是多個字組成的了。 |
| 57 | + |
| 58 | +`[0-9]+` 是正規表達式,其意思是,字串由一到多個0123456789組成。 |
| 59 | + |
| 60 | +## 實作 |
| 61 | + |
| 62 | +上一節中變數的詞法定義並不清楚,例如,變數不可以是「元」,但能不能是「元氣」呢?變數能不能包含數字,像是「2號機」?若允許這樣的寬鬆定義,分詞會較為困難,當讀取到「元」時,並無法確定現在正在讀取「元」關鍵字,同時也可能只讀到「元氣」變數的開頭而已。同理,當讀到[0-9]時,無法判定正在讀取數字,還是某個變數的開頭。 |
| 63 | + |
| 64 | +但無妨,早已有成熟的演算法能應對這類複雜狀況,在零・一版的簡單狀況,倒也不必構思出通用算法才能分詞,只要仔細分析所有狀況就可以了。 |
| 65 | + |
| 66 | +為了方便後續表達,先令 x 代表除了零字詞以及[0-9]的所有字元集合。 |
| 67 | + |
| 68 | +當目前讀取到的字串是... |
| 69 | + |
| 70 | +- 除了元之外的單字詞,亦即()+−*/=・ |
| 71 | + - 讀取到一個字即可確定為詞 |
| 72 | +- 元 |
| 73 | + - 可能是變數的前綴,當下一個字元屬於 x 或 [0-9] ,即確定該詞為變數 |
| 74 | + - 當下個字元不是上述狀況時,是元關鍵字 |
| 75 | +- [0-9]+ |
| 76 | + - 可能是變數的前綴,當下一個字元屬於 x ,即確定該詞為變數 |
| 77 | + - 當下個字元不是上述狀況時,是數字 |
| 78 | +- x |
| 79 | + - 必是變數 |
| 80 | + |
| 81 | +上述分析以狀態機可表達為下圖 |
| 82 | + |
| 83 | + |
| 84 | + |
| 85 | +其中每個狀態向外的虛線表示,若下個字元匹配不到向外的實線,就走回原點,並以當下狀態分詞。 |
| 86 | + |
| 87 | +該圖並沒有畫出所有單字詞,僅以加減為例,其餘單字詞請道友自行想像。 |
| 88 | + |
| 89 | +畫出狀態機之後,以法咒(程式語言)依樣畫葫蘆實作,就是件很容易的事了。 |
| 90 | + |
| 91 | +貧道採用 Rust 法咒來撰寫零版編譯器。 |
| 92 | + |
| 93 | +### 類型定義 |
| 94 | + |
| 95 | +首先,寫出合適的類型來表達詞的種類: |
| 96 | + |
| 97 | +```rust |
| 98 | +// Rust 慣以駝峰式命名類型 |
| 99 | +// 漢語無大小寫,本作慣例以全形英文字母O來當類型的開頭 |
| 100 | +// Rust 管制識別符的字元組成,不允許 ◉、⦿、☯︎ 等等萬國碼,故採用常見的全形O來代替。 |
| 101 | +#[derive(Debug)] |
| 102 | +enum O運算子 { |
| 103 | + 加, |
| 104 | + 減, |
| 105 | + 乘, |
| 106 | + 除, |
| 107 | +} |
| 108 | + |
| 109 | +#[derive(Debug)] |
| 110 | +pub enum O詞 { |
| 111 | + 元, |
| 112 | + 左括號, |
| 113 | + 右括號, |
| 114 | + 運算子(O運算子), |
| 115 | + 等, |
| 116 | + 音界, |
| 117 | + 數字(i64), |
| 118 | + 變數(String), |
| 119 | +} |
| 120 | + |
| 121 | +// TODO: 要實作的分詞術 |
| 122 | +pub fn 分詞(源碼: String) -> Vec<O詞> { |
| 123 | + unimplemented!(); |
| 124 | +} |
| 125 | +``` |
| 126 | + |
| 127 | +### 處理單字詞 |
| 128 | + |
| 129 | +見招拆招,很容易。 |
| 130 | + |
| 131 | +```rust |
| 132 | +pub fn 分詞(源碼: String) -> Vec<O詞> { |
| 133 | + let mut 詞列: Vec<O詞> = Vec::new(); |
| 134 | + for 字 in 源碼.chars() { |
| 135 | + match 字 { |
| 136 | + '+' => { |
| 137 | + 詞列.push(O詞::運算子(O運算子::加)); |
| 138 | + } |
| 139 | + '-' => { |
| 140 | + 詞列.push(O詞::運算子(O運算子::減)); |
| 141 | + } |
| 142 | + '*' => { |
| 143 | + 詞列.push(O詞::運算子(O運算子::乘)); |
| 144 | + } |
| 145 | + '/' => { |
| 146 | + 詞列.push(O詞::運算子(O運算子::除)); |
| 147 | + } |
| 148 | + '=' => { |
| 149 | + 詞列.push(O詞::等); |
| 150 | + } |
| 151 | + '(' => { |
| 152 | + 詞列.push(O詞::左括號); |
| 153 | + } |
| 154 | + ')' => { |
| 155 | + 詞列.push(O詞::右括號); |
| 156 | + } |
| 157 | + '元' => { |
| 158 | + 詞列.push(O詞::元); |
| 159 | + } |
| 160 | + '・' => { |
| 161 | + 詞列.push(O詞::音界); |
| 162 | + } |
| 163 | + _ => { |
| 164 | + // TODO: 處理多字詞 |
| 165 | + } |
| 166 | + } |
| 167 | + } |
| 168 | + 詞列 |
| 169 | +} |
| 170 | +``` |
| 171 | + |
| 172 | +### 處理多字詞 |
0 commit comments