複習 GNU m4 用戶界面設計 雙重空間 序號 嘗試 結論 補充

隨便抓幾十萬個人出來,我可能是其中最懂 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。

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