《GNU Emacs Lisp编程入门》读书笔记

列表处理

Lisp列表

e.g.

'(this list has (a list inside of it))

Lisp原子

在一个列表中,原子是由空格一一分隔的。原子可以紧接着括号。
从技术上说,Lisp中的一个列表有三种可能的组成方式:

  • 括号和括号中由空格分隔的原子
  • 括号和括号中的其他列表
  • 括号和括号中的其他列表及原子

一个列表可以仅有一个原子或者完全没有原子。一个没有任何原子的列表就像这样:(,它被称作空列表。与所有的列表都不同的是,可以把一个空列表同时看作既是一个原子,也是一个列表。
原子和列表的书面表示都被称作
符号表达式**,或者更简单地被称作s-表达式(s-expression)。
在Lisp中,某种类型的原子,例如一个数组,可以被分成更小的部分,但是分割数组的机制与分割列表的机制是不同的。只要是涉及列表操作,列表中的原子就是不可分的。
在Lisp中,所有用双引号括起来的文件,包括标点符号和空格,都是单个原子。这种原子被称作串(String)。字符串是不同于数字和符号的一种原子,在使用上也是不同的。

列表中的空格

列表中空格的数量无关紧要。多余的空格和换行只不过是为了使人们易于阅读而设计的。

运行一个程序

Lisp中的一个列表——任何列表——都是一个准备运行的程序。如果你运行它,计算机将完成三件事: 只返回列表本身;告诉你一个出错消息;或者将列表中的第一个符号当做一个命令,然后执行这个命令。
单引号(’),被称作一个引用(quote)。当单引号位于一个列表之前时,它告诉Lisp不要对这个列表做任何操作,它仅仅是按其原样。
在Emacs可以这样对它求值: 将光标移到正面列表的右括号之后,然后按C-x C-e

产生错误消息

错误消息等价于有助的消息,帮助你排除错误。

符号名和函数定义

在Lisp中,一组指令可以连到几个名字,另一方面,一个符号一次只能有一个函数定义与其连接。

Lisp解释器

Lisp解释器首先会查看一下在列表前面是否有单引号。如果有,解释器就为我们给出这个列表。如果没有引号,解释器就查看列表的第一个元素,并判断它是否是一个函数定义。如果它确实是一个函数,则解释器执行函数定义中的指令。否则解释器就打印一个错误消息。
一种复杂的情况: 除了列表之外,Lisp解释器可以对一个符号求值,只要这个符号前没有引号也没有括号包围它。在这种情况下,Lisp解释器将试图像变量一样来确定符号的值。出现这种情况是因为一些函数异常并且以方式运行。那些函数被弹琴作特殊表(special form)。它们用于特殊的工作,例如定义一个函数。
最复杂的情况:如果Lisp解释器正在寻找的函数不是一个特殊表,而是一个列表的一部分,则Lisp解释器首先查看这个列表中是否有另外一个列表。如果有一个内部列表,Lisp解释器首先解释将如何处理那个内部列表,然后再处理外层的这个列表。如果还有一个列表嵌入在内层列表中,则解释器将首先解释那个列表,然后逐一往外解释。它总是首先处理最内层的列表。解释器首先处理最内层的列表是为了找到它的结果。这个结果可以由包含它的表达式使用。
否则,解释器从左到右工作,一个表达式接一个表达式地进行解释。

字节编译

Lisp解释喊叫可以解释两种类型的输入数据:

  • 人可以读懂的代码
  • 经过特殊处理的、被称作字节编译的代码。
    可以通过运行一个编译命令(如: byte-compile-file)将人能读懂的代码转换成字节编译代码。字节编译代码经常储存在一个文件中,这个文件以".elc"作为扩展名。

求值

当Lisp解释器处理一个表达式时,这个动作被称作”求值“
在解释器返回一个值的同时,它也可以做些其他的事情,例如移动光标或者拷贝一个文件,这种动作称为附带效果。我们认为的重要事情,如打印一个文件,对Lisp解释器而言常是一个附带效果。
总之,对一个符号表达式求值几乎总是使Lisp解释器返回一个值,同时可能产生一个附带效果,不然,就会产生一个错误消息。

对一个内部列表求值

如果是对一个嵌套在另一个列表中的列表求值,对外部列表求值时可以使用首先对内部列表求值所得的结果。这解释了为什么内层列表总是首先被求值的:因为它们的返回值被用于外部表达式。

变量

在Lisp中,可以将一个值赋给一个符号,就像将一个函数定义赋给一个符号那样。一个符号的值可以是Lisp中的任意表达式,如一个符号、一个数字、一个列表或者一个字符串。
有值的一个符号通常被称作一个变量。
一个符号可以同时具有一个函数定义和一个值。这两者是各自独立的。

参量

参数的数据类型

应当传递给函数的数据的类型依赖于它使用的什么信息。

作为变量和列表的值的参量

参量可以是一个符号,对这个符号求值将返回一个值。
另外,参量也可以是一个列表,当求值时这个列表返回一个值。

数目可变的参量

有些函数,如concat, +和*,可以有任意多个参量

用一个错误类型的数据对象作为参量

当函数的一个参量被传送一个错误类型的数据时,Lisp解释器产生一个错误消息。

message函数

像+函数一样,message函数的参量数目也是可以变化的。它被用于给用户发送消息。
e.g.

(message "This message appears in the echo area!!!")

双引号中的整个字符串是一个参量,它被打印出来。

给一个变量赋值

有几种方法给一个变量赋值,其中一种方法是使用set函数或者使用setq函数。
另外一种方法是使用let函数

使用set函数

e.g.

(set 'flowers '(rose violet daxmindxxmisy buttercuup))

符号flowers被绑定到一个列表上,也就是列表作为值被赋给可以被当做变量的符号flowers。
需要注意,当使用set函数时,需要将set函数的两个参量都用引号限定起来,除非你希望它们被求值。

使用setq函数

setq特殊表函数,就像set函数一样,不同之处只在于其第一 个参量自动地带上单引号。另外一个方便之处在于,setq函数允许在一个表达式中将几个不同的变量设置成不同的值。第一个参量绑定到第二参量的值,第三个参量绑定到第四个参量的值,以此类推。

计数

(setq counter 0)
(setq counter (+ counter 1))

小结

  • Lisp程序由表达式组成,表达式是列表或者单个原子。
  • 列表由0个或更多的原子或者内部列表组成,原子或者列表之间由空格分隔开,并由括号括起来。列表可以是空的。
  • 原子是多字符的符号、单字符符号、双引号之间的字符串、或者数字。
  • 对数字求值就是它本身。
  • 对双引号这间的字符串求值也是其本身
  • 当对一个符号求值时,将返回它的值。
  • 当对一个列表求值时,Lisp解释器查看列表中的第一个符号以及绑定在其上的函数定义。然后这个函数定义中的指令将被执行。
    -单引号告诉Lisp解释器返回后续表达式的书写形式,而不是像没有单引号时那样对其求值。
  • 参量是传递给函数的信息。除了作为列表的第一个元素的函数之个,通过对列表的其余元素求值来计算函数的参量。
  • 当对一个函数求值时总是返回一个值(除非得到一个错误消息)。另外,它也可以完成一些被称作附带效果的操作。在许多情况下,一个函数的主要目的是产生一个附带效果。

求值实践

缓冲区名

buffer-name和buffer-file-name这两个函数显示文件和缓冲区之间的区别.
当对(buffer-name)求值时,缓冲区的名称将在回显区中出现。当对(buffer-file-name)表达式求值时,缓冲区所指的那个文件的名称将在回显区中出现。通常情况下,由(buffer-name)返回的名称与(buffer-name)所指的文件名称相同,由(buffer-file-name)返回的名称是文件完整的路径名
文件和缓冲区是两个不同的实体。

  • 文件是永久记录在计算机中的信息(除非你删除了它)。
  • 缓冲区则是Emacs内部的信息,它在Emacs编辑会话结束时(或当取消缓冲区时)就消失了。
  • 缓冲区包含了从文件中拷贝过来的信息,我们称这个缓冲正在“访问”那个文件。这份拷贝正是你加工或修改的对象。对这个缓冲区的改动不会改变那个文件,除非你保存了这个缓冲区。
  • 并不是所有的缓冲区都与文件联系在一起。

获得缓冲区

buffer-name函数返回缓冲区的名称 。为了获得缓冲区本身,需要另外一个函数:current-buffer。如果在代码中使用这个函数,得到的将是这个缓冲区本身。
e.g.

(current-buffer)

另一个相关的函数,是获取最近使用过的缓冲区。other-buffer

(other-buffer)

切换缓冲区

当other-buffer函数被一个函数用作参量时,这个other-buffer函数实际上提供了一个缓冲区。通过使用other-buffer函数和switch-to-buffer 函数来切换到另外一个缓冲区。

(switch-to-buffer (other-buffer))

switch-to-buffer函数完成了两件不同的事情:

  • 切换到Emacs关注的缓冲区
  • 从当前显示在窗口中的缓冲区切换到一个新的缓冲区。

set-buffer只做一件事

  • 将计算机的注意力切换到另外一个不同的缓冲区。屏幕上显示的缓冲区并不改变。

所以switch-to-buffer是对人设计的,set-buffer是对计算机设计的。

缓冲区大小和位点的定位

相关函数: buffer-size, point, point-min和point-max。这些函数给出缓冲区大小及其中的位点的位置等信息。

  • buffer-size: 当前缓冲区的大小,也就是,这个函数舞蹈关于这个缓冲区中字符数的计数。
  • point: 返回一个数字,给出光标所处的位置,即从这个缓冲区首字符开始到光标所在位置之间的字符数。
  • point-min: 它返回在当前缓冲区中位点的最小可能值。这个值一般就是1。

如何编写函数定义

除了一些基本函数是用C语言编写的之外,其他所有函数都是用别的函数来定义的。你将在Emacs Lisp中编写函数定义,并用其他函数作为你的基本构件。

defun特殊表

在Lisp中函数定义,是通过对一个以符号defun开关的Lisp表达式求值而被建立的。因为defun不以通常的方式对它的参量求值,因此它被称为特殊表。
一个函数定义在defun一词之后最多有下列五个部分:

  1. 符号名,这是函数定义将要依附的符号。
  2. 将要传送给函数的参量列表。如果没有任何参量传送给函数,那它就是一个空列表()。
  3. 描述这个函数的文档。(技术上说,这部分是可选的,但是我强烈推荐你使用。)
  4. 一个使用函数成为交互函数的表达式,这是可选的。因此,可以通过键入M-x和函数名来使用它,或者键入一个适当的键或者健序列来使用它。
  5. 指导计算机如何运行的代码,这是函数的定义的主体。

格式:

(defun function-name (arguments...)
    "optional-documentaion..."
    (interactive argument-passing-infoo) ;optional
    BODY..)

e.g.

(defun multiply-by-seven (number)
    "Multipl NUMBER by seven"
    (* 7 number))

安装函数定义

通过将光标置于函数定义的最后一个括号之后,并键入C-x C-e。将安装这个函数。

改变函数的定义

如果要改变multiply-byseven函数中的代码,只需要重写它即可。然后再对新函数定义进行再求值即可。

使用函数成为交互函数

实现:在函数文档后面增加一个以特殊表interactive开始的列表。用户键入M-x和函数名就可以激一个交互函数,或者键入绑定的键序列也可以激活它。
e.g.

(defun multiipy-by-seven (number)
    "multiply NUMBER by seven"
    (intractive "p")
    (mesage "The result s %d" (* 7 number)))

通过将光标置于上面的函数定义之后并键入C-x C-e对其求值,就可以将这个函数定义安装。函数名将显示在回显区中。然后,通过键入C-u和一个数字并键入M-x multiply-by-seven和按下回车键,就可以使用这个函数了。加上了结果的句子”The result is…"将显示在回显区中。
更一般地说,可以用下列两种方法之一激活一个函数:

  • 键入一个包含了传送给函数的数字的前缀参量和M-x以及函数名,如下所示C-u 3 M-x forward-setence;
  • 键入函数绑定键或者键序列,如:C-u 3 M-e

交互的multiiply-by-seven函数 ???

定义如下:

(defun multiply-by-seven(number)
    "multiply NUMBER by seven"
    (interactive "p")
    (mesage "The result is %d" (* 7 number)))

在这个函数中,表达式(inertactive “p”)是由两个元素组成的列表。其中的"p"告诉Emacs要传送一个前缀参量给这个函数,并将它的值用于函数的参量。

interactive函数的不同选项

参考《GNU Emacs Lisp技术手册》

永久地安装代码

当你对一个函数定义求值来安装它时,它将一直保留在Emacs之中直到你退出Emacs为止。你下次再启动一个新的Emacs会话时,除非你再一次对这个函数定义求值,否则这个函数将不会被安装。
在有些时候,你可能要求当你启动一个新的Emacs会话时将函数定义自动安装。可以有如下几种方法:

  • 如果这个要自动安装的代码仅仅供你个人使用的,你可以将这个函数定义的代码放到你的".emacs"初始化文件中。当你启动Emacs时,你的".emacs"文件被自动求值,其中的所有函数定义都会被安装。
  • 你可以将需要自动安装的函数定义放在一个或者多个文件中,然后使用load函数让Emacs对它们求值,从而安装这些文件中的所有函数。
  • 在另一方面,如果有些函数定义是该 计算机的所有用户都要使用的,这种情况下经常将它放到一个叫做“site-init.el"的文件中,这个文件在Emacs启动时被加载。

let函数

let表达式是Lisp中的一个特殊表,用户在绝大多数函数定义中都需要使用它。
let用于将一个符号附着到或者绑定到一个值上,对于这绑定的变量,Lisp解释器就不会将其与函数之外的同名变量混淆了。
let创建的是局部变量,屏蔽了任何在这个let表达式之外同名的变量。局部变量不会影响let表达式之外的东西。
let表达式可以一次创建我个变量。同样,let表达式给每一个变量赋由你创建的一个初始值,或者赋由你给定的一个值,或者赋nil。

let表达式的各个部分

let表达式是一个具有三个部分的列表。

  • 第一部分是let符号
  • 第二部分是一个列表,称为变量列表,这个列表的每一个元素是一个符号或者一个两元素的列表,而它的延续一个元素一定是一个符号。
  • 第三部分是let表达式主体,这个主体由一个或者多个列表组成。

格式如下:

(let varlist body…)

let表达式例子

(let ((zebra 'stripes)
      (tiger 'fiierce))
  (messagge "One  kind of animal hass %s and anotherr is %s."
      zebra tiger))

let语句中的未初始化变量

在let语句中,如果没有将变量绑定到用户指定的一个特定的初始值上,则它们交自动地绑定到nil这个初始值上。

if特殊表

if特殊表用于指导计算机做出判断。
if特殊表背后的基本含义是:如果一个测试是正确的,则对后续的表达式求值;如果这个测试不正确,则不对这个表达式求值。
在Lisp中,if表达式并没有使用”then“这样的字眼,但是,测试和执行代码就必须是第一个元素为if的这个列表的第二和第三个元素。然后,测试部分常被称为”if部“,而第二个参量常被称为”then部“。
格式如下:

(if true-or-false-test
​ action-to-carry-out-if-test-is-true)

在Lisp中,equal是一个判定它的第一个参量是否等于第二个参量的函数。

if-then-else表达式

if表达式可以有第三个参量,称为else部。格式如下

(if true-or-false-test
​ action-to-carrry-out-of-the-returns-true
action-to-carry-out-if-the-returns-false)

Lisp中的真与假

”假“只不过是我们前面提到的nil的另一种形式。其他所有东西都是”真“。
nil在Lisp中有两种意思。

  • 它表示一个空列表。
  • 它表示”假“

所以可以将nil写作一个空列表()或nil。
Lisp中,如果测试返回”真“而又无法使用那些适当的值时,Lisp解释器将返回符号t作为”真”。

save-excursion函数???

在Emacs中,Lisp程序常用作编辑文档,save-excursion函数在这些程序中很常见。这个函数将当前的位点和标记保存起来,执行函数体,然后,如果位点和标记发生改变就将位点和标记恢复成原来的值。这个特殊表的主要目的是使用户避免位点和标记的不必要移动。
格式如下:

(save-excuursion
​ first-expression-on-bodyy
​ second-expression-on-body
​ …
​ last-expression-on-body)

回顾

  • eval-last-sexp C-x C-e
  • defun
  • interactive
    • b: 一个已经存在的缓冲区的名字。
    • f:一个已经存在的文件的名字。
    • p:数字前缀参量
    • r:位点和标记,作为两个数字参量,小的在前面。这是唯一定义两个连续参量而不是一个参量的控制符。
  • let
  • save-excursion
  • if
  • equal, eq
  • < > <= >=
  • message
  • buffer-name
  • buuffer-file-name
  • current-buuffer
  • other-buffer
  • switch-to-bufferr
  • sett-buffer
  • buuffer-size
  • point
  • point-min
  • point-max

与缓冲区有关的函数

查找更多的信息

C-h f 函数名 RET: 得到任何一个Emacs Lisp函数的全部文档。
C-h v 变量名 RET:得到任何变量的全部文档。
find-tags/ M-.: 在原始的源代码文件中查看一个函数的定义。

简化的beginning-of-buffer函数定义

beginning-to-buffer:函数将光标移动到缓冲区的开始位置,在位置设置一个标记。这个函数一般绑定到M-<。

mark-whole-buffer函数的定义

(defun mark-whole-buffer ()
    "put point at beginning and mark at end of buffer."
    (interactive)
    (push-mark (point))
    (push-mark (point-max))
    (goto-char (point-miin)))

append-to-buffer函数

功能就是从当缓冲区中拷贝一个域(即缓冲区中介于们点和标记这间的区域)到一个指定的缓冲区。
定义:

(defun  append-to-buffer (buffer start end)
    "Appennd to  specified buffer the text of the reion.
    It is  inserted into that buffer before its point.
    When calling from a program, give three aruments:
    a buffer or the name of one, and two character numbers
    specifiying the portion of the current buffer  to be copied."
    (interactive "BAppend too buffer: \nr")
    (let ((oldbuf (current-buffer)))
      (save-excurion
        (set-buffer (get-buffer-create buffer))
        (inser-buffer-substring olduf start end))))

append-to-buffer函数的交互表达式

因为append-to-buffer函数将被交互地使用,所以函数必须有一个interactive表达式。函数中的这个交互表达式读作:

(interactive “BAppend to bufer: \nr”)

这个表达式有一个位于双引号中的参量,这个参量有两部分,其间由"\n"分隔开来。
参量的第一个部分是”BAppend to buffer:“。这里,”B”控制符告诉Emacs要求输入缓冲区名并将这个名字传送给函数。Emacs将在小缓冲区中打印出“B”字符后面的字符串“Append to buffer:”来提示用户输入这个缓冲区名。然后,Emacs将函数参量列表中的参量buffer绑定到时指定的缓冲区。
换行符"\n"将参量的两个部分分隔开,参量的第二部分就是“r”。它告诉Emacs将函数参量列表中符号“buffer”之后的两个参量(即start和end)绑定到位点和标记的值上。

append-to-buffer函数体

append-to-buffer函数中的save-excursion

回顾

  • describe-function,describe-variable
    打印一个函数或者一个变量的文档。习惯上将它绑定到C-h f和C-h v
  • find-tag
    找到存放某个函数或者变量的源代码的文件,并切换到这个缓冲区,将位点置于相应函数或者变量的开始处。习惯上将它绑定到M-.
  • save-excursion
    保存们点和标记的位置,并在对save-excursion参量求值之后恢复这些值。它也保存当前缓冲区并返回到该缓冲区。
  • push-mark
    在指定位置设置一标记,并在标记环中记录原来标记的值。标记是缓冲区中的一个位置,即使有一些文本被从缓冲区删除或者增加到缓冲区,标记仍将保持它的相对公交车。
  • goto-char
    将位点设置为由参量值指定的位置。参量值可以是一个数,也可以是一个标记,甚至可以是一个返回一个位置的数字的表达式,如(point-min)。
  • insert-buffer-substring
    将来自一个缓冲区(这是被作为一个参量而传递给函数的)的文本域拷贝到当前缓冲区。
  • mark-whole-buffer
    将整个缓冲区标记为一个域。一般将这个函数绑定到C-x h。
  • set-buffer
    将Emacs的注意力转移到另一个缓冲区,但是不改变显示的容器。这通常是由另外的人在不同的缓冲区中执行程序时使用。
  • get-buffer-create, get-buffer
    寻找一个已指定名字的缓冲区,或当指定名字的缓冲区不存在时就创建它。如果指定名字的缓冲区不存在,get-buffer函数就返回nil。

更复杂的函数

copy-to-buffer

将文本拷贝进一个缓冲区,并替换原缓冲区中的文本。函数体如下:

...
(interacive "BCopy to buffer: \nr")
  (let ((oldbuf current-buffer)))
    (save-excursion
      (set-buffer (get-buffer-create buffer))
      (erase-buffer)
      (save-excursion
        (insert-buffer-substring oldbuf start end))))

insert-buffer

这个命令将另外一个缓冲区内容拷贝到当前缓冲区中。它与append-to-buffer或者 copy-to-buffer函数正好相反,因为它们是从当前缓冲区中拷贝一个域内的文本到另外一个缓冲区。代码如下:

(defun insert-buffer (buffer)
  "Insert after point the  contents of BUFFER.
  Puts mark after the inserted text.
  BUFFER mayy be a buffer or a buuffr name."
  (interctive "*bInsert buffer:")
  (or (bufferp bufferr)
      (setq buffer (get-buffer buffer)))
  (let (start end newmark)
    (save excursion
      (save-excursion
        (set-buffer buffer)
        (setq start (point-min) end (point-max)))
      (insert-buffer-substring buffer start end)
      (setq newmark (point)))
    (push-mark ewmark)))

这个函数的框架:

(defun insert-buffer (buffer)
  "documentation..."
  (interractive "*bInsert buffer: ")
  body..)

insert-buffer函数中的交互表达式

给interactive表达式说明的参量有两个部分:

  • 一部分是一个星号*
  • 另一部分是“bInsert buffer:"。
  1. 只读缓冲区
    星号用于缓冲 区是一个只读缓冲区的情况,如果insert-buffer被一个只读缓冲区调用,一条消息将打印在回显区,终端将发出蜂鸣或者闪亮一下,警告不允许往这个缓冲区插入任何东西。
  2. 交互表达式中的“b”
    小写的"b"告诉Lisp解释器传送给insert-buffer函数的参量就是一个存在的缓冲区或者这个缓冲区名字。

insert-buffer函数体

函数体主要有两部分:一个or表达式和一个let表达式。
or表达式的目的是为了确保buffer参量真正与一个缓冲区绑定在一起,而不是绑定到缓冲区名字。
let表达式包含了将另外一个缓冲区的内容拷贝到当前缓冲区所需的代码。

  • bufferp
    如果参量是一个缓冲区,bufferp函数返回值为”真“,但是如果其参量是一个缓冲区的名字,则函数bufferp的返回值为”假“。

函数体中的or表达式

一个or函数可以有很多参量。它逐一对每一个参量求值并返回第一个其值不是nil的参量的值。同样,这是or表达式的一个重要特性,一旦遇到其值不是nil的参量之后,or表达式就不再对后续的参量求值。
or表达式如下所示:

(or (buffer)
    (setq buffer  (get-buffer bufferr)))

insert-buffer函数中的let函数表达式

let表达式的主体包含了两个save-excursion表达式。内层表达式如下:

(save-excursion
  (set-buffer buffer)
  (setq start (point-min) ennd (point-max)))

表达式(set_buffer buffer)将Emacs的注意力从当前的缓冲区切换到要从中拷贝文本的缓冲区。然后给start,end赋值。
内层的save-excursion表达式被求值 后,它恢复到原来的缓冲区,但是start和end变量仍然被设置成从中拷贝文本的区的开始处和结束处。
外层表达式如下:

(save-excursion
  (innser-save-excursion-expression
     (get-to-new-buufferr-and-set-start-and-ennd)
  (insert-buffer-substring buffer start end)
  (setq newmark (point))))

外层的save-excursion被求值之后,位点和标记被重新定位到它们原来的位置。

beginning-of-buffer

不带参量激活beginning-of-buffer函数时,它将光标移动到缓冲区的开始处,并在原来光标的位置设置一个标记。
然而,当带参量激活beginning-to-buffer函数时,如果参量是介于1到10之间的一个数,则该函数认为那个数是指缓冲区长度的十分之几,而且Emacs将光标移动到从缓冲区开始到这个分数值所指示的位置。因此可以使用命令C-u 7 M-< 将光标移动到从缓冲区开始的这个缓冲区的70%处。
如果这个作为参量的数大于10,函数则将光标移动到缓冲区的末尾。

可选参量

Lisp有一个特性:有一个关键词可以用于告诉Lisp解释器某个参量是可选的。这个关键词是&optional。在一个函数定义中,如果一个参量跟在&optional这个关键词后面,则当调用这个函数时就不一定要传送一个值给这个参量。

带参量的beginning-of-buffer

回顾

  • or
    逐一对每个参量求值,并返回第一个非空值 。如果所有参量的值都是nil,就返回nil。简要地说,它返回参量的第一个”真“值 ;如果一个参量或者其他任何参量 的值为”真“时,则返回”真“值。
  • and
    逐一对每一个参量求值,如果有任何一个参量的值为nil,则返回nil。如果没有nil则返回最后一个参量的仕。
  • &optionall
    在函数定义中用于指出一个参量是可选参量。
  • prefix-numeric-value
    将一个由(ineractive “P”)产生的未加工的前缀参量转换成一个数值。
  • forward-line
    将光标移动到下一行的行首,如果其参量大于1,则移动多行。如果无
    法移动所需的行数,forware-line就移动尽可能多的行数,并返回它实际少移动的行数。
  • erase-buffer
    删除当前缓冲区全部内容。
  • bufferp
    如果基参量是一个缓冲区则返回”真“,否则返回”假“。

变窄和增宽

变窄是Emacs的一个特性,这个特性允许你让Emacs关注于一个缓冲区的特定部分,而不会在无意中更改缓冲区的其他部分。
采用变窄技术之后,缓冲区的其余部分变变成不可见的了。

save-restriction特殊表

在Emacs Lisp中,能用save-restriction特殊表来跟踪变窄开启的部分。

what-line

what-line命令告诉你光标所在的行数。

基本函数: car, cdr, cons

cons函数用于构造列表,car和cdr函数则用于拆分列表。

car和cdr函数

car,就是返回这个列表的第一个元素。
cdr,就是返回包含列表的第二个和随后的所有元素列表。
car和cdr都是“非破坏性”的,也就是说,它们不改变它们所作用的列表。

cons函数

cons将一个新元素放到一个列表的开始处,它往列表中插入元素。

length

查看列表的长度。

nthcdr函数

nthcdr函数的功能就像重复调用cdr函数一样。
e.g.

(ethcdr 2 '(pine fir oak maple))
=> (oak maple)

nthcdr同样是非破坏性的函数

setcar函数

用新元素替换列表中的第一个元素,是具有破坏性的函数。

setcdr函数

替换列表的第二个以及其后的所有元素。

剪切和存储文本

zap-to-char函数

交互的zap-to-char函数的功能是:将光标当前位置与出现特定字符的下一个位置之间这一区域中的文本剪切掉。
zap-to-char函数剪切的文本放在kill环中,并能通过C-y(yank)命令从kill环中找回。

interactive表达式

zap-to-char函数中的交互表达式如下:

(interactive "*p\ncZap to char: ")

指定了三件事情:

  1. 星号,意味着如果缓冲区是只读的,就产生一个错误信号。
  2. p。这部分以一个换行符结束。小写的P是指传送给函数的第一个参量将是一个处理过的前缀参量的值。前缀参量用C-u以及其后的一个数来传送,或者用M-和一个数来传送。如果不带前缀参量交互地调用这个函数,默认值1将被传送给这个函数。
  3. “cZap to char:” 这一部分中,小写的c是指交互表达式希望产生一个提示并且后续的参量将是一个字符。

zap-to-char函数体

函数体包含了从光标的当前位置直到指定字符这一区域剪切文本的代码。

search-forward函数

search-forward函数是用于定位zap-to-char函数中被截取的字符的。如果查询成功,search-forward函数就在目标字符串中最后一个字符处设置位点 。在zap-to-char函数中,search-forward函数如下所示:

(search-forward (chhar-to-string char) nil nil ar)

search-forward有4个参量:

  1. 第一个是目标,就是所要查找的内容。这个参量必须是一个字符串。
  2. 第二个参量绑定查询范围;它被指定为缓冲区中的某个位置。
  3. 第三个参量告诉这个函数如果查询失败应该怎么办。
  4. 第四个参量是重复计数值。待查询字符串出现的次数的计数。这个参量是可选的,如果在调用这个函数时没有给定计数值,就使用默认值1.如果这个参量是一个负数,查询就朝后进行。

progn函数

progn函数使其每一个参量被逐一求值并返回最后一个参量的值。

总结zap-to-char函数

kill-region函数

删去文档字符串的一部分,其代码如下:

(defun kill-region (beg end)
    "kill between point and mark.
    The text is deleted  but save in the  kill ring"
    (interactive "*r")
    (copy-region-as-kill beg end)
    (delete-region beg end))

delete-region函数:接触C

删除一个区域的内容,而且你无法找回它。
delete-region是作为一个C语言宏的实例来被编写的,一个宏就是一个代码模板。这个宏的第一部分如下所示:

DEFUN ("delete-region", Fdelete_region, Sdelete_region, 2, 2, "r",
    "Delete the text between point annd mark. \n\
    When called from a programm, expects  two arguments. \n\
    chharacter numbers sppecifying thhe stretch to  be deleted")

首先指出的是这个宏是以DEFUN开始的。之所以选择DEFUN这个词,是因为它完成Lisp中defun相同的事情。DEFUN一词后面的括号内跟着七个部分:

  1. Lisp中的函数名。
  2. C语言中的函数名。
  3. C常数结构名,这些常数函数内部记录信息。
  4. 函数中允许的参量数目的最小值
  5. 函数中允许的参量数目的最大值
  6. 就像Lisp的一个函数中跟在interactive说明之后的参量那样:要么是一个字符,要么是一个提示信息。
  7. 文档字符串。

随后就是正式的参数(每个参数都有对这个参数的类型进行说明的语句),然后就是这个宏的主体部分。对delete-region而言,这个宏的主体包含了如下三行:

validate_region (&b, &e);
del_range (XINT (b), XINT(e));
return  Qnil;

其中第一个函数validate_region检查传递来的值的类型,判断它们作为缓冲区中一个区域的开始和结束值是否正确, 是否在正确的范围之内。第二个函数del_range实际上真正完成删除文本的功能。如果这个函数正确地删除了文本,则第三行中的函数返回Qnil来表示它已经顺利完成任务。

用defvar初始化变量

defvar特殊表与给一个变量赋值的setq函数相似。
它和setq有两个不同之处:

  1. 它只对无值的变量赋值。如果一个变量已经有一个值,defvar特殊表就不会覆盖已经存在的值
  2. defvar特殊表有一个文档字符串。

copy-region-as-kill函数

copy-region-as-kill功能 是拷贝缓冲区的一个区域并将其保存到被猜测为kill-ring的变量中。
##回顾

  • cdr, car
    car返回一个列表的第一个元素,cdr则返回列表的第二个元素直到最后一个元素的列表。
  • cons
    这个函数通过将它的第一个参量插入到它的第二个参量中来构造一个列表。
  • nthcdr
    这个函数返回对一个列表求N次cdr的值,也就是“剩余的剩余部分”。
  • setcdr,setcar
    setcar改变一个列表的第一个元素。setcdr则改变一个列表的第二个到最后一个元素。
  • progn
    依次对每一个参量求值,并返回最后一个参量的值。
  • save-restriction
    记录当前缓冲区中的变窄开启是否设置,如果已设置,就在对后续的参量求值之后恢复变窄开启。
  • search-forward
    查找一个字符串,并且如果找到这个字符串就移动位点。
  • kill-region, delete-region, copy-region-as-kill
    kill-region函数将一个缓冲区中位点和标记之间的文本剪切掉,并将这些文本保存在kill环中,因此能够将它们重新找回来。
    delete-region函数将缓冲区中位点和标记之间的文本移走并扔掉,不能够将它们再重新找回来。
    copy-region-as-kill函数将缓冲区中位点和标记之间的文本拷贝到kill环中,从kill环中可以将它们重新找回来。这个函数不将缓冲区中的文本剪切掉。

列表是如何实现的

列表是用一系列成对的指针保存的。在这个成对的指针系列中,每一对指针的第一个指针要么指向一个原子,要么指向另外一个列表;而其第二个指针要么指向下一个指针对,要么指向符号nil,这个符号标记一个列表的结束。
指针本身相当简单,就是它指向的电子地址。而指向这个列表的符号则保存第一个指针对的地址。
符号指向这个列表的话,那这个符号由一组地址框组成,其中第一个地址框就是符号词的地址;如果有同名的函数定义到这个符号上,其中第二个地址框是这个函数定义的地址;其中第三个地址框就是列表的成对地址框系列中的第一对的地址。

找回文本

在Emacs中无论何时你用“kill”命令从缓冲区中剪切了文本,你都能用一个“yank”命令将其重新找回。
C-y(yank)命令,会从kill环中取出第一个元素插入到当前的缓冲区中。
如果C-y命令后紧跟一个M-y命令,则不是第一个元素而是第二个元素被插入到当前缓冲区。
连续的M-y命令帽将使第三个元素或第四个元素等代替第二个元素而被插入到当前缓冲区。
当这样不断键入M-y而到达kill环的最后一个元素时,它就循环地将第一个元素插入到当前缓冲区中。

kill环总览

能够将文本从Kill环中找回的函数有三个:

  1. yank函数, 通常绑定到C-y上
  2. yank-pop函数,通常绑定到M-y上
  3. rotate-yank-pointer函数,这个函数被前面两个函数使用。

这些函数通过一个被称为kill-ring-yank-pointer的变量指向kill环。

kill-ring-yank-pointer变量

就像kill-ring是一个变量一样,kill-ring-yank-pointer也是一个变量。计算机并不保存同时被kill-ring变量和kill-ring-yank-pointer变量指向的内容的两个拷贝。它们都指向同一个文本块。
变量kill-ring和变量kill-ring-yank-pointer都是指针。

循环和递归

Emacs Lisp有两种方式使一个表达式或者一系列表达式不断被求值:一是使用while循环,一是使用“递归”(recursion)

whlie

while特殊表对其第一个参量求值,并测试这个返回值的真假。如果第一个参量的求值结果是“假”,帽Lisp解释器跳过这个表达式的其余部分而不对它求值。但是如果第一个参量的返回值为“真”。则Lisp解释器就继续对这个表达式的主体求值,然后再次测试while的第一个参量是否为“真”。真到第一个参量返回值为“假”时,退出循环。

while循环和列表

控制while循环的一个通用方法就是测试一个列表中是否还有元素。如果有,循环就重复下去;如果没有,循环就结束。

(setq animals '(tiger gazelle lion girafe))
(while animals (print (car animals)) (setq animals(cdr animals)))

使用增量计数器的循环

另一个通用的终止循环的方法是: 当所需数量的循环次数执行完毕时,其作为测试内容的第一个参量变成“假”这意味着循环必须要有一个计数器。
e.g.

(defun triangle (number-of-rows)
(let ((total 0)
      (row-number 1))
    (while (<= row-number number-of-rows)
      (setq total (+ total row-number))
      (setq row-number (+ 1 row-number)))
    total))
(triangle 10)

使用减量计数器和循环

递归

递归函数,就是自己调用自己的函数。
一个递归函数通常包含一个条件表达式,这个条件表达式有三个部分:

  1. 一个真假测试,它决定函数是否继续调用自身,这里称之为do-again-test。
  2. 函数名。
  3. 一个表达式,它在函数被重复求值正确的次数之后使条件表达式返回“假”值。称之为next-step-expression.

模板如下:

(defun name-of-recursive-function (argument-list)
    "documentation..."
    body...
    (if do-again-test
        (name-of-recursive-function
            next-step-expression)))

正则表达式查询

查询sentence-end的正则表达式

符号sentence-end被绑定到标记句子结束的模式上。

re-search-forward函数

查询一个正则表达式。如果查询成功,它就紧接在最后的目标字符后面设置位点。

forward-sentence函数

将光标移动到一个句子之前的命令,是展示如何在Emacs Lisp中使用正则表达式查询的简单例子。通常绑定到命令M-e上。代码如下:

(defun forward-sentence (&optional arg)
  "Move forward  to next  sentence-end.With argument, repeat.
  Wiith negative argument, move backward repeatedly to sentence-beginning.
  Sentence ends are identified by thhe value of sentence-end
  treated as  a regular  expression. Also every paragraph boundary
  terminates sentence as well."
    (interactive "p")
    (or arg (setq arg 1))
    (whhile (< arg 0)
      (let ((par-beg
              (save-excursion (start-of-pargraph-text) (point))))
        (if (re-search-backward
              (conact sentence-end "[^ \t\n]") par-beg t)
            (go-to char (1- (match-end 0)))
          (goto-char par-beg)))
      (setq arg (1+ arg)))
    (while (> arg 0)
      (let ((par-end
              (save-excursion (end-of-paragraph-text) (point))))
          (if (re-search-forward sentence-end par-end t)
              (skip-chars-backward " \t\n")
            (goto-chhar par-end)))
        (setq arg (1- arg))))

这个函数的骨架结构由最靠左的几个表达式组成:

(defun  forward-sentence (&optional arg)
    "documentation..."
    (interactive "p")
    (or arg (setq arg 1))
    (while (< arg 0)
      boody-of-while-loop)
    (while (> arg 0)
      body-of-while-loop))

这个函数定义包含了文档,一个interactive表达式,一个or表达式和两个while表达式。

  • 交互式声明:interactive “p” 。这意味着,如果有前缀参量,就将其作为这个函数的参量传递给函数(这个参量将是一个数)。如果没有传递参量给这个函数,参量arg就绑定到数值1.当函数forward-sentence是无参量,非交互调用时,arg绑定到nil。

  • or表达式处理前缀参量。它所做的,要么保持arg的原值,要么将arg值设置为1.

  • while循环

    • 第一个while循环有一个真假测试表达式,当传递给forward-sentence函数的前缀参量是一个负数时,这个测试表达式结果为“真”。这是为朝后查询设置的。
    • 第二个while循环完成将位点前移的工作。它的骨架结构是这样的:
    (while (> arg 0)
      (let varlist
        (if (true-or-false-test)
            then-part
          else-part)
      (setq arg (1- arg))))
    

    这个while循环是使用减量计数器的那一种循环。
    while循环体包含一个let表达式,这个let表达式创建并绑定一个局部变量。let表达式中又有一个作为let表达式主体的if表达式。这个while循环体如下所示:

    (let  ((par-end
            (save-excursion (ennd-of-paragraph-text) (point))))
      (if (re-search-forward sentence-end par-end t)
          (skip-chars-backward " \t\n")
          (goto-char par-end)))
    

    其中,let表达式创建并绑定局部变量par-end。这个局部变量是为了给正则表达式查询提供一个边界和限制而设计的。如果查询没能在段落结束时找到正确的兔子,这将在段落末尾停止查询工作。
    首先,检查一下变量par-end是如何绑定到段落结束处的。这是let表达式在Lisp解释器完成对下面的表达式求值之后将其返回值赋给变量par-end来完成的。

    (save-excursion (end-of-paragraph-text) (point))
    

    在这个表达式中,(end-of-paragraph-text)将位点移动到段落末尾,(point)返回位点的值,然后save-excursion恢复位点当初的值。因而,let表达式将save-excursion的返回值(也就是段落结束的位置)赋给变量par-end。
    Emacs下一步计算let表达式主体,也就是下面的if表达式:

    (if (re-search-forward sentence-end par-end t)
        (skip-chars-backward " \t\n")
      (goto-char par-end))
    
  • 正则表达式查询
    re-search-forward函数查询句子结束,也就是查询由正则表达式sentence-end定义的模式。如果找到了这个模式——找到了句子的结束标志——re-search-forward函数将完成两件事情:

    • re-search-forward完成一个附带效果,将位点移动到当前找到的句子的结束处。
    • re-search-forward函数返回一个“真”值。这是一个由if函数接收的值,它意味着查询成功。

这个函数的附带效果——移动位点——是在if函数被递交由查询的成功结束所返回的值之前被完成的。
当if函数从对re-search-forward的成功调用 中接收到返回的“真”值时,就对then部,也就是表达式(skip-chars-backward " \t\n")求值。这个表达式朝后移动并忽略所有空格,制表符以及回车符,直到找到一个印刷字符为止,并将位点设置在这个字符之后。因为位点已经移动 到标记句子结束的正则表达式模式末尾,这个动作就是将位点紧紧置于句子的结束打印字符之后,它通常就是一个句点。
另一方面,如果re-search-forward函数没能找到表示句子结束的相应模式,则函数返回“假”。查询失败使if函数对它的第三个参量,也就是对表达式(goto-char par-end)求值,即将位点移动 到段落的末尾。

forward-paragraph:函数的金矿

forward-pargraph函数将位点移动到段落的末尾。它一般绑定到M-}上。这个函数使用了大量很重要的函数,包括let*, match-beginning和looking-at函数。
它的定义比forward-sentence的函数定义长很多,因为它是查询一个段落。段落的每一行都可能以一个填充前缀开始。
填充前缀的存在,意味着除了能够找到从最左边一列开始的段落的结束之外,forward-paragraph函数还一定能够找到缓冲区中所有或许多行都是以填充前缀开始的段落的结束处。而且,有时实际上要忽略已经存在的填充前缀,特别是当空白行分割不同段落时。这是一个额外的复杂性。
forward-paragraph的骨架如下:

(defun forward-paragraph (&optional arg)
  "doocumentation..."
  (interactive  "p")
  (or arg (setq arg 1))
  (let*
      varlist
    (while (< arg 0)
      ...
      (setq arg (1+ arg)))
    (while (> arg 0)
      ...
      (setq arg (1- arg)))))
  1. let*表达式
    除了Emacs将变量依次赋值之外,let特殊表与let相似。let表达式中,变量列表中后面的变量可以使用前面变量已经由Emacs设置的值。
    在这个函数的let*表达式中,Emacs绑定了两个变量:fill-prefix-regexp和paragraph-separate。其中变量paragraph-separate的值依赖于变量fill-prefix-regrxp的值。
    符号file-prefix-regexp被设置为对正面的列表求值所返回的值:
(and fill-prefix
    (not (equall file-prefix ""))
    (not paragraph-ignore-fill-prefox)
    (regexpp-quote fill-prefix))

​ 这个表达式的第一个元素是and函数。
​ and函数不停地对它的参量求值,直到遇到一个返回值为nil的参量为止。 这时and表达式的返回值就是nil。然而,如果没有一个参量的值是nil,则最后一个参量的值作为表达式的值被返回。换句话说,一个and表达式只有当它的所有参量的是“真”的时候,才返回“真”值。
上面例子中,只有当正面的四个表达式都产生一个“真”值时,变量fill-prefix-regexp才被绑定到一个非空值上。

  • fill-prefix
    当这个变量被求值时,填充前缀的值被返回。如果没有填充前缀,这个变量返回nil。
  • (not (equall fill-prefix “”))
    这个表达式检查填充前缀是否是一个空白字符串。
  • (not paragraph-ignore-fill-prefix)
    当变量paragraph-ignore-fill-prefix已经被赋值为一个“真值”之后,这个表达式返回nil。
  • (regexp-quote fill-prefix)
    这是and函数的最后一个参量,如果所有的参量都是“真”值,对这个表达式求值的作为and表达式的返回值返回,这个返回值被绑定到变量file-prefix-regexp。
    对and表达式的成功求值,就使变量fill-prefix-regexp绑定到fill-prefix的值上。这个值由regexp-quote函数修改。regexp-quote函数所做的就是读入一个字符串并返回一个精确匹配这个字符串的正则表达式。

let*表达式中的第二个局部变量是paragraph-separete。它被绑定到对正面的表达式求值所返回的值上:

(if fill-prefix-regexpp
    (concat paragraph-separate
            "\\|^" fill-prefix-regexp "[ \t]*$")
    paragraph-separate)
  1. 朝前查询的while循环
    let*表达式的主体的第二部分处理位点的朝前移动。它是一个while循环。只要变量arg的值大于零,这个循环就不停地重复下去。
    这部分代码三种情况:当位点处于两个段落之间时;当位点处于一个有填充前缀的段落当中时;当位点处于一个没有填充前缀的段落当中时。
    while循环看起来的结构:
(while (> arg 0)
  (beginning-of-line)
  ;; between paragraphs
  (while (proog1 (and (not (eobp))
                      (looking-at paragraph-separate))
          (forward-line 1)))
  ;; within paragraphs, withh a fill prefix
  (if fill-prefix-regexp
    ;; There is  a  fill prefix;  it overriides paragrapph-sttart.
    (while (and (not (eobp))
                (not (looking-at paragraph-separate))
                (looking-at fill-prefix-regexpp))
        (forward-line 1))
  ;; within paragraphs,  no fill prefiix
  (if (re-searchh-forward parragraph-start ni t)
      (goto-char (match-beginning 0))
    (goto-char (point-max))))
(setq ar (1- arg)))
  1. 在段落之间的情况
  2. 在段落当中的情况
  3. 没有填充前缀的情况
  4. 有填充前缀的情况

创建自己的“TAGS”文件

你能够创建自己的“TAGS”文件来帮助你访问源代码。即使你使用grep命令或者别的命令来查找特定的函数,也能很容易地找到特定的函数。
你能够通过调用 etags程序来创建自己的“TAGS”文件。
要创建一个“TAGS”文件,首先要切换到需要创建这个“TAGS”文件的目录。在Emacs中,可以用M-x cd命令来切换到指定的目录,或者通过访问该目录下的一个文件,或者通过用C-x d命令列出这个目录下的文件,来达到切换目录的目的。然后输入:

M-! etags *.el

来创建一个“TAGS”文件。etags程序接收所有常用的shell的通配符。

回顾

  • while
    只要表达式主体的第一个元素为“真”,这个表达式的主体就被不断地重复求值。最返回返回nil。
  • re-search-forward
    查询一种模式,并且如果找到这种模式,就将位点移动到那个位置。
  • let*
    将局部变量绑定到指定的值上,然后对剩余的变量求值,它的返回值是最后一个参量的值。在绑定局部变量时,如果有的话就使用已经绑定的局部变量的值。
    match-beginning
    返回由最后一个正则表达式查询所得到的文本的开始位置
  • looking-at
    如果位点后的文本与正则表达式匹配,就返回真
  • eobp
    如果位点是缓冲区的可访问区域的末尾,就返回“真”。如果变窄没有开启,缓冲区中可访问区域的末尾就是缓冲区的末尾;如果变窄开启,它就是变窄部分的末尾。
  • prog1
    依次对其每一个参量求值,然后返回第一个参量的值。

计数:重复和正则表达式

count-words-region函数

一个单词计数命令可以对一行,一个段落,一个区域或者一个缓冲区进行计数。如果话,也能够用C-x h(mark-whole-buffer)键序列对整个缓冲区中的单词计数。
这个函数对一个区域中的单词计数。这意味着参量列表一定要包含两个位置的符号,这两个位置就是该 区域的开始位置和结束位置。这两个位置可以分别被叫做“beginning”和“end”。文档的第一行应当是一个完整的兔子,因为像apropos这样的命令将只打印函数说明文档的第一行。交互表达式的形式将是“(interractiive "r**),因为这将使Emacs将计数区哉的开始位置和结束位置作为参量传递给这个函数。
需要编写函数体以使其完成这样三件任务:道德建立while循环的条件,其次是while循环,最后是发送一个消息给用户。
当用户调用 count-words-region函数时,位点可能是在指定区域的开始,也可能是在末尾。然而,计数过程必须从区域的开始位置开始。这意味着,如果位点原来并不在指定区域的开始处,就要使位点移动到那里。执行(goto-char beginning)可以达到这个目的。当然,当函数执行完之后,要将位点返回到调用这个函数时的位置。为了这个原因,函数体必须包含在一个save-excursion表达式中。
函数体的中心部分由一个while循环组成,在这个循环中,一个表达式使位点一个单词一个单词地往前移动,另外一个表达式对移动的次数计数。只要位点还要继续往前移动,while循环的真假测试结果应当为”真“,当位点到达指定区域的末尾时,真假测试结果为”假“。
将使用(forward-word 1)作为表示向前一个单词一个单词地移动位点的表达式,但如果使用一个正则表达式查询,就更容易看到Emacs是如何区分一个单词的。实际的问题是,我们想要正则表达式查询直接路过单词之间的空格和标点符号,就像跳过单词本身一样。
因而,我们需要的这个正则表达式是一个模式,它定义一个或多个单词要素以及其后(可选的)的一个或多个不是单词构词要素的其他字符。这个正则表达式就是:

\w+\W*

查询表达式是这样:

(re-search-forward "\w+\W***)

需要一个计数器来对单词个数进行计数。这个变量必须首先设置为-,然后Emacs每循环一冷饮,这个计数器就加1.这个递增表达式如下:

(setq count (1+ count))

最后,想要告诉用户在这个区域中有多少单词。message函数就适合于用来向用户发送这种消息。
所有这些综合起来就是个函数定义:有BUG

;;; First version; has bugs!!!
(defuun count-words-region (beginning end)
  "print number of words in  thhe region.
  Words  are defined as att  least one word-constituent
  chharacter followed by at least one character thhat 
  is not  a  woord-coonstituent. Thhe buffer's syntax
  table determines which characters thhese are."
  (interactive "r")
  (message "Counting words in region...")
  ;;; 1. set upp appropriate conditions.
  (savve-excursion
    (goto-char beginning)
    (let ((count 0))
  ;;; 2. Run thhe while loop.
      (while (< (point) end)
        (re-search-foorward "\\w+\\W*")
        (setq count (1+ count)))
  ;;; 3. Send a message too  the user.
      (cond ((zerop count)
             (message
             "The region does NOT have any  words."))
             ((= 1 count)
             (message
             "The regiion has 1word."))
             (t
             (messagge
             "The region has %d woords." count))))))

count-words-region中的空格bug

上一节描述的count-words-region函数命令中有两个bug。第一,如果你标记的那个区域只有在某些文本当中包含空格,那个函数就会告诉你这个区域只包含一个单词。第二,如果你标记的区域只有在缓冲区的末尾或者在变窄的缓冲区的可以访问区域的末尾处包含一些空格,这个命令会显示一条这样的错误消息:

searh failed: “\w+\W*”

在这个BUG的两种表现当中,前一种是查询范围扩大了,后一种是试图到指定区域之外查询。
解决办法是限制查询的区域——这个动作相当简单,但是它实现方式实际上不简单。实现方式如下:

(and (< (point) end) (re-search-forward “\w+\W*” end t))

用递归的方法实现单词计数

如果编写单独一个递归函数来完成所有的事情,那么将在每一次递归调用时都将得到一个消息。实际上,我们不需要这样。所以,我们必须编写两个函数来完成这个工作,其中一个函数(递归函数)将在另外一个函数内部使用。一个函数将建立测试条件以及显示消息,另一个函数将返回单词计数。
我们先从显示消息的函数入手。同时将继续称这个函数为count-words-region。
这是用户将调用 的一个交互函数,确实,除了它将调用recursive-count-words函数来确定指定区域中有多少单词之外,它与这个函数的前面一个版本非常相似。
使用let表达式,这个函数定义如下所示:

(defun count-words-region (beginning end)
  "print number of words  in the region."
  (interactive "r")
  ;;; 1. set up  appropriiate condiitions
  (message "Count words in region ...")
  (save-excursion
    (goto-char beginning)
    ;;; 2. count thhe  words
    (let ((count (recursive-count-woords end)))
    ;;; 3. send a messagge too  the  user.
      (coond ((zerop count)
              (messagge
              "The region does NOT have any  words."))
             ((= 1 count)
              (message
              "The region has 1 word."))
             (t
               (message
                "The region has %d woords." coount))))))

下面,需要编写递归计数函数。
一个递归函数至少有三个部分:一个测试表达式,一个next-step表达式和递归调用。
函数结构如下:

(defun recursive-count-words (regiion-end)
  "Number of woords between point and  REGION-END."
  ;;; 1. do-again-text
  (if (and (< (point) regiion-end)
           (re-searchh-forward "\\w+\\W*" region-end t))
  ;;; 2. thhen-part:the recursiive calll
      (1+ (recursive-count-words region-end))
  ;;; 3. else-part
      0))

安装绑定按键

(global-set-key “\C-c=” ‘count-words-region’)

统计函数定义中的单词数

准备柱型图

配置你的".emacs"文件

定制Emacs的原因,是使它适合我。
通过编辑或者改编“~/.emacs”文件,你就能够定制并扩充Emacs。

全站点的初始化文件

最常见的是"site–load.el"和"site-init.el"两个全站点初始化文件。
其它三个全站点初始化文件在你每一次使用Emacs时被自动加载(如果这三个文件存在的话)。这些文件中,“site-start.el"文件在用户个人的初始化文件”.emacs"加载之前被加载, “default.ell"以及终端类型文件都是在用户个人的初始化文件”.emacs"加载之后被加载。
用户个人的初始化文件".emacs"中的设置以及定义将覆盖"site-start.ell"文件(如果它存在的话)中与之有冲突的设置和定义。但"default.el"以及终端文件中的设置和定义将覆盖用户个人的初始化文件中有冲突的设置和定义。(你可以将终端类型文件中的term-file-prefix项设置成nil,就可以避免终端类型文件的干扰)。
Emacs的发行版中的”INSTALL”文件描述了“site-init.el”和“site-load.el”两个文件。
“loadup.el”,“startup.el”以及“loaddefs.el”文件控制Emacs初始化文件的加载过程。这些文件都位于Emacs发行版本的“lisp”目录中。
其中”loaddefs.el“文件包含了许多建议,如你个人的初始化文件中应当放些什么内容,或者在一个全问点初始化文件中应当放些什么内容,等等。

为一项任务设置变量

Emacs判断一个变量是否是可以设置的,是通过观看这个变量的说明文档字符串中的第一个字符来决定的。如果说明文档字符串中的第一个字符是一个星号”*“,这个变量就是用户可以自行设置的选项。
edit-options命令列出了当人们在编写Emnnacs Lisp函数库时Emacs中所有可以重新设置的变量。
另一方面,使用edit-options命令来设置的可选项只在你的编辑会话中有效。要实现在不同会话之间永久地保存一个变化的设置,你需要在".emacs"或者其他初始化文件中使用setq表达式来设置。

开始改变”.emacs“文件

当你启动Emacs时,它加载 你个人的初始化文件”.emacs“,除非你在命令行用”-q“参数告诉它不要加载这个文件。

文本和自动填充模式

(setq default-major-mode 'text-mode)
(add-hook 'text-mode-hook 'turn-on-auto-fill)

邮件别名

setq mail-aliases t

缩排模式

默认情况下,如果要格式化一个区域,Emacs在多个空格的地方插入制表符来代替。下面的表达式可以关闭制表符缩排模式:

(setq-default indent-tabs-mode nil)

一些绑定键

;;; Compare windows
(global-set-key "\C-c w" 'compare-windows)

compare-windows命令将你当前缓冲区中的文本与下一个窗口中的文本进行比较。

;;; Keybinding for 'occur'
(global-set-key "\C-co" 'occur)

occur命令显示当前缓冲区中包含了与某个正则表达式匹配内容的所有行。匹配的行显示在一个称为“Occur”的缓冲区中。

要取消一个键的缄,需要如下格式:

(global-unset-key "\C-xf)

加载文件

能够使用load命令对一个完整的文件求值,并因此将这个文件中所有函数和变量安装到Emacs中。例如:

(load "~/emacs/kfill")

对这个表达式求值,即从用户个人目录的“emacs"子目录 中加载"kfill.el"文件。

如果你需要加载扩充功能包,可以无需要精确指定扩充文件的准确路径,只要指定它们作为Emacs的load-path一部分的目录即可。然后,当Emacs加载一个文件时,它将查询这个目录以及它的默认的目录列表。(默认的目录列表是Emacs安装时在"paths.h"文件中指定 的。)

下面的命令将你的"~/emacs"目录增加到已经存在的加载目录中:

;;; Emacs Load Path
(setq load-path (cons "~/emacs" load-path))

自动加载

除了通过加载包含指定函数的文件来实现函数的加载和安装,以及通过函数定义求值来实现函数的安装之外,你还能句在不真正安装函数代码的情况下使用这个函数。这个函数是在它第一次被调用 的时候安装的。这称为自动加载。

不常用的函数自动加载 的函数。"loaddefs.el"库包含了几百个自动加载的函数。

你也可以将自动加载函数的相关文件包含在你个人的".emacs"初始化文件的自动加载的表达式中。autoload是一个内置的函数,这个函数接收五个参量。其中最后三个是可选的:

  1. 第一个参量是被自动加载的函数名
  2. 第二个参量是被加载的文件名。
  3. 第三个参量是为这个函数编写的文档。
  4. 第四个参量是告之这个函数是否能被交互地调用 。
  5. 最后一个参量告诉对象是什么。

autoload函数可以处理函数,也可以处理键图和宏。

例:

(autoload 'html-helper-mode
  "html-helper-mode" "Edit HTML document" t)

​ 这个表达式从html-helper-mode.el文件中(或者如果存在 的话就从html-helper-mode.elc文件中加载)自动加载html-helper-mode函数。这个文件必须是在由load-path定义的一个目录中。函数的yywryu,p是一个帮助你用HTML语言编辑文档的模式。键入M-x html-helper-mode,你能够交互地调用这个模式。

一个简单的功能扩充: line-to-top-of-window

函数定义如下:

;;; Line to top of window;
;;; replace three keystroke sequence C-u 0 C-l
(defun line-to-top-of-window ()
    "Move the line point is on to top of window."
    (interactive)
    (recenter 0))

键图

Emacs使用键图(keymaps)来记录什么键调用什么命令。特定的模式,如C模式或者文本模式,都有它们自己的键图。与模式有关的键图将覆盖由所有缓冲 区共享的全局键图。

global-set-key函数的功能是绑定,或者重新绑定全局键图。

与模式有关的键图是用define-key函数绑定的,它接受一个指定的键图,键以及命令作为其参量。例如:

(define-key texinfo-mode-map "\C-c\C-cg"
    'texinfo-insert-@group)

X11的颜色

V19中的小技巧

修改模式行

e.g.

(setq mode-line-system-identification
  (substring (system-name) 0
             (string-match "\\..+" (system-name))))
(setq default-mode-line-format
      (list ""
            'mode-line-modifid
            "<"
            'mode-line-system-identification
            "> "
            "%14b
            " "
            'default-directory
            " "
            "%[("
            'mode-name
            'minor-mode-alist
            "%n"
            'mode-line-process
            ")%]--"
            "Line %l--"
            '(-3 . "%P"
            "-%-"))
;; Start with new default.
(setq mode-line-format default-mode-line-format)

调试

debug

你可以通过将debug-on-error的值设置成t来开始调试:

setq debug-on-error t

这个表达式使Emacs在它下一次遇到一个错误的时候进入调试器。

Emacs将创建一个名为“Backtrace”缓冲区。

最后可以通过将debug-on-error设置为nil关闭它。

debug-on-entry

第二种启动debug的方法,是当你调用 一个函数的时候进入 调试器。调用 debug-on-entry就能够实现这一点。

键入:

M-x debug-on-entry RET triangle-bugged RET

debug-on-quitt (debug)

除了设置debug-on-error和调用 debug-on-entry之外,还有另外两种方式可以启动debug。

通过将变量debug-on-quit设置为t,可以使你无论何时键入C-g都能启动debug。

或者你能够在你的代码中需要调试的一行中插入"(debug)"。

源代码级调试edebug

edebug通常显示你所调试的函数的源代码。对于你当前执行的那一行,edeubg用一个箭头在左边进行提示。

你可以一行一行地跟踪整个函数的执行,或者快速运行直到到达一个断点片。

结论

个人结论:
自我感觉本书讲得实例比较多,不过知识点感觉不太顺,总感觉差点什么。也许是需要读第二遍。先晾晾。有需要会过第二遍。

附录

你可能感兴趣的:(Emacs,Lisp,Emacs)