iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 25
0

因為我們已經將 mrb_value 套用到我們的 Ruby VM 中,也因此能夠更加彈性的處理各種類型的資料,現在就讓我們將字串的支援加入到裡面吧!

跟之前的方式一樣,我們直接撰寫希望呈現效果的 Ruby 腳本並且讓他執行失敗來確認所需要實作的 OPCode

# frozen_string_literal: true

puts "Hello World"

在測試的時候發現 irep_get 的 POOL 區段其實多移動了一個單位的指標會造成問題,請大家修正 p += len + 1;p += len; 但不要去修改 SYMS 區塊的部分

OP_STRING

當我們執行後會發現需要的是 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 螢幕上實現一個簡單的跑馬燈。


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

尚未有邦友留言

立即登入留言