iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 24
0
IoT

拿到錘子的我想在微控制器上面執行 Ruby系列 第 24

Day 24 - 字串(一)

透過前幾天加入的比較、迴圈的支援後,假設我們希望在 TFT 螢幕上繪製文字跑馬燈就必須處理字串,也就是能將字串讀取進來。不過在這之前我們需要將 mrb_value 完善,前面的實作我們都是利用 01 來區分 truefalse 的,但實際上我們處理的都應該要是 mrb_value 才對。

這就牽涉到我們目前使用的 reg[] 陣列,因為他現在是以 intptr_t 來儲存的,所以我們可以拿來當作整數值或者作為指標指向某個記憶體位置,但是在 mruby 的實作中,它實際上是一個 mrb_value 陣列,而我們所有的操作都是 mrb_value 的替換(複製)

也因此,當我們抓取到一個 mrb_value 的時候,就能夠透過 tt 資訊判斷是否為字串,進而讀取 value.p 而非 value.i 來獲取指標並且當作 char* 讀取來當作字串。

修改 reg 實作

// lib/iron/vm.c

// ...

mrb_value stack[irep->nregs - 1];

// ...

// Initialize
stack[0] = mrb_nil_value();


// ...

我們先將 reg 改名為 stack 並且修改型別為 mrb_value 而這也是在 mruby 實際上使用的名字,同時我們將初始化的 stack[0] 設定為 nil 的數值,因為對我們來說我們並沒有物件的概念,也就不會有 Kernel 物件可以作為參考。

推測會叫做 stack 的原因應該是因為 Ruby 都是 Stack-based VM 為基礎所設計的關係,除此之外之前提過的 Context 也都會有自己的 Stack 存在,有點類似當下呼叫的狀況所擁有的資訊堆疊。

修改後的 vm.c

因為修改的部分相當零碎,這邊直接將程式碼片段貼上

// lib/iron/vm.c

// ...
  while(!error) {
    uint8_t insn = READ_B();

    switch(insn) {
      CASE(OP_MOVE, BB){
        stack[a] = stack[b];
        DEBUG_LOG("r[%d] = r[%d] : %p", a, b, &stack[b]);
        NEXT;
      }
      CASE(OP_LOADI, BB) {
        stack[a] = mrb_fixnum_value(b);
        DEBUG_LOG("r[%d] = %d", a, b);
        NEXT;
      }
      CASE(OP_LOADINEG, BB)  {
        stack[a] = mrb_fixnum_value(-b);
        DEBUG_LOG("r[%d] = %d", a, -b);
        NEXT;
      }
      CASE(OP_LOADI__1, B) goto L_LOADI;
      CASE(OP_LOADI_0, B) goto L_LOADI;
      CASE(OP_LOADI_1, B) goto L_LOADI;
      CASE(OP_LOADI_2, B) goto L_LOADI;
      CASE(OP_LOADI_3, B) goto L_LOADI;
      CASE(OP_LOADI_4, B) goto L_LOADI;
      CASE(OP_LOADI_5, B) goto L_LOADI;
      CASE(OP_LOADI_6, B) goto L_LOADI;
      CASE(OP_LOADI_7, B) {
        L_LOADI:
        stack[a] = mrb_fixnum_value(insn - OP_LOADI_0);
        DEBUG_LOG("r[%d] = mrb_int(%d)", a, mrb_int(stack[a]));
        NEXT;
      }
      CASE(OP_LOADNIL, B) {
        stack[a] = mrb_nil_value();
        DEBUG_LOG("r[%d] = nil", a);
        NEXT;
      }
      CASE(OP_LOADSELF, B) {
        stack[a] = stack[0];
        DEBUG_LOG("r[%d] = self %p", a, &stack[a]);
        NEXT;
      }
      CASE(OP_LOADT, B) goto L_LOADF;
      CASE(OP_LOADF, B) {
      L_LOADF:
        stack[a] = insn == OP_LOADT ? mrb_true_value() : mrb_false_value();
        DEBUG_LOG("r[%d] = %s", a, insn == OP_LOADT ? "true" : "false");
        NEXT;
      }
      CASE(OP_JMP, S) {
        p = porg + a;
        DEBUG_LOG("jmp %d", b);
        NEXT;
      }
      CASE(OP_JMPIF, BS) goto L_JMPNOT;
      CASE(OP_JMPNOT, BS) {
      L_JMPNOT:
        if (insn == OP_JMPIF) {
          DEBUG_LOG("jmp %d if r[%d] == %s", b, a, !MRB_IS_FALSE(stack[a]) ? "true" : "false");
        } else {
          DEBUG_LOG("jmp %d if !r[%d] == %s", b, a, MRB_IS_FALSE(stack[a]) ? "false" : "true");
        }

        if ((insn == OP_JMPIF) == (!MRB_IS_FALSE(stack[a]))) {
          p = porg + b;
        }
        NEXT;
      }
      CASE(OP_ADDI, BB) {
        stack[a] = mrb_fixnum_value(mrb_fixnum(stack[a]) + b);
        DEBUG_LOG("r[%d] = r[%d] + %d", a, a, b);
        NEXT;
      }
      CASE(OP_EQ, B) {
        stack[a] = mrb_fixnum(stack[a]) == mrb_fixnum(stack[a + 1]) ? mrb_true_value() : mrb_false_value();
        DEBUG_LOG("r[%d] = r[%d] == r[%d+1]", a, a, a);
        NEXT;
      }
      CASE(OP_LT, B) {
        stack[a] = mrb_fixnum(stack[a]) < mrb_fixnum(stack[a + 1]) ? mrb_true_value() : mrb_false_value();
        DEBUG_LOG("r[%d] = r[%d] < r[%d+1]", a, a, a);
        NEXT;
      }
      CASE(OP_LE, B) {
        stack[a] = mrb_fixnum(stack[a]) <= mrb_fixnum(stack[a + 1]) ? mrb_true_value() : mrb_false_value();
        DEBUG_LOG("r[%d] = r[%d] <= r[%d+1]", a, a, a);
        NEXT;
      }
      CASE(OP_GT, B) {
        stack[a] = mrb_fixnum(stack[a]) > mrb_fixnum(stack[a + 1]) ? mrb_true_value() : mrb_false_value();
        DEBUG_LOG("r[%d] = r[%d] > r[%d+1]", a, a, a);
        NEXT;
      }
      CASE(OP_GE, B) {
        stack[a] = mrb_fixnum(stack[a]) >= mrb_fixnum(stack[a + 1]) ? mrb_true_value() : mrb_false_value();
        DEBUG_LOG("r[%d] = r[%d] > r[%d+1]", a, a, a);
        NEXT;
      }
      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);
        } else {
          DEBUG_LOG("func = %s", name);
          mrb_func_t func = kh_value(mrb->mt, key);

          mrb_value argv[c];
          for(int i = 1; i <= c; i++) {
            argv[i - 1] = stack[a + i];
          }

          mrb_callinfo ci = { .argc = c, .argv = argv };
          mrb->ci = &ci;

          func(mrb);
        }
        NEXT;
      }
      CASE(OP_RETURN, B) {
        DEBUG_LOG("%s r[%d]", insn == OP_RETURN ? "return" : "break", a);
        return mrb_fixnum(stack[a]);
      }
      default:
        DEBUG_LOG("Unsupport OP Code: %d\n", insn);
        error = 1;
    }
  }
  
// ...

基本上就是將數字相關的用 mrb_fixnum_value 製作出 mrb_value 類型的資料,如果是比較的處理則修改為 mrb_fixnum 取出後處理,再用 mrb_fixnum_value 製作新的 mrb_value 放回去,除此之外就是將除錯訊息調整成能被讀取到的狀態,因為我們原版是以 intptr_t 的方式儲存可以直接當作整數輸出,但是現在我們需要用像是指標之類的方式以記憶體位置的方式呈現。

除此之外我們在 lib/iron/value.h 加入了 MRB_IS_FALSE 巨集來幫助我們判斷數值是否為 false 或者 nil

// lib/iron/value.h

// ...
#define MRB_IS_FALSE(o) (o.tt == MRB_TT_FALSE)
//...

修改完畢後可以用 pio test 驗證是否能正常運作,下一篇我們會來製作可以將字串儲存到 mrb_value 的處理,讓我們可以支援輸出字串的功能。


上一篇
Day 23 - 迴圈支援
下一篇
Day 25 - 字串(二)
系列文
拿到錘子的我想在微控制器上面執行 Ruby30

尚未有邦友留言

立即登入留言