上一篇見: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
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)
會比較字符串 a
與 b
是否相同,若是它們相同,這條語句的展開結果是字符串 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,寫一個能夠計算數字平方根的的宏。