Skip to content

最後只剩下若語句還無法編譯了!在討論若語句之前,先來看看零.二版加入的運算子會如何編譯。

比較運算(及餘運算)

精五僅提供 slt (及其立即數版本 slit),也就是小於真言,其餘運算都是透過調整暫存器順序與組合 xor 等等運算來完成的,下方源碼是其中一種作法。

rust
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 通常會被組譯成 jaljal 在跳躍的同時還能將當下咒指針存入指定暫存器,以待未來返回原執行位置。

有時候吾人僅想要跳躍,但不在意當下咒指針,那可以直接用 j 偽真言。

assembly
j 立即數

會被組譯為

jal x0, 立即數

x0 是一個永遠為 0 的暫存器,咒指針就像丟垃圾一樣被丟到 x0 這個垃圾桶去。

在組合語言中,其實不用知道這些細節,決策真言可以直接搭配標籤來使用,組譯器會自動算好立即數填上去:

assembly
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



區塊群結尾標籤:
...
繼續做事
...

由於或若不然區塊僅有其一會執行,在區塊結尾都要跳躍到區塊群結尾標籤,以避免執行到其他區塊。

實作

標籤在同一個真言檔裡不可同名,需要謹慎管理標籤:

rust
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<()> {
    forin 區塊 {
        self.生成句(句, &mut 符號表)?;
    }
    Ok(())
}

完整程式碼可見音界咒源碼