继续前一篇,关于 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 类型之外。
#define XUNTAG(a, type) ((intptr_t) (XLI (a) - (type)))
关于名字 XUNTAG,前缀 X, UN表示取消、去掉,TAG 是类型标记。这个宏用于当已经
确知 Lisp_Object 对象类型的 a 的标记值 type 时使用。
上面两个宏写作方法形态(略去后者):
inline intptr_t Lisp_Object::xpntr() const throw() {
return (intptr_t) (this->xli() & ~TYPEMASK);
}
这样对于 Lisp_Object 获取其信息构成的最基本的最低层的宏(函数)就是如上所述的。
===
下一个我认为很重要的结构是 Lisp_Cons, 其对应的类型是 Lisp_Cons,表示点对单元。
Lisp 语言中核心数据结构就是点对,通过点对主要构成 list(列表),而 lisp 单词本身
就是 LISt Process (列表处理)语言的缩写。由于主要研究实现,故而语法部分我就不
多涉及了。
点对单元结构 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() 等以后遇到再研究。