-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
180 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
是什麼構成一篇漢語文章的呢?從小到大列舉,最小單位是「字」(通常狀況不會再去拆部首),「字」組成「詞」,「詞」再組成「句」,「句」組成「段落」,「段落」組成「文章」。 | ||
|
||
計算機要處理漢語時,第一步通常就是「分詞」,例如說 `嫉妒使我面目全非` ,可以拆解成「嫉妒」、「使」、「我」、「面目」、「全」、「非」。也許有人會把「面目全非」直接分一個詞,也不能說錯,自然語言本就沒必要只能唯一拆解。 | ||
|
||
法咒(程式語言)跟自然語言仍是有些共通之處,同樣能拆解成不同層級。 | ||
|
||
想像編譯器讀取音界咒文件時,它看見的是一個又一個的字元,而分詞器做的事情就很接近上述的漢語分詞器。但法咒(程式語言)不能有岐義,因此需要藉助特殊符號,如空白鍵或音界號來確定詞與詞之間的邊界。 | ||
|
||
來看個範例: | ||
|
||
```音界 | ||
元.人數=(11+3)*4 | ||
人數+1 | ||
``` | ||
|
||
第一行 `元.人數=(1+3)*4` 依序是分解為 | ||
|
||
1. `元`,關鍵字「元」 | ||
2. `.`,音界號 | ||
3. `人數`,這是個變數 | ||
4. `=`,等號 | ||
5. `(`,左括號 | ||
6. `11`,數字 | ||
7. `+`,運算子「加」 | ||
8. `3`,數字 | ||
9. `)`,右括號 | ||
10. `*`,運算子「乘」 | ||
11. `4`,數字 | ||
12. `\n`,換行 | ||
|
||
而第二行 `人數+1` 依序是 | ||
|
||
1. `人數`,這是個變數 | ||
2. `+`,運算子「加」 | ||
3. `1`,數字 | ||
4. `\n`,換行 | ||
|
||
## 定義 | ||
|
||
下表羅列了零・一版音界咒的所有詞: | ||
| 詞 | 種類 | 細分含義 | | ||
| ----------- | --- | ---- | | ||
| 元 | 關鍵字 | | | ||
| ( | 左括號 | | | ||
| ) | 右括號 | | | ||
| + | 運算子 | 加 | | ||
| − | 運算子 | 減 | | ||
| * | 運算子 | 乘 | | ||
| / | 運算子 | 除 | | ||
| = | 等號 | | | ||
| ・ | 音界號 | | | ||
| \n | 換行 | | | ||
| [0-9]+ | 數字 | | | ||
| 除以上詞之外的所有字串 | 變數 | | | ||
|
||
前幾項全是單字詞,要分出它們是再簡單不過,但最後兩種詞「數字」、「變數」就可能是多個字組成的了。 | ||
|
||
`[0-9]+` 是正規表達式,其意思是,字串由一到多個0123456789組成。 | ||
|
||
## 實作 | ||
|
||
上一節中變數的詞法定義並不清楚,例如,變數不可以是「元」,但能不能是「元氣」呢?變數能不能包含數字,像是「2號機」?若允許這樣的寬鬆定義,分詞會較為困難,當讀取到「元」時,並無法確定現在正在讀取「元」關鍵字,同時也可能只讀到「元氣」變數的開頭而已。同理,當讀到[0-9]時,無法判定正在讀取數字,還是某個變數的開頭。 | ||
|
||
但無妨,早已有成熟的演算法能應對這類複雜狀況,在零・一版的簡單狀況,倒也不必構思出通用算法才能分詞,只要仔細分析所有狀況就可以了。 | ||
|
||
為了方便後續表達,先令 x 代表除了零字詞以及[0-9]的所有字元集合。 | ||
|
||
當目前讀取到的字串是... | ||
|
||
- 除了元之外的單字詞,亦即()+−*/=・ | ||
- 讀取到一個字即可確定為詞 | ||
- 元 | ||
- 可能是變數的前綴,當下一個字元屬於 x 或 [0-9] ,即確定該詞為變數 | ||
- 當下個字元不是上述狀況時,是元關鍵字 | ||
- [0-9]+ | ||
- 可能是變數的前綴,當下一個字元屬於 x ,即確定該詞為變數 | ||
- 當下個字元不是上述狀況時,是數字 | ||
- x | ||
- 必是變數 | ||
|
||
上述分析以狀態機可表達為下圖 | ||
|
||
![零・一版分詞狀態機](../image/零・一版分詞狀態機.png) | ||
|
||
其中每個狀態向外的虛線表示,若下個字元匹配不到向外的實線,就走回原點,並以當下狀態分詞。 | ||
|
||
該圖並沒有畫出所有單字詞,僅以加減為例,其餘單字詞請道友自行想像。 | ||
|
||
畫出狀態機之後,以法咒(程式語言)依樣畫葫蘆實作,就是件很容易的事了。 | ||
|
||
貧道採用 Rust 法咒來撰寫零版編譯器。 | ||
|
||
### 類型定義 | ||
|
||
首先,寫出合適的類型來表達詞的種類: | ||
|
||
```rust | ||
// Rust 慣以駝峰式命名類型 | ||
// 漢語無大小寫,本作慣例以全形英文字母O來當類型的開頭 | ||
// Rust 管制識別符的字元組成,不允許 ◉、⦿、☯︎ 等等萬國碼,故採用常見的全形O來代替。 | ||
#[derive(Debug)] | ||
enum O運算子 { | ||
加, | ||
減, | ||
乘, | ||
除, | ||
} | ||
|
||
#[derive(Debug)] | ||
pub enum O詞 { | ||
元, | ||
左括號, | ||
右括號, | ||
運算子(O運算子), | ||
等, | ||
音界, | ||
數字(i64), | ||
變數(String), | ||
} | ||
|
||
// TODO: 要實作的分詞術 | ||
pub fn 分詞(源碼: String) -> Vec<O詞> { | ||
unimplemented!(); | ||
} | ||
``` | ||
|
||
### 處理單字詞 | ||
|
||
見招拆招,很容易。 | ||
|
||
```rust | ||
pub fn 分詞(源碼: String) -> Vec<O詞> { | ||
let mut 詞列: Vec<O詞> = Vec::new(); | ||
for 字 in 源碼.chars() { | ||
match 字 { | ||
'+' => { | ||
詞列.push(O詞::運算子(O運算子::加)); | ||
} | ||
'-' => { | ||
詞列.push(O詞::運算子(O運算子::減)); | ||
} | ||
'*' => { | ||
詞列.push(O詞::運算子(O運算子::乘)); | ||
} | ||
'/' => { | ||
詞列.push(O詞::運算子(O運算子::除)); | ||
} | ||
'=' => { | ||
詞列.push(O詞::等); | ||
} | ||
'(' => { | ||
詞列.push(O詞::左括號); | ||
} | ||
')' => { | ||
詞列.push(O詞::右括號); | ||
} | ||
'元' => { | ||
詞列.push(O詞::元); | ||
} | ||
'・' => { | ||
詞列.push(O詞::音界); | ||
} | ||
_ => { | ||
// TODO: 處理多字詞 | ||
} | ||
} | ||
} | ||
詞列 | ||
} | ||
``` | ||
|
||
### 處理多字詞 |
File renamed without changes.
File renamed without changes.