元・甲=1
乙+1
這個範例的語法完全正確,但乙
並沒有先宣告再使用,因此仍是非法的程式。
由此可見,一份源碼能被剖析成語法樹,吾人仍需對它進行更多檢查,以確認它是否任何意義不明之處。從「符合語法的程式」中過濾掉「不能編譯執行的程式」,這就是語義分析做的事情。
有沒有可能設計一種上下文無關語法,得以強迫在算式
中用到的變數全都是已經宣告的呢?這恐怕辦不到,上下文無關語法天生就記憶不了上下文。音界咒 = 句 | 句・音界咒
一旦分離成多個句
之後,句與句之間就再無關聯,無法互相影響。
或許更強的語法系統能夠做到這點,但以符號檢查來說,在語法樹遍歷一趟就能完成,大可不必大費周章,非得要設計出語法。
讓語法定義完成它擅長的任務就行,剩下的交由語義分析來做。畢竟語法規則寫起來也並不容易是吧!
符號檢查很容易,遍歷語法樹的過程中,讀到變數宣告式
時,將變數名稱加入一集合,後續任何算式
中使用到變數時,檢查該變數名是否存在於集合中即可。
這種檢查也可以在遞迴下降法的剖析函式中順手做完,但貧道就先讓剖析過程純粹一些吧!
pub fn 檢查語法樹(語法樹: O語法樹) -> bool {
let mut 通過 = true;
let mut 變數集 = HashSet::<String>::new();
for 句 in 語法樹.句 {
通過 = 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