《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用一個箭頭在左邊進行提示。

你可以一行一行地跟蹤整個函數的執行,或者快速運行直到到達一個斷點片。

結論

個人結論:
自我感覺本書講得實例比較多,不過知識點感覺不太順,總感覺差點什麼。也許是需要讀第二遍。先晾晾。有需要會過第二遍。

附錄

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章