隨便抓幾十萬個人出來,我可能是其中最懂 GNU m4 的,因爲我曾經寫過一份 GNU m4 的教程[1]。若問我 GNU m4 是什麼?我能想出的最好的答案是,一旦兩三年不用它,我就泯然幾十萬人了。大概某院士之於生物學也是如此吧[2]。
爲了避免數十年後有人偷襲老同志得逞,思慮再三,曲不離手,拳不離口,我覺得還是有必要每年拿出兩三天的時間複習一下 m4。
無論是學習 m4 還是複習 m4,最好的辦法是用它做一個小東西。不止對於 m4 如此,也許對於任何一門編程語言都應該如此。沒錯,m4 是一種編程語言,很微小,很另類,這是重新讀了我爲它寫的那份教程之後的舊認識。上次我用 m4 製作的小東西是文檔的目錄功能,例如:
這次要做的小東西,比文檔目錄功能要簡單許多,但是原理相似。我要爲文檔增加樸素的註解功能。
文檔的註解,就像這篇文章裏的註解。正文裏只出現註解的編號,註解的內容出現在文章的尾部。在正經的排版工具沿用的術語中,這樣的註解稱爲腳註。
用戶界面設計
這次雖然僅僅是使用 m4 定義兩個宏 _note_
和 _place_notes_
,也要煞有介事,稱之爲用戶界面。輸人不輸陣。
這兩個宏的用法示例如下:
隨便抓幾十萬個人出來,我可能是其中最懂 GNU m4 的,因爲我曾經寫過一份 GNU m4
的教程_note_(讓這世界再多一份 GNU m4 教程……)。若問我 GNU m4 是什麼?我能想
出的最好的答案是,一旦兩三年不用它,我就泯然幾十萬人了。大概某院士之於生物學也
是如此吧_note_(某教授舉報某院士學術不端……)。爲了避免數十年後有人偷襲老同志得
逞,思慮再三,我覺得還是有必要每年拿出兩三天的時間複習一下 m4。
……
_place_notes_
期望得到的結果如下圖所示:
至此,我的 UI 設計師生涯,曇花一現,至此結束。
雙重空間
要實現上文所述的用戶界面,需要藉助 m4 的空間轉移功能。
m4 可在多個空間內對輸入流進行處理,最後再合併爲輸出流[3]。本文不再贅述 m4 的基本知識,因爲我已經用很快的速度和很粗放的精神重讀了我寫的 GNU m4 教程。現在,我已明白我所說的這些 m4 的概念,未來亦如是。我想……我已經足夠暗示了,我所寫的 GNU m4 教程,對於順暢地閱讀本文,有多麼重要。
下面我嘗試直接用 m4 空間轉移功能模擬一下註解。
divert(0)dnl
隨便抓幾十萬個人出來,我可能是其中最懂 GNU m4 的,因爲我曾經寫過一份 GNU m4
的教程[1]divert(1)[1] 讓這世界再多一份 GNU m4 教程……divert(0)。若問我 GNU m4 是
什麼?我能想出的最好的答案是,一旦兩三年不用它,我就泯然幾十萬人了。大概某院士之
於生物學也是如此吧[2]divert(1)[2]某教授舉報某院士學術不端……divert(0)。爲了
避免數十年後有人偷襲老同志得逞,思慮再三,我覺得還是有必要每年拿出兩三天的時間複習
一下 m4。
……
divert(1)
將上述內容保存爲 foo.m4 文件,然後執行
$ m4 foo.m4
結果可得:
隨便抓幾十萬個人出來,我可能是其中最懂 GNU m4 的,因爲我曾經寫過一份 GNU m4
的教程[1]。若問我 GNU m4 是什麼?我能想出的最好的答案是,一旦兩三年不用它,我
就泯然幾十萬人了。大概某院士之於生物學也是如此吧[2]。爲了避免數十年後有人偷襲
老同志得逞,思慮再三,我覺得還是有必要每年拿出兩三天的時間複習一下 m4。
……
[1]讓這世界再多一份 GNU m4 教程……[2]某教授舉報某院士學術不端……
雖然得到的註解列表不符合預期,但是關鍵的技術問題已經解決了一半,不是嗎?
序號
關鍵的技術的另一半是自動生成註解的序號。在上一節的示例中,序號是我手工編寫的。雖然手工編寫序號並不是過於繁瑣,但是倘若文檔內容發生變化導致註解的序號需要修改呢?無論是註解,插圖,還是參考文獻,能自動爲它們生成序號,應該是每一個稱職的排版軟件必須提供的功能。
用 m4 爲註解生成序號,需要構造一種可以數值自增的宏,並將其植入 _note_
的定義裏,方能實現在每一次使用 _note_
時,會有能夠自增的數值作爲註解的序號。
在大多數常見的編程語言裏,用變量實現數值自增,不廢吹灰之力,但 m4 沒有變量,只有宏。我可以定義一個宏 _N_
:
define(`_N_', 0)
也可以使用 m4 內建的宏 incr
讓 _N_
的值[4]增 1:
incr(_N_)
若讓 _N_
自增,必須重新定義 _N_
,即:
define(`_N_', incr(_N_))
亦即,
define(`_N_', 0)
define(`_N_', incr(_N_))
若用 C 語言作等價描述,如下:
int _N_ = 0;
_N_++;
或
int _N_ = 0;
_N_ = _N_ + 1;
每次談及編程語言裏數值變量的自增,都會想起很久很久以前我師兄的碩士畢業答辯時的一個小插曲。因爲他是機械專業,答辯會上的專家也都是機械學科的教授,對編程的事可能是不怎麼懂。當師兄講完了他精心準備的 PPT 之後,一個教授給他指出了 PPT 裏的一個小錯誤:同學,你這個 i 怎麼就等於 i + 1 了呢?
嘗試
現在,嘗試基於上述的空間轉移指令和數值自增宏給出 _note_
的定義:
define(`_N_', 0)
define(`_note_',
`define(`_N_', incr(_N_))dnl
[_N_]divert(1)[_N_] $1
divert(0)')
然後再給出 _place_notes_
的定義:
define(`_place_notes_', `divert(1)')
爲了便於測試,建立 note.m4 文件,將上述代碼放入該文件,即:
divert(-1)
define(`_N_', 0)
define(`_note_',
`define(`_N_', incr(_N_))dnl
[_N_]divert(1)[_N_] $1
divert(0)')
define(`_place_notes_', `divert(1)dnl')
divert(0)dnl
note.m4 文件的第一行和最後一行,是 m4 的傳統藝能,用於定義可被其他 m4 文件包含的宏。
在 note.m4 文件同一目錄裏新建一份 foo.m4 文件,內容爲:
include(note.m4)
測試 _note_(註解 1)。再測試_note_(註解 2)。事不過三_note_(註解 3)。
----
_place_notes_
用於測試 _note_
和 _place_notes_
能否符合預期。
執行
$ m4 foo.m4
得到的輸出爲:
測試 [1]。再測試[2]。事不過三[3]。
----
[1] 註解 1
[2] 註解 2
[3] 註解 3
我覺得,現在可以下結論了吧?
結論
- 使用 GNU m4 能夠在文本文檔中實現樸素的註解功能。
- 爲文本文檔實現註解功能,有助於學習或複習 GNU m4 的基本知識。
補充
這篇文章寫完後,發現 Markdown 的語法爲腳註提供了標記。
註解:
[1] 讓這世界再多一份 GNU m4 教程:(1),(2),(3),(4),(5)。
[2] 某教授舉報某院士學術不端,理由是該院士 1999 年發表的一篇論文裏描述的實驗根本不能得出該論文的結論。該院士並不接招。中科院牽頭成立的調查組給出的調查結果是,未發現該院士有論文造假行爲,認爲無需重複論文所述實驗對論文結論予以驗證。
[3] 此處所謂的「空間」,在我寫的 GNU m4 教程中稱爲緩存。
[4] 嚴格而言,是讓 _N_
的展開結果增 1。