Elisp 12:兔子洞

前言:不知多久能學會 Elisp正則表達式

上一章:動態模塊算法

從本章開始,進入這份 Elisp 教程的第三部分。這部份內容側重於應用,在假若不得不引入沒學過的 Elisp 語法之時,則說明所側重的應用一定是好的。segmentfault

一個遊戲

如今考慮來寫一個很簡單的小遊戲:在一個一維的世界裏尋找兔子洞。函數

該如何表示這個一維世界呢?用緩衝區即可表示。該如何在這個世界裏行走呢?在緩衝區內移動插入點。code

在這個世界裏,如何表示兔子洞呢?這須要三思。orm

緩衝區變量

我想用兩個既不是全局變量也不是局部變量的變量來表示兔子洞的入口和出口。在 Elisp 語言裏,這樣的變量能夠是緩衝區變量。下面是這兩個變量的定義:教程

(setq hole-entrance "@#")
(setq hole-exit "#@")

(progn
  (find-file "world.txt")
  (make-local-variable 'hole-entrance)
  (make-local-variable 'hole-exit))

雖然 hole-entrancehole-exit 一開始是定義成了全局變量,可是在 Elisp 解釋器執行了 find-file 以後,當前緩衝區即是存放 world.txt 文件內容的緩衝區,這兩個變量在 make-local-variable 的做用下,就變成了當前緩衝區內的變量。遊戲

任何一個緩衝區都能直接使用同一個全局變量,也將它變成本身的變量,可是各個緩衝區都會以爲本身能夠獨佔它,修改它的值,並且這種修改對其餘緩衝區沒有任何影響。就像是一我的同時出如今多個世界裏,他不知道其餘世界裏的本身是怎樣的境況。ip

簡單的世界

如今,已經有了兔子洞的入口和出口的表示形式了,所以能夠爲上述的遊戲隨便構造一個世界:資源

********@#一個兔子洞#@********@#又
一個兔子洞#@**********************
****
@#
                   這也是一個兔子洞
#@****************

這個世界裏,有三個兔子洞。我將這個世界保存在文本文件 world.txt 裏,待 Elisp 程序使用 (find-file) 將其載入緩衝區。接下來的任務即是如何在存放這個世界的緩衝區內找出這些兔子洞。

search-forward

清楚了要解決的問題是什麼,還有清楚本身有哪些資源能夠利用以及如何利用,此兩者的充分結合,便誕生了算法。要找出兔子洞,能夠利用 Elisp 函數 search-forward,同以前用過的 re-search-forward 類似,可在緩衝區內前向遞進搜索與指定文本相匹配的文本,只是後者支持正則表達式匹配。在尋找兔子洞的過程當中,不須要用正則表達式,所以用 search-forward 更爲適合。

如今試試 search-forward

(setq hole-entrance "@#")
(setq hole-exit "#@")

(progn
  (find-file "world.txt")
  (make-local-variable 'hole-entrance)
  (search-forward hole-entrance))

Elisp 解釋器對上述程序的求值結果是 11,這正是第一個兔子洞入口 @# 以後的位置,亦即 search-forward 返回的結果是緩衝區內與 hole-entrance 的值匹配的文本的末尾位置。

若是 search-forward 在緩衝區內未能發現與指定文本相匹配的文本,默認狀況下它會報錯。我以爲動不動就報錯,過小題大做了,找不到就找不到,讓它返回 nil 就行了,這須要將 search-forward 的第三個參數設爲 t,即

(search-forward hole-entrance nil t)

要設置第三個參數,不得不設置第二個參數。從如今開始要記住,Elisp 函數的參數容許可選,可是假若想設置其中的某個參數,可是又不知它前面的可選參數如何設置的時候,就將它們設爲 nil 便可,由於 nil 是全部變量的默認值,函數的參數是變量。一旦 search-forward 可以返回 nil,即可以經過條件表達式,去接管這種查找失敗的狀況。

因爲 search-forward 不只能返回緩衝區內匹配文本的末尾位置,同時也將插入點移動到這一位置,所以 search-forward 便繼續搜索下一個兔子洞,亦即

(progn
  (find-file "world.txt")
  (make-local-variable 'hole-entrance)
  (make-local-variable 'hole-exit)
  (if (search-forward hole-entrance nil t)
      (search-forward hole-entrance nil t)))

能夠發現第二個兔子洞的入口。

同理,下面的代碼能發現第三個兔子洞的入口:

(progn
  (find-file "world.txt")
  (make-local-variable 'hole-entrance)
  (make-local-variable 'hole-exit)
  (if (search-forward hole-entrance nil t)
      (if (search-forward hole-entrance nil t)
          (search-forward hole-entrance))))

搜索所有的兔子洞

上一節反覆運用 search-forward 搜索兔子洞入口的過程顯然是一個典型的迭代過程,所以能夠用 while 表達式簡化,即

(load "newbie")

(setq hole-entrance "@#")
(setq hole-exit "#@")

(progn
  (find-file "world.txt")
  (make-local-variable 'hole-entrance)
  (make-local-variable 'hole-exit)
  (let (@-extrance)
    (while (setq @-entrance (search-forward hole-entrance nil t))
      (princ\n (format "兔子洞入口:%d" @-entrance)))))

還記得 newbie.el 庫和 princ\n 宏嗎?忘記了,就再回顧一下前面第 10 章的內容吧!

執行這個程序,在個人機器上的輸出爲

Loading /home/garfileo/.my-scripts/elisp/newbie.el (source)...
兔子洞入口:11
兔子洞入口:28
兔子洞入口:67

注意,setq 能夠用做條件表達式,由於它可返回變量綁定的值,要麼是 nil,要麼不是 nil,後者與 t 等價。

對上述程序略做修改,

(load "newbie")

(setq hole-entrance "@#")
(setq hole-exit "#@")

(progn
  (find-file "world.txt")
  (make-local-variable 'hole-entrance)
  (make-local-variable 'hole-exit)
  (let (@-extrance @-exit)
    (while (progn
             (setq @-entrance (search-forward hole-entrance nil t))
             (setq @-exit (search-forward hole-exit nil t)))
      (princ\n (format "兔子洞 (%d %d)" @-entrance @-exit)))))

即可找出全部兔子洞的入口和出口了,由於在個人機器上的輸出結果是

Loading /home/garfileo/.my-scripts/elisp/newbie.el (source)...
兔子洞 (11 18)
兔子洞 (28 37)
兔子洞 (67 98)

結語

遊戲遠未結束,兔子洞是個很複雜的所在。

相關文章
相關標籤/搜索