继续前一篇,关于 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() 等之后遇到再研究。