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 类型之外。

#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() 等以后遇到再研究。


 

你可能感兴趣的:(lisp)