emacs lisp 研究 lisp.h 繼續 (幾何畫板開發筆記 六)

繼續前一篇,關於 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() 等之後遇到再研究。

相關文章
相關標籤/搜索