當我們有基本的大小比較之後,就能夠製作一個無限迴圈或者一個有次數的迴圈。
先修改 app.rb
讓我們實作一個執行特定次數的迴圈來驗證實作
# app.rb
i = 0
while i < 5
i += 1
end
接下來執行 pio test
就會發現我們缺少了一些 OPCode 因此要繼續補齊缺少的條件讓我們的 Ruby VM 可以繼續運行下去。
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;
}
// ...
接下來因為 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_LOADT
和 OP_LOADF
接下來我們修改一下 app.rb
改為以下的版本
i = 0
while true
i += 1
break if i >= 5
end
因為在 mruby 中 true
和 false
直接作為變數被載入還沒有被定義過,因此我們要實作 OP_LOADT
和 OP_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
的方式來建立一個無限迴圈。
loop
和 for
受限於目前的實作,我們現階段無法支援 loop
和 for
這兩種語法,我們可以嘗試在 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 的實作支援,下一篇我們要開始處理載入字串,同時為了能支援整數以外的變數也需要將整個寄存器修改過一遍。