File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -51,6 +51,10 @@ export default defineConfig({
5151 text : "剖析(語法分析)" ,
5252 link : "/零.一版/剖析(語法分析)" ,
5353 } ,
54+ {
55+ text : "符號檢查" ,
56+ link : "/零.一版/符號檢查" ,
57+ } ,
5458 ] ,
5559 } ,
5660 ] ,
Original file line number Diff line number Diff line change 1+ ``` 音界
2+ 元・甲=1
3+ 乙+1
4+ ```
5+
6+ 這個範例的語法完全正確,但` 乙 ` 並沒有先宣告再使用,因此仍是非法的程式。
7+
8+ 由此可見,一份源碼能被剖析成語法樹,吾人仍需對它進行更多檢查,以確認它是否任何意義不明之處。從「符合語法的程式」中過濾掉「不能編譯執行的程式」,這就是語義分析做的事情。
9+
10+ ## 語義分析
11+
12+ 有沒有可能設計一種上下文無關語法,得以強迫在` 算式 ` 中用到的變數全都是已經宣告的呢?這恐怕辦不到,上下文無關語法天生就記憶不了上下文。` 音界咒 = 句 | 句・音界咒 ` 一旦分離成多個` 句 ` 之後,句與句之間就再無關聯,無法互相影響。
13+
14+ 或許更強的語法系統能夠做到這點,但以符號檢查來說,在語法樹遍歷一趟就能完成,大可不必大費周章,非得要設計出語法。
15+
16+ 讓語法定義完成它擅長的任務就行,剩下的交由語義分析來做。畢竟語法規則寫起來也並不容易是吧!
17+
18+ ## 符號檢查
19+ 符號檢查很容易,遍歷語法樹的過程中,讀到` 變數宣告式 ` 時,將變數名稱加入一集合,後續任何` 算式 ` 中使用到變數時,檢查該變數名是否存在於集合中即可。
20+
21+ 這種檢查也可以在遞迴下降法的剖析函式中順手做完,但貧道就先讓剖析過程純粹一些吧!
22+
23+ ### 實作
24+
25+ ``` rust
26+ pub fn 檢查語法樹(語法樹: O語法樹) -> bool {
27+ let mut 通過 = true ;
28+
29+ let mut 變數集 = HashSet :: <String >:: new ();
30+
31+ for 句 in 語法樹. 句 {
32+ 通過 = match 句 {
33+ O句:: 變數宣告(宣告) => {
34+ let 通過 = 檢查算式(& 變數集, & 宣告. 算式);
35+ 變數集. insert (宣告. 變數名. clone ());
36+ 通過
37+ }
38+ O句:: 算式(算式) => 檢查算式(& 變數集, & 算式),
39+ } && 通過 // 「通過」寫在 && 後面,避免短路
40+ }
41+
42+ 通過
43+ }
44+
45+
46+ fn 檢查算式(變數集: & HashSet <String >, 算式: & O算式) -> bool {
47+ match 算式 {
48+ O算式:: 變數(變數名) => {
49+ if 變數集. contains (變數名) {
50+ true
51+ } else {
52+ println! (" {} 未宣告" , 變數名);
53+ false
54+ }
55+ }
56+ O算式:: 數字(_ ) => true ,
57+ O算式:: 二元運算(二元運算) => {
58+ let 左通過 = 檢查算式(變數集, 二元運算. 左. as_ref ());
59+ let 右通過 = 檢查算式(變數集, 二元運算. 右. as_ref ());
60+ 左通過 && 右通過
61+ }
62+ }
63+ }
64+
65+ ```
66+
67+ ## 符號重定義
68+
69+ 音界咒允許變數重新宣告,類似於 Rust,後面的宣告覆蓋前面宣告。
70+
71+ ``` 音界
72+ 元・甲=1
73+
74+ // 此處甲都是1
75+
76+ 元・甲=2
77+
78+ // 此處甲都是2
79+
80+ ```
You can’t perform that action at this time.
0 commit comments