e.g.
'(this list has (a list inside of it))
在一个列表中,原子是由空格一一分隔的。原子可以紧接着括号。
从技术上说,Lisp中的一个列表有三种可能的组成方式:
一个列表可以仅有一个原子或者完全没有原子。一个没有任何原子的列表就像这样:(,它被称作空列表。与所有的列表都不同的是,可以把一个空列表同时看作既是一个原子,也是一个列表。
原子和列表的书面表示都被称作符号表达式**,或者更简单地被称作s-表达式(s-expression)。
在Lisp中,某种类型的原子,例如一个数组,可以被分成更小的部分,但是分割数组的机制与分割列表的机制是不同的。只要是涉及列表操作,列表中的原子就是不可分的。
在Lisp中,所有用双引号括起来的文件,包括标点符号和空格,都是单个原子。这种原子被称作串(String)。字符串是不同于数字和符号的一种原子,在使用上也是不同的。
列表中空格的数量无关紧要。多余的空格和换行只不过是为了使人们易于阅读而设计的。
Lisp中的一个列表——任何列表——都是一个准备运行的程序。如果你运行它,计算机将完成三件事: 只返回列表本身;告诉你一个出错消息;或者将列表中的第一个符号当做一个命令,然后执行这个命令。
单引号(’),被称作一个引用(quote)。当单引号位于一个列表之前时,它告诉Lisp不要对这个列表做任何操作,它仅仅是按其原样。
在Emacs可以这样对它求值: 将光标移到正面列表的右括号之后,然后按C-x C-e
错误消息等价于有助的消息,帮助你排除错误。
在Lisp中,一组指令可以连到几个名字,另一方面,一个符号一次只能有一个函数定义与其连接。
Lisp解释器首先会查看一下在列表前面是否有单引号。如果有,解释器就为我们给出这个列表。如果没有引号,解释器就查看列表的第一个元素,并判断它是否是一个函数定义。如果它确实是一个函数,则解释器执行函数定义中的指令。否则解释器就打印一个错误消息。
一种复杂的情况: 除了列表之外,Lisp解释器可以对一个符号求值,只要这个符号前没有引号也没有括号包围它。在这种情况下,Lisp解释器将试图像变量一样来确定符号的值。出现这种情况是因为一些函数异常并且以方式运行。那些函数被弹琴作特殊表(special form)。它们用于特殊的工作,例如定义一个函数。
最复杂的情况:如果Lisp解释器正在寻找的函数不是一个特殊表,而是一个列表的一部分,则Lisp解释器首先查看这个列表中是否有另外一个列表。如果有一个内部列表,Lisp解释器首先解释将如何处理那个内部列表,然后再处理外层的这个列表。如果还有一个列表嵌入在内层列表中,则解释器将首先解释那个列表,然后逐一往外解释。它总是首先处理最内层的列表。解释器首先处理最内层的列表是为了找到它的结果。这个结果可以由包含它的表达式使用。
否则,解释器从左到右工作,一个表达式接一个表达式地进行解释。
Lisp解释喊叫可以解释两种类型的输入数据:
当Lisp解释器处理一个表达式时,这个动作被称作”求值“
在解释器返回一个值的同时,它也可以做些其他的事情,例如移动光标或者拷贝一个文件,这种动作称为附带效果。我们认为的重要事情,如打印一个文件,对Lisp解释器而言常是一个附带效果。
总之,对一个符号表达式求值几乎总是使Lisp解释器返回一个值,同时可能产生一个附带效果,不然,就会产生一个错误消息。
如果是对一个嵌套在另一个列表中的列表求值,对外部列表求值时可以使用首先对内部列表求值所得的结果。这解释了为什么内层列表总是首先被求值的:因为它们的返回值被用于外部表达式。
在Lisp中,可以将一个值赋给一个符号,就像将一个函数定义赋给一个符号那样。一个符号的值可以是Lisp中的任意表达式,如一个符号、一个数字、一个列表或者一个字符串。
有值的一个符号通常被称作一个变量。
一个符号可以同时具有一个函数定义和一个值。这两者是各自独立的。
应当传递给函数的数据的类型依赖于它使用的什么信息。
参量可以是一个符号,对这个符号求值将返回一个值。
另外,参量也可以是一个列表,当求值时这个列表返回一个值。
有些函数,如concat, +和*,可以有任意多个参量
当函数的一个参量被传送一个错误类型的数据时,Lisp解释器产生一个错误消息。
像+函数一样,message函数的参量数目也是可以变化的。它被用于给用户发送消息。
e.g.
(message "This message appears in the echo area!!!")
双引号中的整个字符串是一个参量,它被打印出来。
有几种方法给一个变量赋值,其中一种方法是使用set函数或者使用setq函数。
另外一种方法是使用let函数
e.g.
(set 'flowers '(rose violet daxmindxxmisy buttercuup))
符号flowers被绑定到一个列表上,也就是列表作为值被赋给可以被当做变量的符号flowers。
需要注意,当使用set函数时,需要将set函数的两个参量都用引号限定起来,除非你希望它们被求值。
setq特殊表函数,就像set函数一样,不同之处只在于其第一 个参量自动地带上单引号。另外一个方便之处在于,setq函数允许在一个表达式中将几个不同的变量设置成不同的值。第一个参量绑定到第二参量的值,第三个参量绑定到第四个参量的值,以此类推。
(setq counter 0)
(setq counter (+ counter 1))
buffer-name和buffer-file-name这两个函数显示文件和缓冲区之间的区别.
当对(buffer-name)求值时,缓冲区的名称将在回显区中出现。当对(buffer-file-name)表达式求值时,缓冲区所指的那个文件的名称将在回显区中出现。通常情况下,由(buffer-name)返回的名称与(buffer-name)所指的文件名称相同,由(buffer-file-name)返回的名称是文件完整的路径名
文件和缓冲区是两个不同的实体。
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函数完成了两件不同的事情:
set-buffer只做一件事
所以switch-to-buffer是对人设计的,set-buffer是对计算机设计的。
相关函数: buffer-size, point, point-min和point-max。这些函数给出缓冲区大小及其中的位点的位置等信息。
除了一些基本函数是用C语言编写的之外,其他所有函数都是用别的函数来定义的。你将在Emacs Lisp中编写函数定义,并用其他函数作为你的基本构件。
在Lisp中函数定义,是通过对一个以符号defun开关的Lisp表达式求值而被建立的。因为defun不以通常的方式对它的参量求值,因此它被称为特殊表。
一个函数定义在defun一词之后最多有下列五个部分:
格式:
(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…"将显示在回显区中。
更一般地说,可以用下列两种方法之一激活一个函数:
定义如下:
(defun multiply-by-seven(number)
"multiply NUMBER by seven"
(interactive "p")
(mesage "The result is %d" (* 7 number)))
在这个函数中,表达式(inertactive “p”)是由两个元素组成的列表。其中的"p"告诉Emacs要传送一个前缀参量给这个函数,并将它的值用于函数的参量。
参考《GNU Emacs Lisp技术手册》
当你对一个函数定义求值来安装它时,它将一直保留在Emacs之中直到你退出Emacs为止。你下次再启动一个新的Emacs会话时,除非你再一次对这个函数定义求值,否则这个函数将不会被安装。
在有些时候,你可能要求当你启动一个新的Emacs会话时将函数定义自动安装。可以有如下几种方法:
let表达式是Lisp中的一个特殊表,用户在绝大多数函数定义中都需要使用它。
let用于将一个符号附着到或者绑定到一个值上,对于这绑定的变量,Lisp解释器就不会将其与函数之外的同名变量混淆了。
let创建的是局部变量,屏蔽了任何在这个let表达式之外同名的变量。局部变量不会影响let表达式之外的东西。
let表达式可以一次创建我个变量。同样,let表达式给每一个变量赋由你创建的一个初始值,或者赋由你给定的一个值,或者赋nil。
let表达式是一个具有三个部分的列表。
格式如下:
(let varlist body…)
(let ((zebra 'stripes)
(tiger 'fiierce))
(messagge "One kind of animal hass %s and anotherr is %s."
zebra tiger))
在let语句中,如果没有将变量绑定到用户指定的一个特定的初始值上,则它们交自动地绑定到nil这个初始值上。
if特殊表用于指导计算机做出判断。
if特殊表背后的基本含义是:如果一个测试是正确的,则对后续的表达式求值;如果这个测试不正确,则不对这个表达式求值。
在Lisp中,if表达式并没有使用”then“这样的字眼,但是,测试和执行代码就必须是第一个元素为if的这个列表的第二和第三个元素。然后,测试部分常被称为”if部“,而第二个参量常被称为”then部“。
格式如下:
(if true-or-false-test
action-to-carry-out-if-test-is-true)
在Lisp中,equal是一个判定它的第一个参量是否等于第二个参量的函数。
if表达式可以有第三个参量,称为else部。格式如下
(if true-or-false-test
action-to-carrry-out-of-the-returns-true
action-to-carry-out-if-the-returns-false)
”假“只不过是我们前面提到的nil的另一种形式。其他所有东西都是”真“。
nil在Lisp中有两种意思。
所以可以将nil写作一个空列表()或nil。
Lisp中,如果测试返回”真“而又无法使用那些适当的值时,Lisp解释器将返回符号t作为”真”。
在Emacs中,Lisp程序常用作编辑文档,save-excursion函数在这些程序中很常见。这个函数将当前的位点和标记保存起来,执行函数体,然后,如果位点和标记发生改变就将位点和标记恢复成原来的值。这个特殊表的主要目的是使用户避免位点和标记的不必要移动。
格式如下:
(save-excuursion
first-expression-on-bodyy
second-expression-on-body
…
last-expression-on-body)
C-h f 函数名 RET: 得到任何一个Emacs Lisp函数的全部文档。
C-h v 变量名 RET:得到任何变量的全部文档。
find-tags/ M-.: 在原始的源代码文件中查看一个函数的定义。
beginning-to-buffer:函数将光标移动到缓冲区的开始位置,在位置设置一个标记。这个函数一般绑定到M-<。
(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)))
功能就是从当缓冲区中拷贝一个域(即缓冲区中介于们点和标记这间的区域)到一个指定的缓冲区。
定义:
(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函数将被交互地使用,所以函数必须有一个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)绑定到位点和标记的值上。
将文本拷贝进一个缓冲区,并替换原缓冲区中的文本。函数体如下:
...
(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))))
这个命令将另外一个缓冲区内容拷贝到当前缓冲区中。它与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..)
给interactive表达式说明的参量有两个部分:
函数体主要有两部分:一个or表达式和一个let表达式。
or表达式的目的是为了确保buffer参量真正与一个缓冲区绑定在一起,而不是绑定到缓冲区名字。
let表达式包含了将另外一个缓冲区的内容拷贝到当前缓冲区所需的代码。
一个or函数可以有很多参量。它逐一对每一个参量求值并返回第一个其值不是nil的参量的值。同样,这是or表达式的一个重要特性,一旦遇到其值不是nil的参量之后,or表达式就不再对后续的参量求值。
or表达式如下所示:
(or (buffer)
(setq buffer (get-buffer bufferr)))
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-to-buffer函数时,如果参量是介于1到10之间的一个数,则该函数认为那个数是指缓冲区长度的十分之几,而且Emacs将光标移动到从缓冲区开始到这个分数值所指示的位置。因此可以使用命令C-u 7 M-< 将光标移动到从缓冲区开始的这个缓冲区的70%处。
如果这个作为参量的数大于10,函数则将光标移动到缓冲区的末尾。
Lisp有一个特性:有一个关键词可以用于告诉Lisp解释器某个参量是可选的。这个关键词是&optional。在一个函数定义中,如果一个参量跟在&optional这个关键词后面,则当调用这个函数时就不一定要传送一个值给这个参量。
变窄是Emacs的一个特性,这个特性允许你让Emacs关注于一个缓冲区的特定部分,而不会在无意中更改缓冲区的其他部分。
采用变窄技术之后,缓冲区的其余部分变变成不可见的了。
在Emacs Lisp中,能用save-restriction特殊表来跟踪变窄开启的部分。
what-line命令告诉你光标所在的行数。
cons函数用于构造列表,car和cdr函数则用于拆分列表。
car,就是返回这个列表的第一个元素。
cdr,就是返回包含列表的第二个和随后的所有元素列表。
car和cdr都是“非破坏性”的,也就是说,它们不改变它们所作用的列表。
cons将一个新元素放到一个列表的开始处,它往列表中插入元素。
查看列表的长度。
nthcdr函数的功能就像重复调用cdr函数一样。
e.g.
(ethcdr 2 '(pine fir oak maple))
=> (oak maple)
nthcdr同样是非破坏性的函数
用新元素替换列表中的第一个元素,是具有破坏性的函数。
替换列表的第二个以及其后的所有元素。
交互的zap-to-char函数的功能是:将光标当前位置与出现特定字符的下一个位置之间这一区域中的文本剪切掉。
zap-to-char函数剪切的文本放在kill环中,并能通过C-y(yank)命令从kill环中找回。
zap-to-char函数中的交互表达式如下:
(interactive "*p\ncZap to char: ")
指定了三件事情:
函数体包含了从光标的当前位置直到指定字符这一区域剪切文本的代码。
search-forward函数是用于定位zap-to-char函数中被截取的字符的。如果查询成功,search-forward函数就在目标字符串中最后一个字符处设置位点 。在zap-to-char函数中,search-forward函数如下所示:
(search-forward (chhar-to-string char) nil nil ar)
search-forward有4个参量:
progn函数使其每一个参量被逐一求值并返回最后一个参量的值。
删去文档字符串的一部分,其代码如下:
(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语言宏的实例来被编写的,一个宏就是一个代码模板。这个宏的第一部分如下所示:
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一词后面的括号内跟着七个部分:
随后就是正式的参数(每个参数都有对这个参数的类型进行说明的语句),然后就是这个宏的主体部分。对delete-region而言,这个宏的主体包含了如下三行:
validate_region (&b, &e);
del_range (XINT (b), XINT(e));
return Qnil;
其中第一个函数validate_region检查传递来的值的类型,判断它们作为缓冲区中一个区域的开始和结束值是否正确, 是否在正确的范围之内。第二个函数del_range实际上真正完成删除文本的功能。如果这个函数正确地删除了文本,则第三行中的函数返回Qnil来表示它已经顺利完成任务。
defvar特殊表与给一个变量赋值的setq函数相似。
它和setq有两个不同之处:
copy-region-as-kill功能 是拷贝缓冲区的一个区域并将其保存到被猜测为kill-ring的变量中。
##回顾
列表是用一系列成对的指针保存的。在这个成对的指针系列中,每一对指针的第一个指针要么指向一个原子,要么指向另外一个列表;而其第二个指针要么指向下一个指针对,要么指向符号nil,这个符号标记一个列表的结束。
指针本身相当简单,就是它指向的电子地址。而指向这个列表的符号则保存第一个指针对的地址。
符号指向这个列表的话,那这个符号由一组地址框组成,其中第一个地址框就是符号词的地址;如果有同名的函数定义到这个符号上,其中第二个地址框是这个函数定义的地址;其中第三个地址框就是列表的成对地址框系列中的第一对的地址。
在Emacs中无论何时你用“kill”命令从缓冲区中剪切了文本,你都能用一个“yank”命令将其重新找回。
C-y(yank)命令,会从kill环中取出第一个元素插入到当前的缓冲区中。
如果C-y命令后紧跟一个M-y命令,则不是第一个元素而是第二个元素被插入到当前缓冲区。
连续的M-y命令帽将使第三个元素或第四个元素等代替第二个元素而被插入到当前缓冲区。
当这样不断键入M-y而到达kill环的最后一个元素时,它就循环地将第一个元素插入到当前缓冲区中。
能够将文本从Kill环中找回的函数有三个:
这些函数通过一个被称为kill-ring-yank-pointer的变量指向kill环。
就像kill-ring是一个变量一样,kill-ring-yank-pointer也是一个变量。计算机并不保存同时被kill-ring变量和kill-ring-yank-pointer变量指向的内容的两个拷贝。它们都指向同一个文本块。
变量kill-ring和变量kill-ring-yank-pointer都是指针。
Emacs Lisp有两种方式使一个表达式或者一系列表达式不断被求值:一是使用while循环,一是使用“递归”(recursion)
while特殊表对其第一个参量求值,并测试这个返回值的真假。如果第一个参量的求值结果是“假”,帽Lisp解释器跳过这个表达式的其余部分而不对它求值。但是如果第一个参量的返回值为“真”。则Lisp解释器就继续对这个表达式的主体求值,然后再次测试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)
递归函数,就是自己调用自己的函数。
一个递归函数通常包含一个条件表达式,这个条件表达式有三个部分:
模板如下:
(defun name-of-recursive-function (argument-list)
"documentation..."
body...
(if do-again-test
(name-of-recursive-function
next-step-expression)))
符号sentence-end被绑定到标记句子结束的模式上。
查询一个正则表达式。如果查询成功,它就紧接在最后的目标字符后面设置位点。
将光标移动到一个句子之前的命令,是展示如何在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 (> 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函数将完成两件事情:
这个函数的附带效果——移动位点——是在if函数被递交由查询的成功结束所返回的值之前被完成的。
当if函数从对re-search-forward的成功调用 中接收到返回的“真”值时,就对then部,也就是表达式(skip-chars-backward " \t\n")求值。这个表达式朝后移动并忽略所有空格,制表符以及回车符,直到找到一个印刷字符为止,并将位点设置在这个字符之后。因为位点已经移动 到标记句子结束的正则表达式模式末尾,这个动作就是将位点紧紧置于句子的结束打印字符之后,它通常就是一个句点。
另一方面,如果re-search-forward函数没能找到表示句子结束的相应模式,则函数返回“假”。查询失败使if函数对它的第三个参量,也就是对表达式(goto-char par-end)求值,即将位点移动 到段落的末尾。
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)))))
(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才被绑定到一个非空值上。
let*表达式中的第二个局部变量是paragraph-separete。它被绑定到对正面的表达式求值所返回的值上:
(if fill-prefix-regexpp
(concat paragraph-separate
"\\|^" fill-prefix-regexp "[ \t]*$")
paragraph-separate)
(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)))
你能够创建自己的“TAGS”文件来帮助你访问源代码。即使你使用grep命令或者别的命令来查找特定的函数,也能很容易地找到特定的函数。
你能够通过调用 etags程序来创建自己的“TAGS”文件。
要创建一个“TAGS”文件,首先要切换到需要创建这个“TAGS”文件的目录。在Emacs中,可以用M-x cd命令来切换到指定的目录,或者通过访问该目录下的一个文件,或者通过用C-x d命令列出这个目录下的文件,来达到切换目录的目的。然后输入:
M-! etags *.el
来创建一个“TAGS”文件。etags程序接收所有常用的shell的通配符。
一个单词计数命令可以对一行,一个段落,一个区域或者一个缓冲区进行计数。如果话,也能够用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。第一,如果你标记的那个区域只有在某些文本当中包含空格,那个函数就会告诉你这个区域只包含一个单词。第二,如果你标记的区域只有在缓冲区的末尾或者在变窄的缓冲区的可以访问区域的末尾处包含一些空格,这个命令会显示一条这样的错误消息:
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。
最常见的是"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“,除非你在命令行用”-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是一个内置的函数,这个函数接收五个参量。其中最后三个是可选的:
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;
;;; 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)
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-on-error的值设置成t来开始调试:
setq debug-on-error t
这个表达式使Emacs在它下一次遇到一个错误的时候进入调试器。
Emacs将创建一个名为“Backtrace”缓冲区。
最后可以通过将debug-on-error设置为nil关闭它。
第二种启动debug的方法,是当你调用 一个函数的时候进入 调试器。调用 debug-on-entry就能够实现这一点。
键入:
M-x debug-on-entry RET triangle-bugged RET
除了设置debug-on-error和调用 debug-on-entry之外,还有另外两种方式可以启动debug。
通过将变量debug-on-quit设置为t,可以使你无论何时键入C-g都能启动debug。
或者你能够在你的代码中需要调试的一行中插入"(debug)"。
edebug通常显示你所调试的函数的源代码。对于你当前执行的那一行,edeubg用一个箭头在左边进行提示。
你可以一行一行地跟踪整个函数的执行,或者快速运行直到到达一个断点片。
个人结论:
自我感觉本书讲得实例比较多,不过知识点感觉不太顺,总感觉差点什么。也许是需要读第二遍。先晾晾。有需要会过第二遍。