我們在上一篇已經可以製作 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 裡面 nil
和 false
都屬於 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_value
的 tt
(類型)和 value.i
(整數值)做設定。
true
和 false
數值我們繼續加入巨集到 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_VALUE
跟 SET_NIL_VALUE
的差異在於將「整數值」設定成 1
而 SET_TRUE_VALUE
除了類型變成 MRB_TT_TRUE
之外,整數值也會是 1
也因此,我們在前面提到的 nil
和 false
差異大致上就可以這樣去區分:
true
或者 false
的時候看 tt
(類型)就能知道nil
和 false
的時候,看是 value.i
(整數值)是否大於 0
這邊有趣的是 true
和 false
都是有「數值」的,剛好用來表示「存在的」情況,但是 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 的專案結構下面,同時就可以將程式放到開發版上面運行摟!