繼續前一篇,關於 struct Lisp_Object 還有一點點相關的宏(函數)要說明。 安全
已知 struct Lisp_Object 的字段 i 中有 val+tag 兩種信息,也已知 XTYPE() 宏用於獲得 tag
信息,那麼也必定有得到 val 部分的宏: 數據結構
#define XPNTR(a) ((intptr_t) (XLI(a) & ~TYPEMASK)) 函數
名字 XPNTR,其中 X 是前綴,PNTR 應是 Pointer 的簡寫。這個宏取出 val 部分,轉換爲 intptr_t
類型。從名字知道語義上是將值看作一個指針,實際是指向 Lisp 對象實體的指針。除了int 類型以外。 this
#define XUNTAG(a, type) ((intptr_t) (XLI (a) - (type))) spa
關於名字 XUNTAG,前綴 X, UN表示取消、去掉,TAG 是類型標記。這個宏用於當已經
確知 Lisp_Object 對象類型的 a 的標記值 type 時使用。 指針
上面兩個宏寫做方法形態(略去後者):
inline intptr_t Lisp_Object::xpntr() const throw() {
return (intptr_t) (this->xli() & ~TYPEMASK);
} rest
這樣對於 Lisp_Object 獲取其信息構成的最基本的最低層的宏(函數)就是如上所述的。 對象
=== ip
下一個我認爲很重要的結構是 Lisp_Cons, 其對應的類型是 Lisp_Cons,表示點對單元。
Lisp 語言中核心數據結構就是點對,經過點對主要構成 list(列表),而 lisp 單詞自己
就是 LISt Process (列表處理)語言的縮寫。因爲主要研究實現,故而語法部分我就不
多涉及了。 rem
點對單元結構 Lisp_Cons 也定義於 lisp.h 中。其形式以下:
struct Lisp_Cons {
Lisp_Object car; // 此點對單元的 car.
Lisp_Object cdr; // 此點對單元的 cdr.
};
因爲喜歡對名字、命名(Naming)究根追底,對 car,cdr 這樣奇怪的名字咱們仍是
試着理解一下爲何。當 John McCarthy 實現 Lisp 語言於早期 IBM 704 計算機上的時候,
IBM 704 機器的一個機器字(word)有 36 個 bits,這個字能夠被分解爲4個部分:
car: Contents of the Address part of Register number
cdr: Contents of the Decrement part of Register number
cpr,ctr 略。
這樣一個點對單元在放入 704 的機器字的時候,用 car 部分做爲第一個對象的指針,用
cdr 部分做爲列表剩餘部分的指針。這樣的名字一直沿用至今。。。有時它們也有別名:
car: head, first
cdr: tail, rest
這些都是從 http://en.wikipedia.org/wiki/CAR_and_CDR 這裏看到的。。。
一個 Lisp_Object 當其 tag 值爲 Lisp_Cons 的時候,其 xpntr() 部分就是指向 Lisp_Cons
結構的指針:
#define XCONS(a) (assert(CONSP(a)), (struct Lisp_Cons *)XUNTAG(a, Lisp_Cons))
// 一樣語義寫做函數形態:
inline Lisp_Cons *xcons(Lisp_Object a) {
assert(a.is_cons()); return (struct Lisp_Cons *) a.xuntag(Lisp_Cons);
}
// 或:
inline Lisp_Cons *Lisp_Object::xcons() {
assert(this->is_cons()); return (struct Lisp_Cons *) this->xuntag(Lisp_Cons);
}
當已知一個 Lisp_Object 的類型是點對單元時,就能夠訪問該點對單元的字段 car,cdr:
#define XCAR_AS_LVALUE(c) (XCONS(c)->car)
#define XCDR_AS_LVALUE(c) (XCONS(c)->cdr)
這兩個宏提取一個 Lisp_Object c 的 car,cdr 部分,而且強調是能夠用做左值。寫做方法形態:
inline Lisp_Object& xcar_as_lvalue(Lisp_Object c) { return XCONS(c)->car; }
若是隻爲了獲得/讀取 car,cdr 而不打算修改/設置它們,則更安全、簡單的宏是:
#define XCAR(c) LISP_MAKE_RVALUE (XCAR_AS_LVALUE (c))
#define XCDR(c) LISP_MAKE_RVALUE (XCDR_AS_LVALUE (c))
這裏使用了前面介紹的 LISP_MAKE_RVALUE() 宏來將左值轉換爲右值,從而保護原值不被修改。
一樣寫做方法形態更容易理解:
inline Lisp_Object Lisp_Object::xcar() { return this->xcons()->car; }
inline Lisp_Object Lisp_Object::xcdr() { return this->xcons()->cdr; }
設置/寫入 car,cdr 的宏:
#define XSETCAR(c,n) (XCAR_AS_LVALUE (c) = (n))
#define XSETCDR(c,n) (XCDR_AS_LVALUE (c) = (n))
寫做方法形態:
inline Lisp_Object Lisp_Object::xsetcar(Lisp_Object n) {
this->xcons()->car = n;
}
若是有更容易理解的寫法就更好了:
c.xcar() = car;
===
若是當一個 Lisp_Object 對象不知道不肯定其是什麼類型,而又想獲得其 car,cdr 時使用宏:
#define CAR(c) (CONSP ((c)) ? XCAR ((c)) \
: NILP ((c)) ? Qnil \
: wrong_type_argument (Qlistp, (c)))
寫做函數形態,以方便理解:
inline Lisp_Object Lisp_Object::car() {
if (this->is_cons()) return this->xcar();
if (this->is_nil()) return nil;
signal_error(wrong_type_argument, ...);
}
注: nil 這裏假設是一個 Lisp 常量,語義上等價於別的語言 false;在程序中實際寫爲 Qnil。
爲了方便語義描述,我直接使用 nil 來表示。
在 Lisp 語義上,若是是點對單元,則返回其 car;若是是 nil,返回 nil,由於 nil 也做爲空
列表對待,nil 的 car,cdr 都是 nil。若是都不是,則是類型錯誤,signal error 屬於錯誤
處理部分,之後有機會研究。 CDR() 宏也是相似的,再也不細述。
若是不知道 Lisp_Object 的類型,又要獲取其 car,且不觸發錯誤,則可使用以下宏:
#define CAR_SAFE(c) (CONSP(c) ? XCAR(c) : nil)
語義簡單明瞭,若是是點對單元則返回其 car,不然都返回 nil。CDR_SAFE() 與此相似。
這樣,對於 Lisp_Cons 點對單元,其從 Lisp_Object 轉換,讀取,寫入的底層宏就基本完備了。 基於這些基本宏還可以創建起更多的宏或函數,如 XCADR(), F_length() 等之後遇到再研究。