iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 17
0
IoT

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

Day 17 - 變數(二)

我們在上一篇已經可以製作 MRB_TT_FIXNUM 這類整數型的變數,但是必須透過下面這種方式手動去製作 mrb_value

mrb_value v = {
 .tt = MRB_TT_FIXNUM,
 .value.i = 1
}

隨著未來增加的型別增加,這樣的做法除了會讓原始碼變長之外也不太容易使用,我們可以參考 mruby 的作法,製作「生成變數」跟「讀取變數」兩個一組的方法來輔助我們。

nil 數值

在 mruby 裡面我們可以用下面的程式碼獲得 false 的效果,但是這是為什麼呢?

val = nil
puts "I am false" unless val

val = false
puts "I am false, too" unless val

puts "We are same!" if nil == false

這是因為在 mruby 裡面 nilfalse 都屬於 MRB_TT_FALSE 但是擁有著不同的數值,Ruby 就是利用這樣的設計巧妙的區分出「不存在」跟「假」的概念。

CRuby 的實作中有很高機率也是這樣設計的,透過 mruby 雖然我們無法完全的對應到 CRuby 但是可以從中窺探一些 CRuby 本身的設計。

我們先參考 mruby 原始碼在 src/value.h 裡面加入 SET_NIL_VALUE(o) 巨集用來設定變數為 nil

// src/value.h

#define SET_VALUE(o, ttt, attr, v) do {\
  (o).tt = ttt;\
  (o).attr = v;\
} while(0)

#define SET_NIL_VALUE(r) SET_VALUE(r, MRB_TT_FALSE, value.i, 0)

這邊製作了兩個巨集,上面的 SET_VALUE 帶入 SET_NIL_VALUE 巨集傳入的結果後展開後會長成這樣

do {
 (r).tt = MRB_TT_FALSE;
 (r).value.i = 0;
}

我們接著在加入 mrb_nil_value() 方法的實作

// src/value.h

IRON_INLINE mrb_value mrb_nil_value(void) {
  mrb_value v;
  SET_NIL_VALUE(v);
  return v;
}

搭配上面的 SET_NIL_VALUE 展開後,我們就能得到這樣的實作

static inline mrb_value mrb_nil_value(void) {
  mrb_value v;
  do {
    (v).tt = MRB_TT_FALSE;
    (v).value.i = 0;
  }
  return v;
}

實際上就是將 mrb_valuett(類型)和 value.i(整數值)做設定。

truefalse 數值

我們繼續加入巨集到 src/value.h 裡面

// src/value.h

#define SET_FALSE_VALUE(r) SET_VALUE(r, MRB_TT_FALSE, value.i, 1)
#define SET_TRUE_VALUE(r) SET_VALUE(r, MRB_TT_TRUE, value.i, 1)

我們會看到 SET_FALUE_VALUESET_NIL_VALUE 的差異在於將「整數值」設定成 1SET_TRUE_VALUE 除了類型變成 MRB_TT_TRUE 之外,整數值也會是 1

也因此,我們在前面提到的 nilfalse 差異大致上就可以這樣去區分:

  1. 單純區分 true 或者 false 的時候看 tt(類型)就能知道
  2. 細分 nilfalse 的時候,看是 value.i(整數值)是否大於 0

這邊有趣的是 truefalse 都是有「數值」的,剛好用來表示「存在的」情況,但是 nil 的數值用 0 表示對應「不存在」透過這樣的機制是否就能更好瞭解為什麼會有「不存在」跟「假」的概念了呢?

整數值

最後製作整數值的方式就單純很多,一樣先利用巨集幫我們設定資料。

// src/value.h

#define SET_INT_VALUE(r, n) SET_VALUE(r, MRB_TT_FIXNUM, value.i, (n))

然後定義 mrb_fixnum_value(int i) 的方法來製作 mrb_value 出來

// src/value.h

static inline mrb_value mrb_fixnum_value(int i) {
  mrb_value v;
  SET_INT_VALUE(v, i);
  return v;
}

除此之外再繼續定義 mrb_fixnum(v) 的巨集幫我們抽取數值出來

// src/value.h

#define mrb_fixnum(o) (o).value.i
#define mrb_int(o) mrb_fixnum(o)

如此一來我們就可以用這些方法來改寫部分程式,像是 src/main.c 我們自訂的 puts 方法

// src/main.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++) {
    printf("%d\n", mrb_int(argv[i]));
  }
}

下一篇我們要來把專案轉換到 PlatformIO 的專案結構下面,同時就可以將程式放到開發版上面運行摟!


上一篇
Day 16 - 變數(ㄧ)
下一篇
Day 18 - PlatformIO 的準備
系列文
拿到錘子的我想在微控制器上面執行 Ruby30

尚未有邦友留言

立即登入留言