Skip to content

Commit

Permalink
精五真言生成(四)編譯若語句
Browse files Browse the repository at this point in the history
  • Loading branch information
MROS committed Oct 17, 2024
1 parent 9bc6d3f commit 4148a74
Show file tree
Hide file tree
Showing 4 changed files with 233 additions and 2 deletions.
4 changes: 4 additions & 0 deletions book/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ export default defineConfig({
text: "精五真言生成(三)實作:術的編譯",
link: "/零.二版/精五真言生成(三)實作:術的編譯.md",
},
{
text: "精五真言生成(四)編譯若語句",
link: "/零.二版/精五真言生成(四)編譯若語句.md",
},
],
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -282,4 +282,4 @@ fn 施術(真言檔: &mut File, 術: &O施術, 符號表: &O符號表) -> io
}
```

至此,術的編譯大致講解完畢,部分與零.一版重複的程式碼就先不貼上來了。若有興趣,可直接參考[音界咒源碼](https://github.com/MROS/yinjie-lang/blob/3c6389823284722338642e3de672ef48d8e8ac9e/%E9%9B%B6%E8%99%9F%E7%B7%A8%E8%AD%AF%E5%99%A8/src/%E7%9C%9F%E8%A8%80%E7%94%9F%E6%88%90/%E7%9C%9F%E8%A8%80%E7%94%9F%E6%88%90%E5%99%A8.rs)
至此,術的編譯大致講解完畢,部分與零.一版重複的程式碼就先不貼上來了。若有興趣,可直接參考[音界咒源碼](https://github.com/MROS/yinjie-lang/blob/8aab11a3a6640fbe7691dbbe25cb48f8c4f69532/%E9%9B%B6%E8%99%9F%E7%B7%A8%E8%AD%AF%E5%99%A8/src/%E7%9C%9F%E8%A8%80%E7%94%9F%E6%88%90/%E7%9C%9F%E8%A8%80%E7%94%9F%E6%88%90%E5%99%A8.rs)
227 changes: 227 additions & 0 deletions book/零.二版/精五真言生成(四)編譯若語句.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
最後只剩下若語句還無法編譯了!在討論若語句之前,先來看看零.二版加入的運算子會如何編譯。

## 比較運算(及餘運算)

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

有時候吾人僅想要跳躍,但不在意當下咒指針,那可以直接用 `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 t00則跳至標籤(&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(())
}
```

完整程式碼可見[音界咒源碼](https://github.com/MROS/yinjie-lang/blob/8aab11a3a6640fbe7691dbbe25cb48f8c4f69532/%E9%9B%B6%E8%99%9F%E7%B7%A8%E8%AD%AF%E5%99%A8/src/%E7%9C%9F%E8%A8%80%E7%94%9F%E6%88%90/%E7%9C%9F%E8%A8%80%E7%94%9F%E6%88%90%E5%99%A8.rs)
2 changes: 1 addition & 1 deletion book/零.二版/語義分析:類型檢查.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,4 @@

在施術時,應檢查欲施之術名是否確實為術,若是,還要進一步檢查它的形參與實參數長度是否相當。

類型檢查的實作可以參考[音界咒源碼](https://github.com/MROS/yinjie-lang/blob/1034459a443f44730b5ecec737062444738a7628/%E9%9B%B6%E8%99%9F%E7%B7%A8%E8%AD%AF%E5%99%A8/src/%E7%AC%A6%E8%99%9F%E6%AA%A2%E6%9F%A5.rs)
類型檢查的實作可以參考[音界咒源碼](https://github.com/MROS/yinjie-lang/blob/8aab11a3a6640fbe7691dbbe25cb48f8c4f69532/%E9%9B%B6%E8%99%9F%E7%B7%A8%E8%AD%AF%E5%99%A8/src/%E7%AC%A6%E8%99%9F%E6%AA%A2%E6%9F%A5.rs)

0 comments on commit 4148a74

Please sign in to comment.