因為我們已經將 mrb_value
套用到我們的 Ruby VM 中,也因此能夠更加彈性的處理各種類型的資料,現在就讓我們將字串的支援加入到裡面吧!
跟之前的方式一樣,我們直接撰寫希望呈現效果的 Ruby 腳本並且讓他執行失敗來確認所需要實作的 OPCode
# frozen_string_literal: true
puts "Hello World"
在測試的時候發現
irep_get
的 POOL 區段其實多移動了一個單位的指標會造成問題,請大家修正p += len + 1;
為p += len;
但不要去修改 SYMS 區塊的部分
當我們執行後會發現需要的是 OP_STRING
(79
) 這個 OPCode 要實作,這個指令主要的功能就是從 POOL 將字串取出,然後我們再複取出的字串放到記憶體中,並且將 mrb_value
的指標指向這個位置。
也因此我們會在 mruby 的註解上看到 /* R(a) = str_dup(Lit(b)) */
這樣的說明,表示我們字串資料複製後放到指定的位置中。
首先我們來實作 OP_STRING
的處理來取出資料
// lib/iron/vm.c
// ...
CASE(OP_STRING, BB) {
const char* str = (const char*)irep_get(data, IREP_TYPE_LITERAL, b);
stack[a] = mrb_string_value(str);
DEBUG_LOG("r[%d] = str_dup(lit(%d) %s)", a, b, str);
NEXT;
}
// ...
我們在這邊實作了跟方法類似的處理,先將要載入的字串利用 irep_get
抓取出來,然後利用接下來會實作的 mrb_string_value
製作出新的 mrb_string_value
放到寄存器裡面,讓後續的行為可以利用這個變數。
mrb_string_value
有過前面的經驗,實作上就相對簡單很多,唯一需要注意的就是我們有使用了 malloc
去分配記憶體,需要在未來找機會將他回收。這個時機點通常會跟 GC 的處理有關係,不過我們這次暫時還沒有機會實作。
// lib/iron/value.h
// ...
static inline mrb_value mrb_string_value(const char* src) {
mrb_value v;
int len = strlen(src);
char* lit = (char*)malloc(len + 1);
memcpy(lit, src, len);
v.tt = MRB_TT_STRING;
v.value.p = (void*)lit;
return v;
}
我們先計算傳入的字串長度,然後分配一段足夠的記憶體將字串內的資料複製進去,最後再將 mrb_value
的類型設定為 MRB_TT_STRING
以及將指標指向字串。
除了記憶體的問題之外,我們在這類的操作還需要注意「傳參」跟「傳址」的情況,有時候我們可能會因為「傳參」的狀況在操作中做了多餘的複製,而造成原本記憶體不太足夠的微控制器無法正常運作。
這裡我們傳遞的是字串的指標,也因此複製的部分只有位置資訊而已。
puts
方法當我們順利分配字串到記憶體並且轉為變數後,我們就能正常使用,不過之前的實作 puts
只能處理整數資訊,我們需要更新一下實作讓他可以處理字串。
// src/hal_native/embed_methods.c
void 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", (char*)argv[i].value.p);
} else {
printf("%d\n", mrb_fixnum(argv[i]));
}
}
}
// src/hal_arduino/embed_methods.c
void mrb_puts(mrb_state* mrb) {
int argc = mrb_get_argc(mrb);
mrb_value* argv = mrb_get_argv(mrb);
char num[4];
tft.fillScreen(TFT_BLACK);
for(int i = 0; i < argc; i++) {
if(argv[i].tt == MRB_TT_STRING) {
tft.drawString((char*)argv[i].value.p, 8, i * 16 + 8, 1);
} else {
sprintf(num, "%d", mrb_fixnum(argv[i]));
tft.drawString(num, 8, i * 16 + 8, 1);
}
}
}
另外要注意的是測試的部分我們目前並沒有跟 hal_native
共用,因此也需要更新對應的內容才會看到正確的結果。
下一篇我們會基於前面所有的實作,在 TFT 螢幕上實現一個簡單的跑馬燈。