Emacs Lisp 编程
Table of Contents
1. 开发环境
ELisp 代码通常保存在 .el 为后缀的文件中,也可切换到“*scratch*” buffer 里编代码。
当处于 lisp-mode 时,光标移到 Lisp 表达式尾部:
- 按 C-x C-e 执行 Lisp 表达式。
- 按 C-j 执行 Lisp 表达式,并插入返回值在当前 buffer 中。
还有一些工具帮助你开发 Emacs Lisp 程序:
- paredit,自行安装,可用于补全括号等操作。
- ielm,Emacs 自带的 Emacs Lisp 交互式环境,M-x ielm。
Emacs Lisp 的大多函数都有说明文字,调用 documentation 函数可以查看对应的文档,如查看 message 的说明:
(documentation 'message)
当然,更常用的是使用快捷键 C-h f 或 M-x describe-function。
2. 基础
; 这是注释 ; 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") 'hello ; 表示返回 hello 这个符号,等同于 (quote hello)
3. 基本类型
;; 通过 type-of 函数获得对象的类型: (type-of 1) ; => integer (type-of "test") ; => string (type-of 1.) ; => integer (type-of 1.0) ; => float (type-of 'a) ; => symbol ;; 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",获得符号的名字 ;; t 为真,nil 和 () 为假,除此之外都为真,并且 nil 和 () 是相等的。 ;; 术语“谓词函数”指函数返回值是 t 或者 nil,这类函数名一般都以“p”结尾,比如 booleanp,用来判断对象是不是布尔类型: (booleanp t) ; => t (booleanp nil) ; => t (booleanp 1) ; => nil (booleanp '()) ; => t ;; 字符串由双引号包围 "hello world" ;; 实质上字符串由不可变的字符序列组成——用字符组成的数组。所以可以使用操作数组的函数操作字符串。如"hello"表示字符串 hello,而这个字符串由“h”、“e”、“l”、“l”和“o”这几个字符组成。 ;; 可以使用 string 函数手动创建字符串: (string ?\h ?\i) ; => "hi" ;; 创建重复 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" ;; 在 Emacs Lisp中,运算符其实也是函数: (+ 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,次方 (lsh 2 2) ; => 8,左移操作 (lsh 4 -2) ; => 1,右移操作 ;; 整数 1 1. ; 类似1 -1 ; 负数表示 +1 ;; 数字和字符串互转 (string-to-number "10") ; => 10 (number-to-string 10) ; => "10" ;; 判断是否是整数 (integerp 1) ; => t (integerp 1.0) ; => nil ;; 除此之外还有浮点数: +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 ;; Emacs Lisp 有列表(list)和数组(array)两种序列,一个 list 就是一组 cons cell 组成,一个 cons cell 包含两个元素,car 函数取第一个,cdr 取第二个,如: (defvar a-cons-cell (cons 1 2)) (car a-cons-cell) ; => 1 (cdr a-cons-cell) ; => 2 ;; dotted pair: (1 . 2) ;; 表示 car 是 1,cdr 是 2。 ;; association list(alist): ((a . 1) (b . 2) (c . 3)) ;; 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")) ;; 数组(array),固定长度的序列,字符串、向量、字符表(chars table)和布尔向量(bool-vector)都是数组。 ;; 向量(vector)表示如下: [1 2 3] ;; 布尔向量(bool-vector): (make-bool-vector 3 t) (make-bool-vector 3 nil)
4. 循环
;; do,基本语法: (do ((变量名 初始值 [累加])) (结束条件) body) ;; 例,输出 1 ~ 10: (do ((i 0 (+ i 1))) ((> i 10)) (princ i))
5. 函数
;; 函数用 defun 定义,如: (defun hello () (message "hello world")) ;; 实质上我们在 M-x 中执行的命令,都是定义的 Emacs Lisp 函数。要想让函数能在 M-x 中执行,需定义成如下: (defun hello () (interactive) (message "hello world")) ;; 便可在 M-x 输入 hello 来调用。
6. Buffer
;; 打印输出 ;; 调用 message 将会在“*Messages*” Buffer 中打印出字符串,所有的打印记录可以到 M-x view-echo-area-messages 中查看: (message "hi") ;; insert 函数可以在当前 buffer 里插入内容 (insert "hello world") ;; mode-name 变量存储了当前 mode 的名字,按下 M-x eval-expression,输入 mode-name 即可。 ;; switch-to-buffer,切换 buffer ;; 不仅可以切换到已有的 buffer,还可以新建 buffer。下面这句代码将切换到一个叫“test”新的 buffer: (switch-to-buffer "test") ;; 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 中的钩子是一种在一定情况下才会触发的机制。 ;; add-hook,使 Emacs 在进入某个模式的时候才执行,如: (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. 绑定键
;; kbd,将字符串形式的快捷键描述转换成 Emacs 内部的表示法: (kbd "C-c C-c") ; 表示两次连按“Ctrl+c” ;; 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) ;; 如果要查看某组快捷键绑定在哪个函数上,可以执行 describe-key 或者使用快捷键 C-h k。 ;; 比如想查看 C-b 绑定在哪个函数上,首先按下 C-h k,然后 Emacs 会提示你按下要查看的快捷键,最后会打开一个临时 buffer,显示相关信息。
9. 系统编程
;; 执行系统命令: (call-process "/bin/uname" nil t nil) ;; shell-command,执行 shell: (shell-command "uname") ;; shell-command-to-string,获得命令执行的输出: (let ((output (shell-command-to-string "uname"))) (message output)) ;; 在使用 shell-command 系列函数时,一定要注意参数是否可控,如果没严格限制输入内容,就能通过构造非法的 shell 语法来控制命令的执行。
10. 单元测试
;; 自带的单元测试框架——ERT,详细请见:https://www.gnu.org/software/emacs/manual/html_node/ert/index.html ;; 定义一个测试用例: (ert-deftest test () (should (= 2 (+ 1 1)))) ;; 通过快捷键 M-x 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 ;; .
11. 调试
使用自带的 edebug。