讓這世界再多一份 GNU m4 教程 (3)

上一篇見:http://segmentfault.com/a/1190000004108113編程

註釋符

# 符號是行註釋符。不過,與咱們所熟悉的註釋文本不一樣,m4 的註釋文本會被髮送到輸出流。例如:segmentfault

define(`VERSION',`A1')
VERSION # VERSION `quote' unmatched`

會被展開爲:less

A1 # VERSION `quote' unmatched`

能夠用 changecom 宏修改 m4 默認的註釋符,例如測試

changecom(`@@')

這樣,@@ 就變成了註釋符。編碼

若是你須要塊註釋符,也能夠作到,例如:code

changecom(/*,*/)

若是不向 changecom 提供任何參數,其餘 m4 實現會恢復默認的註釋符,可是 GNU m4 不會恢復默認的註釋符,而是關閉 m4 的註釋功能。若是要恢復默認的註釋符,必須這樣:文檔

changecom(`#')

若是不但願 m4 回顯註釋文本,能夠用 dnl 宏替換註釋符,例如:字符串

define(`VERSION',`A1')
VERSION dnl VERSION `quote' unmatched`

dnl 會將其後的內容一直連同行尾的換行符通通幹掉。get

若是讓塊註釋文本不回顯,須要基於條件語句進行一些 hack。不過,因爲註釋這種東西並無存在的必要,因此就再也不理睬它了。之因此說,註釋不重要,是由於咱們有更強大的註釋機制——文式編程cmd

引號,逃逸以及非 ASCII 字符

m4 有一個不足之處,它沒有專用的逃逸符。對於非引號字符的字符,引號老是能夠做爲逃逸符使用。可是,怎麼對引號自己進行逃逸呢?畢竟不少場合須要左引號字符做爲普通字符出現。

事實上,這篇文檔是用 Markdown 標記寫的,我也沒法將左引號符號以 Markdown 行內代碼標記表現出來。

雖然能夠在引號的外層再封裝一層引號從而將前者變爲普通字符,例如:

I said, ``Quote me.''     # -> I said, `Quote me.'

可是,有些時候你只想以普通文本的形式顯示左引號,不但願出現一個與之配對的右引號。對於這個問題,可使用 changequote 宏修改 m4 默認的引號定界符,例如:

changequote(<!,!>)
a `quoted string

m4 會將其處理爲:

a `quoted string

由於此時,真正的引號是 <!!>

若是不向 changequote 提供任何參數,就恢復了默認的引號定界符。例如:

changequote(<!,!>)dnl
a `quoted string

changequote`'dnl
a `quoted string'

m4 的處理結果爲:

a `quoted string

a quoted string

通常狀況下,應該避免使用 changequote,而是將引號字符定義爲宏:

define(`LQ', `changequote(<,>)`dnl'
changequote`'')

define(`RQ',`changequote(<,>)dnl`
'changequote`'')

m4 遇到 LQ 宏將其展開爲「`」字符,遇到 RQ 宏就將其展開爲「'」字符。這兩個宏的定義所體現的技巧是,臨時的改變 m4 默認的引號定界符,而後再改回來。

不過,有時候須要全局性的修改 m4 的默認引號定界符,例若有些鍵盤上沒有「`」字符,或者 m4 要處理的文本必須將「`」字符視爲普通字符。使用 changequote 必定要當心陷阱:GNU m4 提供的 changequote 與其早期版本以及 m4 的其餘實現有區別。

爲了可移植,要麼向 changequote 提供 2 個參數來調用它,要麼就不提供任何參數,例如:

changequote

changequoe 會改變宏的定義,例如:

define(x,``xyz'')
x                    # -> xyz
changequote({,})
x                    # -> `xyz'

不要用一樣的字符做爲引號的定界符,這樣作,就沒法進行引號的嵌套了。

Markdown 用於格式化行內代碼的標記用的就是相同的『左引號』與『右引號』……這樣的錯誤,誕生於上個世紀 70 年代的 m4 沒有犯。

不要將引號定界符更改成以字母、下劃線或數字開頭的字符。m4 雖然不反對這樣作,可是它不認爲這種字符是引號定界符。數字做爲引號定界符,雖然能夠被 m4 承認,可是當它做爲一個記號自己的組成元素時,它就失去了引號定界符的身份了。

如今的 GNU m4 能夠支持非 ASCII 字符,所以也能夠用它們來做爲引號定界符,例如:

changequote(左引號, 右引號)
a 左引號quoted string右引號   # -> a quoted string

define(我是宏, 我知道你是宏)
我是宏

可是最好不要這麼幹,特別是不要將它們用於宏名。由於,使用 8 位寬的字符,就已經讓 m4 行爲有些怪異了。GNU m4 1.4.17 版本(本文寫做過程當中所用的 m4 版本)的手冊中說:GNU m4 不理解多字節文本,它只是將文本視爲以字節爲單位的數據,而且支持 8 位寬的字符做爲宏名與引號定界符,但 NUL 字符(即 '\0')除外。

m4 能處理中文,這是一種巧合。這種巧合應該只發生在 UTF-8 編碼的輸入流中。由於 UTF-8 的編碼機制決定了中文字符的任何一個字節都與 ASCII 碼不一樣。若是是 GB2312,GB18030 這樣的字符集,或許就沒有這麼好的運氣了

條件

m4 提供了兩種條件宏,ifdef 宏用於判斷宏是否認義,ifelse 宏是判斷表達式的真假。

ifdef(`a', b)

對於上述條件宏,若是 a 是已定義的宏,那麼這條語句的展開結果是 b

ifdef(`a', b, c)

對於上述條件宏,若是 a 是未定義的宏,這條語句的展開結果是 c

被測試的宏,它的定義能夠是空字串,例如:

define(`def')
`def' is ifdef(`def', , not) defined.  # -> def is defined.

ifelse(a,b,c,d) 會比較字符串 ab 是否相同,若是它們相同,這條語句的展開結果是字符串 c,不然展開爲字符串 d

ifelse 能夠支持多個分支,例如:

ifelse(a,b,c,d,e,f,g)

它等價於:

ifelse(a,b,c,ifelse(d,e,f,g))

數字

m4 只認識文本,因此在它看來,數字也是文本。不過 m4 提供了內建宏 eval,這個宏能夠對整型數的運算表達式進行『求值』——求值結果在 m4 看來依然是文本。

例如:

define(`n', 1)dnl
`n' is ifelse(eval(n < 2), 1, less than, eval(n == 2), 1, , greater than) 2

eval(n < 2) 是對 n < 2 這個邏輯表達式進行『求值』,結果是字符串 1,所以 ifelse 的第一個參數與第二個參數相等,所以 ifelse 宏的展開結果是其第三個參數 less than,因此展開結果爲:

n is less than 2

我以爲不必用 m4 來計算,由於它提供的計算功能太孱弱。能夠考慮用 GNU bc 來彌補它的不足。在 m4 中,能夠經過 esyscmd 宏訪問 Shell,例如:

2.1 ifelse(eval(esyscmd(`echo "2.1 > 2.0" | bc')), 1, `greater than', `less than') 2.0

展開結果爲:

2.1 greater than 2.0

不過,esyscmd 是 GNU m4 對 syscmd 的擴展,別的 m4 的實現可能沒有這個宏。

挑戰

(1) 若是用 m4 處理 C 代碼文件,將 # 符號做爲 m4 的行註釋符,會有哪些顯而易見的好處?

(2) 藉助 GNU m4 提供的 esyscmd 宏,結合 GNU bc,寫一個能夠計算數字平方根的的宏。

下一篇見:http://segmentfault.com/a/1190000004131031

相關文章
相關標籤/搜索