Skip to content
音界
元・甲=1
乙+1

這個範例的語法完全正確,但並沒有先宣告再使用,因此仍是非法的程式。

由此可見,一份源碼能被剖析成語法樹,吾人仍需對它進行更多檢查,以確認它是否任何意義不明之處。從「符合語法的程式」中過濾掉「不能編譯執行的程式」,這就是語義分析做的事情。

語義分析

有沒有可能設計一種上下文無關語法,得以強迫在算式中用到的變數全都是已經宣告的呢?這恐怕辦不到,上下文無關語法天生就記憶不了上下文。音界咒 = 句 | 句・音界咒一旦分離成多個之後,句與句之間就再無關聯,無法互相影響。

或許更強的語法系統能夠做到這點,但以符號檢查來說,在語法樹遍歷一趟就能完成,大可不必大費周章,非得要設計出語法。

讓語法定義完成它擅長的任務就行,剩下的交由語義分析來做。畢竟語法規則寫起來也並不容易是吧!

符號檢查

符號檢查很容易,遍歷語法樹的過程中,讀到變數宣告式時,將變數名稱加入一集合,後續任何算式中使用到變數時,檢查該變數名是否存在於集合中即可。

這種檢查也可以在遞迴下降法的剖析函式中順手做完,但貧道就先讓剖析過程純粹一些吧!

實作

rust
pub fn 檢查語法樹(語法樹: O語法樹) -> bool {
    let mut 通過 = true;

    let mut 變數集 = HashSet::<String>::new();

    forin 語法樹.句 {
        通過 = match 句 {
            O句::變數宣告(宣告) => {
                let 通過 = 檢查算式(&變數集, &宣告.算式);
                變數集.insert(宣告.變數名.clone());
                通過
            }
            O句::算式(算式) => 檢查算式(&變數集, &算式),
        } && 通過 // 「通過」寫在 && 後面,避免短路
    }

    通過
}


fn 檢查算式(變數集: &HashSet<String>, 算式: &O算式) -> bool {
    match 算式 {
        O算式::變數(變數名) => {
            if 變數集.contains(變數名) {
                true
            } else {
                println!("{} 未宣告", 變數名);
                false
            }
        }
        O算式::數字(_) => true,
        O算式::二元運算(二元運算) => {
            let 左通過 = 檢查算式(變數集, 二元運算..as_ref());
            let 右通過 = 檢查算式(變數集, 二元運算..as_ref());
            左通過 && 右通過
        }
    }
}

符號重定義

音界咒允許變數重新宣告,類似於 Rust,後面的宣告覆蓋前面宣告。

音界
元・甲=1

// 此處甲都是1

元・甲=2

// 此處甲都是2