上一篇见: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,写一个能够计算数字平方根的的宏。