Emacs Lisp 基础

eval 代码

1
(+ 3 4)

方法有:

  1. 将光标移到闭括号外, 然后调用 eval-last-sexp (C-x C-e) , 然后 emacs 就会计算与该右括号匹配的左括号及之间的表达式了.

  2. 你也可以选择某一段代码, 然后 eval-region (按 Alt+x 然后输入该命令.)

  3. Alt+x ielm , 它会开启一个交互式的 elisp 命令行窗口.

查看函数的文档: Alt+x describe-function (C-h f)

打印

1
2
3
(message "hi")

(message "hi, %s" "emacs")

它将输出到一个名为 *Messages* 的 buffer 中. 你可以通过 Alt+x view-echo-area-messages (C-h e) 来切换到该 buffer.

算术运算

1
2
3
(+ 4 5 1)
(% 7 4)
...

注意, 2. 表示的是整数, 2.0 才是浮点数.

断言函数. 有些函数名是以 p 结尾的, 常常表示它会返回 true 或者 false . t 表示是 true, nil 表示是 false

1
2
(integerp 3.) ; t
(floatp 3.0) ; t

string 与 number 的转换

1
2
(string-to-number "3")
(number-to-string 3)

true, false

在 elisp 中, nil 表示是 false , 其他所有的都会被视为 true . 注意, nil 是等同于空表 () ,所以 () 也表示 false

为了方便, t 是用作 true .

布尔函数

and 和 or

1
2
3
(and t nil)

(or t nil)

比较数字

1
2
3
4
5
(< 3 4)

(> 3 4)

(/= 3 4) ; t  /= 是不等于符号.

(= 3 3)

字符串比较

1
2
3
4
5
(equal "abc" "abc") ;t

(string-equal "abc" "abc") ;t

(string-equal "abc" 'abc) ;t

equal 是通用的检测, 如果两个操作数是相同的类型并且是相同的值, 才为 true , 否则为 false .

变量

全局变量

1
2
3
(setq x 1)

(setq a 3 b 3 c 7); a=3, b=3, c=7

局部变量

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
(let (var1 var2 ..) body)
(let (a b)
	(setq a 3)
    (setq b 4)
    (+ a b)
); returns 7



(let ((var varValue) (var2 var2Value)...) body)

(let ((a 3) (b 4))
	(+ a b)
); returns 7

if then else

1
2
3
4
5
6
7
(if test body)

(if test true_body false_body)

例如

(if  (< 3 2) 7 8)

表达式块

1
2
3
4
(progn (message "a") (message "b"))
//等同于

(message "a") (message "b")

(progn ...) 是类似于 C 风格的 {...}

progn 的返回值是最后一个表达式的值.

循环

1
2
3
4
5
6
7
8
9
(while test body)

例如

(setq x 0)
(while (< x 4)
	(print (format "number is %d" x))
    (setq x (1+ x))
)

定义一个函数(function)

1
(defun function_name (p1 p2 ...) "doc_string" body)

定义一个命令(command)

command 表示的是用户可以通过 execute-extended-command 即 (Alt+x) 来调用的.

当一个函数也是一条命令时, 我们称该函数是一个可交互式使用的.

为了使函数可以交互式使用, 可以添加 (interactive)doc_string 之后的右边即可. 例如:

1
2
3
4
(defun yay ()
  "Insert “Yay!” at cursor position."
  (interactive)
  (insert "Yay!"))

以下是一个定义的模板

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
(defun myCommand ()
  "One sentence summary of what this command do.

More detailed documentation here."
  (interactive)
  (let (localVar1 localVar2 )
    ; do something here …
    ; …
    ; last expression is returned
  )
)

Emacs Lisp 的文本处理

光标位置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
(point) ; 返回当前光标的位置

(region-beginning) ;
(region-end) ;

(line-beginning-position)
(line-end-position)


(point-min)
(point-max)

移动光标以及搜索

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
(goto-char 39)

(forward-char 4)
(backward-char 4)

; 文本搜索
(search-forward "some")
(search-backward "some")

; 正则搜索
(re-search-forward "[0-9]")
(re-search-backward "[0-9]")

(skip-chars-forward "a-z")
(skip-chars-backward "a-z")

删除/插入/修改

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
(delete-char 9)
(delete-region 3 10)

(insert "hello")

;获取从光标位置 71~300 之间的字符串
(setq x (buffer-substring 71 300))


; 将位置  71~3000 之间的字符串转换为大写
(capitalize-region 71 300)

Strings

1
2
3
4
5
(length "abc")

(substring "abcdefg" 3 4)

(replace-regexp-in-string "[0-9]" "X" "abc123");; returns abcXXX

Buffers

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
(buffer-name)

(buffer-file-name)

;切换到名为 xyz 的 buffer
(set-buffer "xyz")

(save-buffer)

(kill-buffer "xyz")

; 临时设置一个 buffer 为当前 buffer 来工作.
(with-current-buffer "xyz"
	;; do something
)

文件 files

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
; 打开一个文件
(find-file "~/")

; 等同 save as
(write-file path)

; 将文件的内容插入到当前位置
(insert-file-contents path)

; 追加一个文本块到文件
(append-to-file start-pos end-pos path)

(rename-file file-name new-name)

(copy-file old-name new-name)

(delete-file file-name)

; dir path
(file-name-directory full-path)

; filename part
(file-name-nondirectory full-path)

(file-name-extension file-name)

(file-name-sans-extension file-name)

以下是一个插入p 标签的例子

1
2
3
4
5
(defun insert-p-tag ()
  "Insert <p></p> at cursor point."
  (interactive)
  (insert "<p></p>")
  (backward-char 4))

基本函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
; 返回当前光标所在的单词内容
(thing-at-point 'word)

; 返回当前光标所在的单词带有连字符或下划线的内容
(thing-at-point 'symbol)

; 返回当前光标所在的行内容
(thing-at-point 'line)

; 返回当前光标所在单词的开始和结束位置
(bounds-of-thing-at-point 'word)

shell 中执行 script

1
emacs --script process_log.el

获取命令行的参数

命令行参数会保存在一个内部 elisp 变量 argv 中.它是一个 list, 每一项都是命令行中的item.

1
2
3
4
(message "argv 0: %s" (elt argv 0)) ; %s is for string
(message "argv 1: %s" (elt argv 1))
(message "argv 2: %s" (elt argv 2))
(message "argv 3: %s" (elt argv 3))
1
2
3
4
5
6
emacs --script test.el uni 2 -tri

argv 0: "uni"
argv 1: "2"
argv 2: "-tri"
argv 3: nil

Elisp 数据结构

elips 中最重要的类似 list 的数据类型:

sequence (并不是一个实际的数据类型) ,又为为 List 和 Array (并不是实际的数据类型)

List 又分为 (Associate List 和 Property List)

Array 又分为(Vector 和 String)

vector

类似 Java 的数组

  1. 读写元素是常量时间
  2. 元素可以是任意类型
  3. 元素不能被添加或删除. 即 vector 的长度是固定的.
  4. 元素的值可以被修改

创建 vector

1
2
3
4
5
6
(setq v (vector 3 4 5))

(setq x 7)
(setq v (vector 3 x 5))

v ; [3 7 5]

如果你不想这些元素被 eval , 则可以写成:

1
2
3
4
5
6
7
(setq x 7)

;; create a vector of elements 3 x 5
;; each element is NOT be evaluated
(setq v [3 x 5])

v ; [3 x 5] . the x remains a symbol, not 7

访问

1
2
3
(aref array n) ;返回 array 数组在索引为 n 的元素

(elt sequence n) ;返回 sequence 在索引为 n 的元素.

emacs 文档中的 sequence , 你可以认为是 listvector

例如

1
2
3
(aref ["a" "b" "c"] 0) ; ⇒ "a"

(elt ["a" "b" "c"] 0) ; ⇒ "a"

elt 是更通用的用法, 它可以同时用于 vector 和 list.

修改

(aset ARRAY IDX NEWELT) , 返回值是 NEWELT

1
2
3
(setq v [3 4 5])
(aset v 0 "b")
v  ; ⇒ ["b" 4 5]

嵌套 vector

1
[[1 2] [3 4]]

join vector, 转换 list 为 vector

1
2
3
4
5
(vconcat [3 4] ["a" "b"]) ; ⇒ [3 4 "a" "b"]

(vconcat [3 4] '("a" "b")) ; ⇒ [3 4 "a" "b"]

(vconcat [3 4] "ab") ; ⇒ [3 4 97 98]

转换 vector 为 list

(append sequence1 sequence2 …)

1
2
3
(append [1 2 3] [4 5] nil)
;; (1 2 3 4 5)
;; proper list

list

创建 list

(list a b …)

1
2
3
(setq mylist (list 1 "b" 3))

(message "%S" mylist)

如果你不想元素被进行 eval , 可以这样子写

'(a b …) 它等同于 (quote (list a b …))

1
2
3
4
5
; assign a list to a var
(setq mylist '(a b c))

; prints a list
(message "%S" mylist)

数字的 list

1
2
3
4
5
(number-sequence n m step)

(number-sequence n m) 

(number-sequence n)

长度

1
(length list)

获取 list 的元素

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
(car list)	;first element

(nth n list)	;nth element

(car (last list))	;last element

(cdr list)	;2nd to last elements.

(nthcdr n list)	;nth to last elements.

(butlast list n)	;without the last n elements.

向前追加 list

1
(cons x list)	;Return a new list, with x added to front. (prepend)

向后追加 list

1
(append list1 list2)	;Return a new list, join two lists

修改 list 变量

1
2
3
4
5
(push list) ;Add a element to the front variable. Returns the new list.
(pop list)	;Remove first element from the variable. Returns the removed element.
(nbutlast list n)	;Remove last n elements from the variable. Returns the new value of the variable.
(setcar list x)	;replaces the first element in list with x. Returns x.
(setcdr list x)	;replaces the rest of elements in list with x. Returns x.
1
2
3
(setq mylist '("a" "b" "c"))
(pop mylist)   ; "a"
(print mylist) ; ("b" "c")

list 转换为 string

1
2
3
4
5
6
7
8
(mapconcat 'identity '("ab" "cd" "ef") ",")
;; "ab,cd,ef"

(format "%s" '(1 "two" 3))
;; "(1 two 3)"

(substring (format "%s" '(1 "two" 3)) 1 -1)
;; "1 two 3"

Associate List

这是一个 cons 对, 类似 (cons key value) . alist

(cons a b) 也可以写为 (a . b)

key, value 都可以是任意类型.

可以有重复的 key , 因为它是按顺序来维护的.

创建

1
2
3
4
(setq x
      '(("mary" . 23)
        ("john" . 24)
        ("smith" . 33)))

通过 key 获取键值对

(assoc key alist)

1
2
3
4
5
6
7
8
(setq x
      '(("mary" . 23)
        ("john" . 24)
        ("smith" . 33)))

;; get the pair with key "john"
(assoc "john" x)
; returns ("john" . 24)

获取了 cons pair 后, 就可以使用 car 来获取第一个元素 (key) , cdr 来获取最后一个元素 (value) 了.

1
2
3
4
5
6
7
8
(setq x
      '(("mary" . 23)
        ("john" . 24)
        ("smith" . 33)))

;; get the value with key "john"
(cdr (assoc "john" x))
; returns 24

通过 value 来获取键值对

(rassq value alist)

1
2
3
4
5
6
7
8
(setq x
      '(("mary" . 23)
        ("john" . 24)
        ("smith" . 33)))

;; get the pair with value 24
(rassoc 24 x)
; returns ("john" . 24)

Property List

'(key1 val1 key2 val2 …)

它通常用于小于50个元素的键值对.它与 alist 的函数一样. 如果超过100个元素, 则建议使用 alist.

访问

1
2
3
4
(plist-get '(x 1 y 2) 'y) ; 2

;; non existent key returns nil
(plist-get '(x 1 y 2) 'b) ; nil

设置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
(setq xx '(a 1 b 2))

;; set value to a existing key
(setq xx
      (plist-put xx 'b 3))
;; must use setq, because plist-put works by return value

(plist-get xx 'b) ; 3

;; set value to new key
(setq xx
      (plist-put xx 'd 3))

(plist-get xx 'd) ; 3

检测是否存在

1
2
3
4
(setq xx '(a 1 b 2))

;; check if a key exist
(plist-member xx 'b)

Symbol Property List

设置

1
2
3
(put 'ff 'xx 5)

(setplist 'ff '(a 1 b 2))

访问

1
2
3
(get 'ff 'xx) ; nil

(symbol-plist 'ff )

Map

mapcar

(mapcar FUNCTION SEQUENCE)

它会将函数 FUNCTION 应用到 SEQUENCE 中的每一个元素, 然后返回一个 list 的结果.

1
2
3
(mapcar '1+ [3 4 5] ) ; (4 5 6)

(mapcar '1+ '(3 4 5)) ; (4 5 6)

函数为 lambda

1
2
3
(mapcar
 (lambda (x) (elt x 0))
 [[1 2] [3 4]] ) ; ⇒ (1 3)

mapc

类似 mapcar ,但返回值是 nil

loop list

dolist

(dolist (VAR LIST) BODY)

循环处理 LISP , VAR 表示 LISP 的元素, 在 BODY 里应用处理. 返回值是 nil

1
2
3
4
(let (
      (xlist (number-sequence 97 122)) ;; list 97 to 122
      )
  (dolist (n xlist) (insert n)))

dotimes

(dotimes (VAR COUNT) BODY …)

返回值是 nil . VAR 表示的是从 0 到 count 之间的步进数.(包括0, 但<count)

1
2
3
(dotimes (i 4)
  (insert (number-to-string i)))
;; inserts "0123", returns nil

(dotimes (VAR COUNT RESULT) BODY …)

通过计算 RESULT 来获得获取值

1
2
3
4
5
(let ((v [3 4 5]))
  (dotimes (i (length v))
    (insert
     (number-to-string
      (elt v i))))) ; inserts 345

while

1
2
3
4
(let ((mylist '(a b c)))
  (while mylist
    (message "%s" (pop mylist))
    (sleep-for 1)))

过滤 list

更多过滤函数

在 25.1 及之后版本

依赖: (require 'seq)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
(require 'seq)

(setq xx '(1 "a" 2))

;; remove items that's not a number
(seq-filter 'numberp xx)
;; (1 2)

;; old remain unchanged
xx
;; '(1 "a" 2)

Hash Table

创建

(make-hash-table :test 'equal) 它会返回一个新的 hashtable

1
(setq myHash (make-hash-table :test 'equal))

添加

1
(puthash "joe" "19" myHash)

删除

1
(remhash "liz" myHash)

获取 key 的值

1
(gethash "jane" myHash)

计算长度

1
(hash-table-count myHash)

删除所有元素

1
(clrhash myHash)

检测元素是否存在

1
(gethash myKey myHash)

如果存在, 则返回值, 否则返回 nil.

Lisp Symbol

Symbol 的概念类似于其他编程语言的标识符, 除了:

  • lisp symbol 可以持有未eval过的, 只是表示自身的, 一个内部名字. 在这种意义下, 它类似于 string.
  • lisp symbol 可以存储超过一个的值.

symbol 的 cell

每一个 lisp symbol 都拥有以下的 cell 来存储东西

  • “print name” cell : 一个字符串, 与 symbol 字面值一样. 它是自动设置的, 不能被更改.
  • “value” cell: 存储 symbol 的值, 当该 cell 不为空时, 该 symbol 被视为一个变量.
  • “function” cell: 存储函数定义的对象, Lisp 宏 macros, 或其他可以像 function 那样执行的对象.
  • “property list” cell: 持有一个 name/value 对的list. 用于存储 symbol 的元数据, 例如: 函数的状态, 字体规则(用于语法高亮), 是否废弃等.

function cellvalue cell 可能为空, 这时就被称为 void . 当你获取一个 cellvaluevoid 时, 会产生一个 lisp 错误. (一个空emtpy cell, 不同于有一个 value 为 nil 的 cell).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
;; get symbol's name cell value
(symbol-name 'sin) ; "sin"

;; get symbol's value cell value
(symbol-value 'sin) ; void-variable error
;; because the value cell of the symbol sin is void
;; or, we just say that sin isn't a variable

;; get symbol's function cell value
(symbol-function 'sin) ;#<subr sin>
;; the value is a primitive function (written in C), and has print form of #<subr sin>

;; get symbol's property list cell value
(symbol-plist 'sin) ; (side-effect-free t)

quoting symbol

一个 symbol , 典型情况下是用于计算来获取它的 value 的. 但你可以通过 quoting symbol 来停止这样子. 例如 (quote x) . 你可以想象 quote拥有 evaluation (仅仅持有它, 而不要去计算它).

什么时候需要 quote symbol

一些函数, 它会自动为你进行 quote 参数. 但这并没有语法上的方式来告诉你, 一个函数是否需要它的参数是 quote 的. 最好就是查看函数的文档 (Alt+x describe-function) 来查看一个参数是否需要 quote .

函数 setsetq 几乎是一样的, 最主要的不同是 set 并不会自动将第一个参数进行 quote, 而 setq 会.

例如

1
2
3
(setq f '1+)
(setq f 'cos)
(setq f 'sqrt)

我们用 f 来包装, 因为我们不确定到底使用哪个函数, 直到运行时才能知道.

然后

1
2
3
4
5
6
7
8
;; here's our data
(setq mylist '(1 2 3))

;; normally, when using mapcar, we want first arg quoted
(mapcar '1+ mylist) ; (2 3 4)

;; here, we don't want first arg quoted
(mapcar f mylist) ; (1.0 1.4142135623730951 1.7320508075688772)

检测个值是否是 symbol

1
2
3
4
5
6
(setq x1 123 )

(symbolp x1) ; nil
;; nil, because x1 is evaluated, and that value is 123, not a symbol

(symbolp 'x1) ; t

获取 symbol 的四个 cell 值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
;; get symbol's name cell value
(symbol-name 'sin) ; "sin"

;; get symbol's value cell value
(symbol-value 'sin) ; void-variable error
;; because the value cell of the symbol sin is void
;; or, we just say that sin isn't a variable

;; get symbol's function cell value
(symbol-function 'sin) ;#<subr sin>
;; the value is a primitive function (written in C), and has print form of #<subr sin>

;; get symbol's property list cell value
(symbol-plist 'sin) ; (side-effect-free t)

设置 symbol 的四个 cell 值

名字是不能修改的.

设置 value cell

1
2
3
4
5
;; set a symbol's value cell
(setq y "yes yes")

;; get it
(symbol-value 'y) ; "yes yes"

检测 value cell 是否为空

1
2
3
4
5
(boundp 'h) ; nil

(setq h 4)

(boundp 'h) ; t

设置 function cell

1
2
3
4
5
6
;; a function that returns 4
(defun z () 4)
;; Note: return value of defun is not defined

;; get a symbol's function cell value
(symbol-function 'z) ; (lambda nil 4)(boundp 'h) ; nil

检测是否是 function

1
2
3
4
5
6
7
(fboundp 'f) ; nil

;; define a function that return 3
(defun f () 3)

;; now the fuction cell is filled
(fboundp 'f) ; t

设置 property cell

参见上面的 Property List

综合例子

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
(setq x "hello world")

(defun x (a b)
  "a + b"
  (+ a b)
  )

(x 3 4);7
(message x);"hello world"

(symbol-name 'x);"x"
(symbol-value 'x);"hello world"
(symbol-function 'x); (lambda (a b) "a + b" (+ a b))
(symbol-plist 'x); (group-documentation "The X Window system.")

参考

http://ergoemacs.org/emacs/elisp_basics.html