iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 23
0

當我們有基本的大小比較之後,就能夠製作一個無限迴圈或者一個有次數的迴圈。

先修改 app.rb 讓我們實作一個執行特定次數的迴圈來驗證實作

# app.rb

i = 0
while i < 5
 i += 1
end

接下來執行 pio test 就會發現我們缺少了一些 OPCode 因此要繼續補齊缺少的條件讓我們的 Ruby VM 可以繼續運行下去。

OP_MOVE

OP_MOVE 基本上算是非常簡單的處理,就是把寄存器的某一個數值移動到另一個位置,之前在實作 callinfo (呼叫資訊)的時候已經知道在某些情況會作為陣列當作參數傳入,在這樣的狀況下會需要順序的正確,而 OP_MOVE 剛好就能幫助我們移動這些資訊。

因為我們在之前已經有先將 OP_MOVE 放到 lib/iron/opcode.h 裡面了,因此只需補上缺少的 VM 實作即可。

// lib/iron/vm.c

// ...
      CASE(OP_MOVE, BB){
        reg[a] = reg[b];
        DEBUG_LOG("r[%d] = r[%d] : %ld", a, b, reg[b]);
        NEXT;
      }
// ...

OP_LOADNIL

接下來因為 while 迴圈的中止,在沒有回傳值的狀況下會是 nil 的回傳,同樣的因為 OPCode 我們在之前已經定義過了,這邊只需要補上 lib/iron/vm.c 的實作即可。

// lib/iron/vm.c

// ...

      CASE(OP_LOADNIL, B) {
        reg[a] = 0;
        DEBUG_LOG("r[%d] = nil", a);
        NEXT;
      }
      
// ...

如此一來我們的 while i < 5 的實作就能夠正常運行,接下來我們要製作出無限迴圈的情況。

OP_LOADTOP_LOADF

接下來我們修改一下 app.rb 改為以下的版本

i = 0
while true
  i += 1
  break if i >= 5
end

因為在 mruby 中 truefalse 直接作為變數被載入還沒有被定義過,因此我們要實作 OP_LOADTOP_LOADF 來實現這件事情。

// lib/iron/vm.c

// ...

      CASE(OP_LOADT, B) goto L_LOADF;
      CASE(OP_LOADF, B) {
      L_LOADF:
        reg[a] = insn == OP_LOADT ? 1 : 0;
        DEBUG_LOG("r[%d] = %s", a, insn == OP_LOADT ? "true" : "false");
        NEXT;
      }
      
// ...

如此一來我們就可以使用 while true 的方式來建立一個無限迴圈。

關於 loopfor

受限於目前的實作,我們現階段無法支援 loopfor 這兩種語法,我們可以嘗試在 app.rb 撰寫對應的程式碼,會發現一些變化。

i = 0
loop do
  i += 1
  break if i > 5
end

首先是 ireps 的數量會從 0 增加到 1 這也表示我們多了一個「子程序」要處理,他就會有屬於他自己的 ISEQ / POOL / SYMS 等資訊,而呼叫的 OPCode 則是 OP_BLOCK (85) 不過我們目前還沒有考慮這些實作,因此暫時無法實現這樣的機制。

如果對 Ruby 的底層設計有所了解的話,就可以猜到像是我們用 def example 定義方法時也會產生新的 ireps 區塊出來,在 Ruby 區分不同程式碼區段的方式大致上就是靠這樣做切分的,也因此我們才能在 Ruby 裡面利用 #method 這類方式再將一個方法轉換成 Proc 來使用。

如果有時間的話我們會再挑戰將 Block 的實作支援,下一篇我們要開始處理載入字串,同時為了能支援整數以外的變數也需要將整個寄存器修改過一遍。


上一篇
Day 22 - 比較處理
下一篇
Day 24 - 字串(一)
系列文
拿到錘子的我想在微控制器上面執行 Ruby30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言