Emacs Lisp编程

Table of Contents

1 说明

写着玩玩儿而已。

1.1 许可协议

本作品采用知识共享 署名 4.0 国际 许可协议进行许可。访问 http://creativecommons.org/licenses/by/4.0/ 查看该许可协议。

2 开发环境

用Emacs打开一个.el为后缀的文件名,或,切换到“*scratch*”这个Buffer里编代码。

当你处在lisp-mode时,光标移到Lisp表达式尾部:

  1. 按C-x C-e执行Lisp表达式。
  2. 按C-j执行Lisp表达式,并插入返回值在当前Buffer中。

还有一些工具帮助你开发Emacs Lisp程序:

  • paredit,自行安装,可用于补全括号等操作。
  • ielm,Emacs自带的Emacs Lisp交互式环境。M-x ielm。

2.1 帮助

调用documentation函数可以查看符号对应的文档:

(documentation 'message)

更常用的是C-h f或M-x describe-function。

3 基础

Lisp的语法很简单,就三种:

  • (hello):表示调用hello这个函数(或宏)。
  • '(hello):表示一个列表。
  • hello:返回符号对应的值,等价于求变量的值。

用一些例子说明:

(defvar hello "hello world") ; 定义一个变量
(setf hello "Hello world") ; 调用setf来改变变量的值
hello ; 返回hello对应的值
;; 改变hello的值,和setf的区别是set需要对符号加上引用符“'”
(set 'hello "Hello")
;; 表示这是一个列表,有三个元素:
;; 第一个是符号setf
;; 第二个是符号hello
;; 第三个是字符串:Hello world
'(setf hello "Hello world")
知识点:

1. Emacs Lisp的注释符号是英文分号“;”。

2. 执行“'hello”表示返回hello这个符号,等同于(quote hello)。“hello”表示返回符号hello对应的值。

4 基本类型

通过type-of函数获得对象的类型:

(type-of 1) ; => integer
(type-of "test") ; => string
(type-of 1.) ; => integer
(type-of 1.0) ; => float
(type-of 'a) ; => symbol

4.1 符号

Symbol是对象的名字。

Common Lisp程序员注意,Common Lisp会把Symbol名称转成大写,Emacs Lisp不会。

;;; 设置符号的值
(setq hello "hello world") ; => "hello world"
(setf hello "hello world") ; => "hello world"
(set 'hello "hello world") ; => "hello world"

(symbolp 'hello) ; => t,判断对象是不是符号
(symbol-value 'hello) ; => "hello world",获得符号对应的值
(symbol-name 'hello) ; => "hello",获得符号的名字

4.2 布尔

t为真,nil和()为假,除此之外都为真,并且nil和()是相等的。

术语 谓词函数 专指函数返回值是t或者nil,函数名一般以“p”结尾,比如booleanp,用来判断对象是不是布尔类型:

(booleanp t) ; => t
(booleanp nil) ; => t
(booleanp 1) ; => nil
(booleanp '()) ; => t

4.3 字符串

字符串由双引号包围。

实质上字符串由不可变的字符序列组成——用字符组成的数组。所以可以使用操作数组的函数操作字符串。如"hello"表示字符串hello,而这个字符串由“h”、“e”、“l”、“l”和“o”这几个字符组成。

也可以使用string函数手动创建字符串:

(string ?\h ?\i) ; => "hi"

4.3.1 示例

;;; 创建重复n次的字符串
(make-string 5 ?a) ; => "aaaaa"

;;; 取子字符串
(substring "hello world" 0 5) ; => "hello",取下标0~5之间的字符
(substring "hello world" 6) ; => "world",从下标6开始取剩余字符
(substring "hello world" -5) ; => "world",倒序取字符
(substring "hello world" -5 -1) ; => "worl"

;;; 连接字符串
(concat "hello " "world") ; => "hello world"

;;; 切割字符串
(split-string "hello world" " ") ; => ("hello" "world")

;;; 获得字符串长度
(length "a string") ; => 8

;;; 谓词函数
(stringp "abc") ; => t,判断是不是字符串
;; 判断对象是否是字符串或者nil
(string-or-null-p "abc") ; => t
(string-or-null-p nil) ; => t
(string-or-null-p t) ; => nil
;; 判断对象是否是字符串或者字符
(char-or-string-p ?\a) ; => t
(char-or-string-p "a") ; => t
(char-or-string-p 1) ; => t,实质上每个字符都有对应的数字

;;; 判断字符串是否为空的三种方法
(eq "" "") ; => t
(= 0 (length "")) ; => t
(equal "" "") ; => t
(string-equal "" "") ; => t

;;; 字符串转列表
(string-to-list "hello world") ; => (104 101 108 108 111 32 119 111 114 108 100)

;;; 删除空白字符
(replace-regexp-in-string "" "" "astring") ; => "astring"

4.4 数字

4.4.1 基本运算

(+ 1 1 1) ; => 3
(- 3 2) ; => 1
(* 2 2) ; => 4
(/ 4 3) ; => 1,注意这里和Common Lisp不一样的是,这里不是返回有理数(4/3),而是返回近似值。
(/ 4 3.0) ; => 1.3333333333333333
(% 4 3) ; => 1,求余
(expt 2 3) ; => 8,次方

4.4.2 整数(Integer)

表示法:

1
1. ; 类似1
-1 ; 负数表示
+1
4.4.2.1 示例
;;; 数字和字符串互转
(string-to-number "10") ; => 10
(number-to-string 10) ; => "10"

;;; 判断是否是整数
(integerp 1) ; => t
(integerp 1.0) ; => nil

4.4.3 浮点(Float)

表示法:

+1.0
1.0

;;; 科学计数
+1e10
100e-10
1e-08
;;; 判断对象是不是浮点数
(floatp 1) ; => nil
(floatp 1.0) ; => t

;;; 与浮点数做运算,返回的是浮点类型
(+ 1 1.0) ; => 2.0,
(* 1 1.0) ; => 1.0
(- 10 1.0) ; => 9.0
(/ 4 2.0) ; => 2.0

4.4.4 位操作

(lsh 2 2) ; => 8,左移操作
(lsh 4 -2) ; => 1,右移操作

4.5 序列

Emacs Lisp有两种序列:列表(List)和数组(Array)。

4.5.1 列表(List)

一个List就是一组Cons Cell组成。

4.5.1.1 Cons Cell

一个Cons Cell包含两个元素,car函数取第一个,cdr取第二个:

(defvar a-cons-cell (cons 1 2))
(car a-cons-cell) ; => 1
(cdr a-cons-cell) ; => 2
4.5.1.2 Dotted pair

(1 . 2)

表示car是1,cdr是2。

4.5.1.3 Association List(alist)

((a . 1) (b . 2) (c . 3))

4.5.1.4 基本操作

add-to-list

将指定的元素添加到列表头部。

(defvar a '(1 2 3 4))
(add-to-list 'a 5) ;; => (5 1 2 3 4)

经验1,对数据做“shadow”:

Org mode导出HTML文件时,如果源文件中有Latex表达式,就在HTML中外部引用MathJax这个JavaScript库来展现。引用的URL是外部链接,默认值存储在列表org-html-mathjax-options中。如果想自定义URL,就可用add-to-list向列表头部插入自定义的URL,在导出时会优先使用这个URL:

(add-to-list 'org-html-mathjax-options '(path "https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS_HTML"))

4.5.2 数组(Array)

数组是固定长度的序列。

字符串、向量、字符表(chars table)和布尔向量(bool-vector)都是数组。

4.5.2.1 向量(Vector)
[1 2 3] ; 定义一个向量
4.5.2.2 布尔向量(Bool-vector)

创建一个布尔向量

(make-bool-vector 3 t)
(make-bool-vector 3 nil)

5 函数定义

基本定义:

(defun hello ()
  (message "hello world"))

实质上我们在M-x中执行的命令,都是定义的Emacs Lisp函数。要想让函数能在M-x中执行,需定义成如下:

(defun hello ()
  (interactive)
  (message "hello world"))

便可在M-x输入hello来调用。

6 Buffer

6.1 打印输出

(message "hi")

将会在“*Messages*” Buffer中打印出字符串,所有的打印记录可以到M-x view-echo-area-messages中查看

6.2 在Buffer中插入内容

(insert "hello world") ; insert函数可以在当前Buffer里插入内容

6.3 获得当前Buffer名字

mode-name变量存储了当前mode的名字,按下M-x eval-expression,输入mode-name即可。

6.4 切换Buffer

switch-to-buffer

不仅可以切换到已有的Buffer,还可以新建Buffer。下面这句代码将切换到一个叫“test”新的Buffer:

(switch-to-buffer "test")

6.5 with-current-buffer

获得某个Buffer的内容:

(with-current-buffer "a-buffer"
  (buffer-string))

按行处理Buffer内容

(dolist (line (split-string (with-current-buffer "a-buffer"
			      (buffer-string))))
  (print line))

7 钩子(Hook)

Emacs中的钩子是一种在一定情况下才会触发的机制。

7.1 add-hook

让Emacs在进入某个模式(Mode)的时候才执行,如:

(add-hook 'text-mode-hook
	    (message "Welcome to Text mode"))

当Emacs进入text-mode的时候,就会提示“Welcome to Text mode”。如果要执行多条语句,可以用lambda或者progn,按李杀的说法,应该尽量避免在add-hook使用lambda,因为这样无法remove-hook。请见:http://ergoemacs.org/emacs/emacs_avoid_lambda_in_hook.html

8 绑定键

8.1 kbd函数

kbd函数将字符串形式的快捷键描述转换成Emacs内部的表示法,如:

(kbd "C-c C-c") ; 表示两次连按“Ctrl+c”

8.2 global-set-key

将某个函数绑定至指定的快捷键

(defun say-hello ()
  (interactive)
  (message "hello world"))

;; 将快捷键C-c h绑定到了say-hello函数上,当按下组合键时,会调用say-hello函数
(global-set-key (kbd "C-c h") 'say-hello)

8.3 查看快捷键绑定的函数

如果要查看某组快捷键绑定在哪个函数上,可以执行describe-key或者使用快捷键C-h k。

比如想查看C-b绑定在哪个函数上,首先按下C-h k,然后Emacs会提示你按下要查看的快捷键,最后会打开一个临时Buffer,显示相关信息。

9 调试与测试

9.1 调试

使用自带的edebug。

9.2 单元测试

使用自带的ERT。

9.2.1 定义一个测试用例

(ert-deftest test ()
  (should (= 2 (+ 1 1))))

M-x ert,运行测试用例。ERT会输出测试用例执行的结果:

Selector: test

Passed: 1

Failed: 0

Skipped: 0

Total: 1/1

Started at: 2015-01-25 11:40:02+0800

Finished.

Finished at: 2015-01-25 11:40:02+0800

.

详细请见:https://www.gnu.org/software/emacs/manual/html_node/ert/index.html