雖然我們實作了好幾個方法,但忘記了要處理方法回傳值,在 mruby 中處理回傳值也是相當簡單的,因為編譯器在生成指令的時候都已經幫我們處理好對應的機制,只需要將數值複製到正確的位置上就自然而然能夠正常的將數值回傳。
我們原本在 lib/iron/iron.h
定義的 mrb_func_t
是像這樣
typedef void (*mrb_func_t)(mrb_state* mrb);
在 mruby 的設計中每個方法的定義都應該回傳 mrb_value
作為回傳值,也因此我們需要將他修正為
typedef mrb_value (*mrb_func_t)(mrb_state* mrb);
因為修改了 mrb_func_t
也因此之前定義的方法都會變成無法使用的狀態,所以需要修正方法的實作,這邊用 mrb_puts
當作示範。
mrb_value mrb_puts(mrb_state* mrb) {
int argc = mrb_get_argc(mrb);
mrb_value* argv = mrb_get_argv(mrb);
for(int i = 0; i < argc; i++) {
if(argv[i].tt == MRB_TT_STRING) {
printf("%s\n", mrb_string(argv[i]));
} else {
printf("%d\n", mrb_fixnum(argv[i]));
}
}
return mrb_nil_value();
}
實際上並不困難,只需要修改回傳值以及加入 return mrb_nil_value()
回傳即可。
在實際的 mruby 實作(CRuby 也是類似)還會傳入
mrb_value self
當作參數,大多數時候都會是return self
但因為我們沒有實作物件的概念,因此即使回傳self
也會是nil
接下來我們需要把 OP_SEND
相關的處理加上修正,讓回傳值可以正確的被存到寄存器裡面。
// lib/iron/vm.c
// ...
CASE(OP_SEND, BBB) {
const char* name = (const char*)irep_get(data, IREP_TYPE_SYMBOL, b);
khiter_t key = kh_get(mt, mrb->mt, name);
if (key == kh_end(mrb->mt)) {
DEBUG_LOG("func %s not found", name);
stack[a] = mrb_nil_value();
} else {
// ...
stack[a] = func(mrb);
}
NEXT;
}
CASE(OP_SENDB, BBB) {
const char* name = (const char*)irep_get(data, IREP_TYPE_SYMBOL, b);
khiter_t key = kh_get(mt, mrb->mt, name);
if (key == kh_end(mrb->mt)) {
DEBUG_LOG("func %s not found", name);
stack[a] = mrb_nil_value();
} else {
// ...
stack[a] = func(mrb);
}
NEXT;
}
// ...
實際上就是將找不到方法時直接回傳 nil
確保不會抓取到錯誤的數值,同時將剛剛修改過的 mrb_func_t
回傳值儲存到原本要用來儲存回傳值的 stack[a]
上面。
經過前面實作 Block 的經驗,我們會發現一個 Block 的運行單位剛好是 mrb_exec
而呼叫 return
的時候也剛好是從 mrb_exec
離開,因此我們也要將 mrb_exec
的回傳值修改為 mrb_value
搭配其他機制。
先把原本在 lib/iron/vm.h
的 int
替換為 mrb_value
mrb_value mrb_exec(mrb_state* mrb, const uint8_t* irep);
然後修改實作將回傳值以及用到 return
的地方都用 mrb_value
替代
// lib/iron/vm.c
mrb_value mrb_exec(mrb_state* mrb, const uint8_t* data) {
// ...
CASE(OP_BREAK, B) goto L_RETURN;
CASE(OP_RETURN, B) {
L_RETURN:
DEBUG_LOG("%s r[%d]", insn == OP_RETURN ? "return" : "break", a);
if (mrb->ctx && insn == OP_BREAK) {
mrb->ctx->block = 0;
}
return stack[a];
}
// ...
return mrb_nil_value();
}
如此一來我們就可以取用回傳值來做一些處理了!
為了驗證前面的修改能夠正常使用,我們實做一個簡單的 mrb_add
方法來確認。
sum = add(1, 2)
puts sum
mrb_value mrb_add(mrb_state* mrb) {
mrb_value* argv = mrb_get_argv(mrb);
int sum = mrb_fixnum(argv[0]) + mrb_fixnum(argv[1]);
return mrb_fixnum_value(sum);
}
// ...
mrb_define_method(mrb, "add", mrb_add);
調整測試的程式碼加入上面兩段程式後,運行 pio test
就可以看到 3
被正確的顯示在螢幕上。
要注意上面的程式碼只是參考不能直接使用
不過這個回傳值處理還是非常陽春的,像是「陣列」就無法處理,這些情況就需要未來持續擴充我們的 Ruby VM 實作來支援。