Emacs Lisp学习

[TOC]

基本概念

符号

一个symbol是一个具有唯一名字的object,它有四个cell,每个cell引用另一个object:

  • print name
  • value
  • function
  • property list

LISP differs from most imperative programing languages such as C, Java, Perl, in that it deals with symbols, as opposed to just variables and values.

In practice, this means that in lisp, variables can be manipulated in its un-evaluated state. The situation is like the need for the “evaluate” command in many languages, where the programer can built code as strings and do “evaluate(myCodeString)” to achieve meta-programing. In lisp, variable’s unevaluated form are always available. You just put a apostrophe in front of it. This is why variables in lisp are called symbols. This makes meta-programing more powerful.

For example, in most languages, once you defined “x=3”, you cannot manipulate the variable “x” because it gets evaluated to 3 right away. If you want, you have to build a string “”x”” and manipulate this string, then finally use something like “evaluate(myCodeString)” to achieve the effect. In most languages, the use of “evaluate()” breaks down quickly because the language is not designed for doing it. It’s extremely slow, and impossible to debug, and there lacks many facilities for such meta programing.

The ability to meta-program has many applications. For example, when you need to take user input as code (such as math formulas), or need to manipulate math expressions, or writing programs that manipulate source code. (e.g. XML transformation)

Cons Cell、list和alist

  • 一个cons cell包含两部分:CAR和CDR,CAR和CDR可以引用任何对象
  • 一个list是一个cons cell的序列,每个cons cell的CDR指向它的下一个cons cell,就是数据结构里的链接表(list)。用一个包含多个元素的括号来表示list,如 (A 2 "A")
  • alist就是association list,是构造特殊的list,它的每个cons cell里,CAR是key,CDR是value。

    这样来表示alist:

    ((rose . red) (lily . white) (buttercup . yellow))
    

    其中(rose . red)是一个cons cell的一般化表示法,rose是CAR,red是CDR。可以使用函数assoc来非常方便的查询alist,比如:

    (assoc 'rose '((rose . red) (lily . white) (buttercup . yellow)))
    

函数

function是用symbol定义的

  • defun定义一个symbol做为function,创建一个lambda expression并把它存入这个symbol的function cell里

宏和函数区别在于:函数只是对表达式求值;而宏先展开(expansion)表达式,然后再对展开后的表达式求值。

在参数处理上也有同样的区别:宏的参数作为表达式(expression)传递给宏展开;而函数参数是求值(evaluate)后传递给函数的。

所以,如果这样定义宏是会出错的:

(defmacro inc (var)
  (setq var (1+ var)) ; 错误!当expansion时,var是一个表达式,而 1+ 是不能作用于表达式的。

定义宏的时候,可以用declare表达式增加一些debug和indent信息,比如:

(defmacro when (cond &rest body)
  (declare (indent 1) (debug t))
  (list 'if cond (cons 'progn body)))
(symbol-plist 'when) ; => (lisp-indent-function 1 edebug-form-spec t)

直接用list、cons、append构造宏写起来比较复杂,为了简洁,lisp中有一个特殊的记号宏“`” (backquote),在backquote宏里,所有的表达式都是引起(quote)的,如果要让其不引起,需要在前面加逗号“,”,如果要让一个列表作为整个列表的一部分(slice),可以用“,@”。比如,上面的宏可以写成:

(defmacro when (cond &rest body)
  (declare (indent 1) (debug t)
  `(if ,cond (progn ,@body))))

宏、函数和backquote宏

(defmacro inc (var)
  `(setq ,var (1+ ,var)))
(let ((var 0)) (inc var))
  => 1

(defun inc (var)
  `(setq ,var (1+ ,var)))
(let ((var 0)) (inc var))
  => (setq 0 (1+ 0))

可以使用 macroexpand 函数来模拟宏的扩展。

(defmacro inc (var)
  (list 'setq var (list '1+ var)))
   => inc

(macroexpand '(inc r))
   => (setq r (1+ r))

可以使用 edebug-defun 来调试宏,需要 (declare (debug t))

正则表达式

Elisp 里的正则式比一般接触到的PHP、Perl里的正则式要难以驾驭一些,因为碰到需要转意特殊字符的时候,经常会写出很多让人发晕的反斜杠(backslash \)。这里有两个tips:

  • 先写出正常的正则式,然后把其中需要转意的特殊字符前面再加上一个backslash。比如需要匹配特殊字符 \$、|、 $和\,在Elisp里就对应写成\$、\\|\\( \\\\ (最后一个比较特别)
  • 对于[]表示的字符集中,特殊字符不能够使用backslash转意,而必须把特殊字符放在匹配表达式的最前面或者最后边。比如需要在字符集中匹配bracket]的话,就必须放在最前面;匹配hyphen-的话,就必须放在最后边。有些tricky。

比如这条语句:

(let ((str ")}123[te\"st]abc"))
  (string-match "[^])}'\">]+[])}'\">]" str)
  (match-string 0 str))        ; output: 123[te"st]

所幸的是Emacs提供一个很好用的正则式调试工具,可以帮助我们测试复杂的正则式: M-x re-builder

调试elisp

调试elisp可以使用edebug调试器,使用方法是:

  • 移动光标到待调试函数的末尾,执行 M-x edebug-defun
  • 进入emacs-lisp-modelisp-interaction-mode,运行待调试函数,比如M-:或者M-x ielm后调用待调试函数;
  • 自动进入edebug-mode,就可以开始调试了。

调试方法和gdb类似,常用命令如下:

命令 说明
SPC 执行下一条语句
n step next下一条语句
i step into下一条语句
b 设置断点
g continue
E 切换到evaluation list buffer,可以输入一些expression来eval