改造

如今,我基本熟悉了 pretty-c 模塊 [1]。熟悉,也許是爲了控制甚至改變,不然沒理由要去熟悉。請想想本身對親人、愛人和朋友曾經作過的以及正在作的那些事吧!因而,我要對 pretty-c 模塊進行一些改造。算法

過程名

在用僞 C 代碼表達一些算法的時候,我會遵循了本身以前養成的文化編程習慣 [2],採用「@ 過程名 #」的形式指代一個算法,採用「# 過程名 @」的形式調用一個算法。例如:編程

@ K 均值聚類中心初始化 #
輸入:點集 points,簇數 K
輸出:含聚類初始中心的分類樹
{
        size_t *indices = agn_generate_indices(K, 0, points->n - 1);
        # 初始化種類中心 @
        # 初始化種類樹 @
        agn_array_free(init_centers);
        free(indices);
        return class_tree;
}

當我在 ConTeXt MkIV 使用 C 代碼的形式對上述算法描述進行排版時,我但願它可以經過 pretty-c 模塊認出「@ 過程名 #」和「# 過程名 @」,而後,將它們排版爲下面這樣的形式:segmentfault

模擬

個人目的是將「@ 過程名 #」和「# 過程名 @」分別排版爲「< 過程名 > ≡」和「< 過程名 >」的形式。因而,我要解決的問題是將「@ 過程名 #」和「# 過程名 @」轉化爲相應的 ConTeXt MkIV 的排版語句。編程語言

注:實際上不是 <>,而是 TeX 數學符號 \langle\rangle,如今只是用 <> 做爲替代。

< 過程名 > ≡」能夠用 ConTeXt 代碼 $\langle$ 過程名 $\rangle\equiv$ 來表示。因爲 ConTeXt MkIV 會用等寬字體排版代碼文本,而我但願可以繼續用正文字體來排版「< 過程名 > ≡」,因此須要用 \switchtobody 將字體臨時切換爲正文字體,即函數

\switchtobodyfont[rm]{$\langle$ 過程名 $\rangle\equiv$}

這樣便完成了對「< 過程名 > ≡」的模擬。將上述語句中的 \equiv 去掉,結果即是對「< 過程名 >」的模擬。字體

因爲我但願模擬結果爲深藍色,因此再增長着色語句:ui

\color[darkblue]{\switchtobodyfont[rm]{$\langle$ 過程名 $\rangle\equiv$}}

下面是一份完整的 ConTeXt MkIV 源文檔,用於查看上述語句的模擬效果:lua

\usemodule[zhfonts]
\starttext
\starttyping[escape=yes]
/BTEX\color[darkblue]P\switchtobodyfont[rm]{$\langle$過程名$\rangle\equiv$}}/ETEX
\stoptyping
\stoptext

該文檔的編譯結果以下:spa

我以爲尖括號的位置有些靠下,有些不協調,那麼便將它們擡高一些:3d

\usemodule[zhfonts]
\starttext
\starttyping[escape=yes]
/BTEX\color[darkblue]P\switchtobodyfont[rm]{\raise0.1em\hbox{$\langle$}過程名\raise0.1em\hbox{$\rangle\equiv$}}}/ETEX
\stoptyping
\stoptext

\raise0.1em\hbox{$\langle$}\raise0.1em\hbox{$\rangle\equiv$} 可將左尖括號擡高 0.1 字符寬度,這是 TeX 家族慣用的版式微調方法。結果爲

能夠接受。

Handler

如今的問題是,怎樣讓 pretty-c 模塊可以幫我將上面的模擬代碼寫入 ConTeXt MkIV 源文件。pretty-c 模塊的 handler 部分負責此事。handler 是一個函數集:

local handler = visualizers.newhandler {
    ... ... ...,
    boundary     = function(s) CSnippetBoundary(s) end,
    comment      = function(s) CSnippetComment(s)  end,
    string       = function(s) CSnippetString(s) end,
    ... ... ...,
}

這些函數主要用於處理 ConTeXt MkIV 經過 pretty-c 模塊從 C 代碼中識別出來的語句 s

如今假設 ConTeXt MkIV 可以在 C 代碼中識別出「@ 過程名 #」和「# 過程名 @」形式的語句,那麼識別結果就是相應的字串 s,亦即對於「@ 過程名 #」而言,s 是以 @ 開頭,以 # 結尾;對於「# 過程名 @」而言,s 是以 # 開頭,以 @ 結尾。

對於上一節的模擬結果

\color[darkblue]P\switchtobodyfont[rm]{\raise0.1em\hbox{$\langle\,$}過程名\raise0.1em\hbox{$\,\rangle\equiv$}}}

若將「過程名」兩側部分視爲 Lua 字串,對於「@ 過程名 #」和「# 過程名 @」這兩種形式,能夠分離出三個字串:

local langle = "\\color[darkblue]{\\switchtobodyfont[rm]\\raise.1em\\hbox{$\\langle$}"
local rangle = "\\raise.1em\\hbox{$\\rangle$}}"
local rangle_equiv = "\\raise.1em\\hbox{$\\rangle\\equiv$}}"

基於 Lua 的字串鏈接語法,「@ 過程名 #」可表示爲「langle .. x .. rangle_equiv」,而「# 過程名 @」則可表示爲「langle .. x .. rangle」,其中 x 是去掉了兩端的 @# 符號以及空白字符的 s。合成所得的字串,能夠經過調用 ConTeXt MkIV 提供的 context 函數,將其寫入源文檔,例如:

context(langle .. x .. rangle_equiv)

對於 s,要去掉兩段的 @# 符號及空白字符,可使用 Lua 字串模塊提供的 gsub 函數。例如,若 s 爲「@ 過程名 #」形式,可像下面這樣處理:

string.gsub(s, "string.gsub(s, "^@%s*(.-)%s*#$", "%1")

s 爲「# 過程名 @」形式,可像下面這樣處理:

string.gsub(s, "string.gsub(s, "^#%s*(.-)%s*@$", "%1")

string.gsub 處理以後的 s,即是上述的 x

有了這些知識,我即可以在 handler 集合中增長兩個 handler:

local handler = visualizers.newhandler {
    ... ... ...,
    procname     = function(s) context(langle .. string.gsub(s, "^@%s*(.-)%s*#$", "%1") .. rangle_equiv) end,
    procnameref  = function(s) context(langle .. string.gsub(s, "^#%s*(.-)%s*@$", "%1") .. rangle) end,
    ... ... ...,
}

這樣,只要 ConTeXt MkIV 可以經過 pretty-c 模塊從 C 代碼中識別出「@ 過程名 #」和「# 過程名 @」形式的語句,再把它們分別傳遞給上述的兩個 handler 即可以完成相應的排版工做。

Grammar

若讓 ConTeXt MkIV 可以經過 pretty-c 模塊從 C 代碼中識別出「@ 過程名 #」和「# 過程名 @」形式的語句,這須要在 pretty-c 模塊增長兩條語法規則。

對於「@ 過程名 #」形式的語句,它們對應的語法規則若用 LPEG 代碼來描述,最簡單的描述爲「P("@") * (1 - P("#"))^0 * P("#")」,意思是,以 @ 爲開始,以 # 爲結尾,且中間出現 # 的任意字串。同理,對於形如「# 過程名 @」形式的語句,它們對應的語法規則能夠描述爲「P("#") * (1 - P("@"))^0 * P("@")」。

我對 Lua 的 LPEG 庫很陌生。上述代碼裏出現的 1,個人淺薄理解是,它表示任意字符,應當也能夠寫爲 P(1)

可是,上述語法規則,過於簡單,容易致使 ConTeXt MkIV 在識別一些語句時會出如今我看來是錯誤的判斷。例如,

#include <stdio.h>
int main(void)
{
        # 打印 "hello world" @
        return 0;
}

若 ConTeXt MkIV 按照「P("#") * (1 - P("@"))^0 * P("@")」這條規則去識別上述代碼,它會將其「讀」爲:

#include <stdio.h>
int main(void)
{
        # 打印 "hello world" @

由於這部分代碼,是以 # 開頭,以 @ 結尾,而且中間未出現 @

如何解決這樣的問題呢?有三種方法。第一種方法是不解決。第二種方法是換用別的符號來做爲「過程名」的起止符(定界符)。第三種方法是對語法規則進一步給出限定,從而避免混淆。我用第三種方法來解決這個問題。

我對「@ 過程名 #」和「# 過程名 @」形式的語句給出的限定是,「過程名」中不能出現換行符。因而,它們對應的語法規則就變成:「P("@") * (1 - P("#") - newline)^0 * P("#")」和「P("#") * (1 - P("@") - newline)^0 * P("@")」。如此,當 ConTeXt MkIV 遇到 #include <stdio.h> 語句時,它很容易就能夠根據「P("#") * (1 - P("@") - newline)^0 * P("@")」判斷這條語句並不符合這一語法,因而就消除了錯誤。

下面將這兩條語法規則添加到 t-pretty-c.lua 的 grammar 集合:

local grammar = visualizers.newgrammar(
   "default",
   {
      "visualizer",
      ... ... ... ,
      procname    = makepattern(handler, "procname", P("@") * (1 - P("#") - newline)^0 * P("#")),
      procnameref = makepattern(handler, "procnameref", P("#") * (1 - P("@") - newline)^0 * P("@")),
      pattern =
          V("procname")
          + V("procnameref")
          + ... ... ... ,

      ... ... ... ,
   }
}

這樣,ConTeXt MkIV 一旦加載了 pretty-c 模塊,它就具有從 C 代碼中識別「@ 過程名 #」和「# 過程名 @」形式的語句了。

總結

如今,我差很少能夠爲 ConTeXt MkIV 編寫任何一種我熟悉的編程語言的代碼高亮模塊了。


[1] 五光十色
[2] orez 的故事

相關文章
相關標籤/搜索