最後只剩下若語句還無法編譯了!在討論若語句之前,先來看看零.二版加入的運算子會如何編譯。
比較運算(及餘運算)
精五僅提供 slt (及其立即數版本 slit),也就是小於真言,其餘運算都是透過調整暫存器順序與組合 xor 等等運算來完成的,下方源碼是其中一種作法。
match 運算子 {
// 加減乘除...
O運算子::餘 => {
// rem 指令是有號整數取餘
// 尚有 urem 指令,乃無號整數取餘,但音界咒並不支援
writeln!(真言檔, "\trem t0, t0, t1")?;
}
O運算子::等於 => {
writeln!(真言檔, "\txor t2, t0, t1")?; // t2 = t0 ^ t1
writeln!(真言檔, "\tseqz t0, t2")?; // t0 = (t2 == 0) ? 1 : 0
}
O運算子::異於 => {
writeln!(真言檔, "\txor t2, t0, t1")?; // t2 = t0 ^ t1
writeln!(真言檔, "\tsnez t0, t2")?; // t0 = (t2 != 0) ? 1 : 0
}
// 以下比較運算僅 slt 為精五真言
O運算子::小於 => {
writeln!(真言檔, "\tslt t0, t0, t1")?; // t0 = (t0 < t1)
}
O運算子::大於 => {
// 組譯為 slt t0, t1, t0
writeln!(真言檔, "\tsgt t0, t0, t1")?; // t0 = (t0 > t1)
}
O運算子::小於等於 => {
// 甲<=乙,即 !(甲>乙)
writeln!(真言檔, "\tsgt t0, t0, t1")?; // t0 = (t0 > t0)
writeln!(真言檔, "\txori t0, t0, 1")?; // t0 = t0 ^ 1
}
O運算子::大於等於 => {
// 甲>=乙,即 !(甲<乙)
writeln!(真言檔, "\tslt t0, t0, t1")?; // t0 = (t0 < t1)
writeln!(真言檔, "\txori t0, t0, 1")?; // t0 = t0 ^ 1
}
}
精五決策
精五的決策分為兩種,一是條件決策,一是無條件跳躍。
條件決策
精五提供了 beq
(相等則跳), bne
(相異則跳), bge
(大於等於則跳), blt
(小於則跳)等多個條件決策真言。貧道實在不曉得為何比較運算只提供了一個 slt
,其他比較都要組合兩個真言才能做,而決策就如此大方。
條件決策真言的用法與形式皆雷同,僅語義不同。由於音界咒尚不注重優化,所以接下來僅使用與介紹 beq
。
beq
接受三個參數
beq 暫存器甲, 暫存器乙, 立即數
當 暫存器甲
與 暫存器乙
相等時,將 立即數 * 2
加到咒指針(program counter)上,這個立即數是一個 12 位元的有號整數,所以總共可以移動正負 4KB 的距離,在一個術之內跳躍綽綽有餘了。
補充:每個精五真言都佔 4 個位元組,那為和立即數是乘 2 不是乘 4 ?因為精五還支援壓縮指令,指令可以被壓縮到只佔用 2 個位元組。
無條件跳躍
若需要更遠的跳躍,則需要 j
系列的決策真言,例如偽真言 call
會呼叫另一個術,而術與術之間的距離可能很遠,所以 call
通常會被組譯成 jal
,jal
在跳躍的同時還能將當下咒指針存入指定暫存器,以待未來返回原執行位置。
有時候吾人僅想要跳躍,但不在意當下咒指針,那可以直接用 j
偽真言。
j 立即數
會被組譯為
jal x0, 立即數
x0
是一個永遠為 0 的暫存器,咒指針就像丟垃圾一樣被丟到 x0
這個垃圾桶去。
在組合語言中,其實不用知道這些細節,決策真言可以直接搭配標籤來使用,組譯器會自動算好立即數填上去:
beq t0, x0, 標籤1
# ...
# t0 != 0 時該做的事
# ...
標籤1:
# ...
# 繼續做事
# ...
若語句真言生成
以上組語就類似於
若(算式)【
// 算式 != 0 時該做的事
】
// 繼續做事
的編譯結果。
若要支援或若
、不然
,就多加幾個標籤:
若(算式1)【
...
】或若(算式2)【
...
】不然【
...
】
繼續做事
可編譯為
# 計算算式1
beq t0, x0, 標籤1
...
...若區塊
...
j 標籤3
標籤1:
# 計算算式2
beq t0, x0, 標籤2
...
...或若區塊
...
j 標籤3
標籤2:
...
...不然區塊
...
j 標籤3
區塊群結尾標籤:
...
繼續做事
...
由於若
、或若
、不然
區塊僅有其一會執行,在區塊結尾都要跳躍到區塊群結尾標籤
,以避免執行到其他區塊。
實作
標籤在同一個真言檔裡不可同名,需要謹慎管理標籤:
fn 新分支標籤名(&mut self) -> String {
self.分支標籤計數 += 1;
format!("分支標籤——{}", self.分支標籤計數)
}
fn 上標籤(&mut self, 標籤名: &String) -> io::Result<()> {
writeln!(self.真言檔, "{}:", 標籤名)
}
fn t0為0則跳至標籤(&mut self, 標籤名: &String) -> io::Result<()> {
writeln!(self.真言檔, "\tbeq t0, x0, {}", 標籤名)
}
fn 新區塊群結尾標籤名(&mut self) -> String {
self.區塊群結尾標籤計數 += 1;
format!("區塊群結尾標籤——{}", self.區塊群結尾標籤計數)
}
fn 跳至標籤(&mut self, 標籤名: &String) -> io::Result<()> {
writeln!(self.真言檔, "j {}", 標籤名)
}
fn 生成若(&mut self, 若: &O若, 符號表: &O符號表) -> io::Result<()> {
// 若
self.計算(&若.條件, 符號表)?;
self.彈出()?;
let mut 分支標籤名 = self.新分支標籤名();
self.t0為0則跳至標籤(&分支標籤名)?;
// 區塊內的新增區域變數,在區塊外不應被擷取
// 故需複製符號表,以免原符號表被影響
self.生成區塊(&若.區塊, 符號表.clone())?;
if 若.或若列表.len() == 0 && 若.不然.is_none() {
// 僅有若,無或若、不然。
self.上標籤(&分支標籤名)?;
return Ok(());
}
let 區塊群結尾標籤名 = self.新區塊群結尾標籤名();
self.跳至標籤(&區塊群結尾標籤名)?;
// 或若
for 或若 in &若.或若列表 {
self.上標籤(&分支標籤名)?;
self.計算(&或若.條件, 符號表)?;
self.彈出()?;
分支標籤名 = self.新分支標籤名();
self.t0為0則跳至標籤(&分支標籤名)?;
self.生成區塊(&或若.區塊, 符號表.clone())?;
self.跳至標籤(&區塊群結尾標籤名)?;
}
// 不然
self.上標籤(&分支標籤名)?;
if let Some(不然) = &若.不然 {
self.生成區塊(&不然.區塊, 符號表.clone())?;
}
self.上標籤(&區塊群結尾標籤名)
}
fn 生成區塊(
&mut self, 區塊: &Vec<O句>, mut 符號表: O符號表
) -> io::Result<()> {
for 句 in 區塊 {
self.生成句(句, &mut 符號表)?;
}
Ok(())
}
完整程式碼可見音界咒源碼。