Skip to content

Commit 5b6f9db

Browse files
committed
零・一版分詞算法
1 parent 2192190 commit 5b6f9db

6 files changed

Lines changed: 180 additions & 4 deletions

book/.vitepress/config.mts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,16 @@ export default defineConfig({
3636
items: [
3737
{
3838
text: "音界咒零.一版設計與定義",
39-
link: "/音界咒零.一版設計與定義",
39+
link: "/零.一版/音界咒零.一版設計與定義",
4040
},
4141
{ text: "全形字體選擇", link: "/全形字體選擇" },
4242
{
43-
text: "零.一版編譯目標:精五門(RISC-V)真言極簡子集",
44-
link: "/零.一版編譯目標:精五門(RISC-V)真言極簡子集",
43+
text: "編譯目標:精五門(RISC-V)真言極簡子集",
44+
link: "/零.一版/編譯目標:精五門(RISC-V)真言極簡子集",
45+
},
46+
{
47+
text: "分詞",
48+
link: "/零.一版/分詞",
4549
},
4650
],
4751
},
44.3 KB
Loading

book/序・去往新世界.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44

55
將現代軟體當做一個世界,從學會寫程式的第一天開始,絕大多數的人就永遠也脫離不了它。本作企圖指引人們如何離開原世界(下稱塵界),再造一個新世界(洞天、仙界、天界)。
66

7-
在這個新世界,不必為了理解一段彆扭的程式碼去考古三十年前的郵件往返,不必再與費解的歷史遺留問題做鬥爭,人不該活在前人定義好的世界裡反覆修補。來吧!道友,去創造一個新世界,即使要設計世界的法則會讓你絞盡腦汁;即使維持世界的運轉會令人精疲力竭...
7+
在這個新世界,不必為了理解一段彆扭的程式碼去考古三十年前的郵件往返,不必再與費解的歷史遺留問題做鬥爭,人不該活在前人定義好的世界裡反覆修補。來吧!道友,去創造一個新世界,即使設計世界的法則讓你絞盡腦汁;即使維持世界的運轉令你精疲力竭...

book/零.一版/分詞.md

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
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. ``,數字
26+
9. ``,右括號
27+
10. ``,運算子「乘」
28+
11. ``,數字
29+
12. `\n`,換行
30+
31+
而第二行 `人數+1` 依序是
32+
33+
1. `人數`,這是個變數
34+
2. ``,運算子「加」
35+
3. ``,數字
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+
![零・一版分詞狀態機](../image/零・一版分詞狀態機.png)
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+
forin 源碼.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

Comments
 (0)