注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

天道酬勤 玩物丧志

用勇气去改变可以改变的事情,用胸怀去包容无法改变的事情,用智慧去判断两者的区别

 
 
 

日志

 
 

让这世界再多一份GNU m4 教程 (全文整理)下  

2017-10-06 17:34:46|  分类: 默认分类 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

作者:garfileo  作者主页

本文整理自:https://segmentfault.com/a/1190000004104696

整理者:Timekeeperl    csdn博客

欢迎转载!请注明作者与出处!

注释符

de style="line-height: 24.5px;" >  #de> 符号是行注释符。不过,与我们所熟悉的注释文本不同,m4 的注释文本会被发送到输出流。例如:

de style="line-height: 24.5px;"  >de>
  1. define(`VERSION',`A1')  
  2. VERSION # VERSION `quote' unmatched`  

会被展开为:

de style="line-height: 24.5px; white-space: initial;"  >de>
  1. A1 # VERSION `quote' unmatched`  

可以用 de style="line-height: 24.5px;" >changecomde> 宏修改 m4 默认的注释符,例如

de style="line-height: 24.5px; white-space: initial;"  >de>
  1. changecom(`@@')  

这样,de style="line-height: 24.5px;" >@@de> 就变成了注释符。

如果你需要块注释符,也可以做到,例如:

de style="line-height: 24.5px; white-space: initial;"  >de>
  1. changecom(/*,*/)  

       如果不向 de style="line-height: 24.5px;" >changecomde> 提供任何参数,其他 m4 实现会恢复默认的注释符,但是 GNU m4 不会恢复默认的注释符,而是关闭 m4 的注释功能。如果要恢复默认的注释符,必须这样:

de style="line-height: 24.5px; white-space: initial;"  >de>
  1. changecom(`#')  

如果不希望 m4 回显注释文本,可以用 de style="line-height: 24.5px;" >dnlde> 宏替换注释符,例如:

de style="line-height: 24.5px;"  >de>
  1. define(`VERSION',`A1')  
  2. VERSION dnl VERSION `quote' unmatched`  

de style="line-height: 24.5px;" >dnlde> 会将其后的内容一直连同行尾的换行符统统干掉。

如果让块注释文本不回显,需要基于条件语句进行一些 hack。不过,由于注释这种东西并没有存在的必要,所以就不再理睬它了。之所以说,注释不重要,是因为我们有更强大的注释机制——文式编程


引号,逃逸以及非 ASCII 字符

       m4 有一个不足之处,它没有专用的逃逸符。对于非引号字符的字符,引号总是可以作为逃逸符使用。但是,怎么对引号本身进行逃逸呢?毕竟很多场合需要左引号字符作为普通字符出现。

事实上,这篇文档是用 Markdown 标记写的,我也无法将左引号符号以 Markdown 行内代码标记表现出来。

虽然可以在引号的外层再封装一层引号从而将前者变为普通字符,例如:

de style="line-height: 24.5px; white-space: initial;"  >de>
  1. I said, ``Quote me.'' # -> I said, `Quote me.'  

但是,有些时候你只想以普通文本的形式显示左引号,不希望出现一个与之配对的右引号。对于这个问题,可以使用 de style="line-height: 24.5px;" >changequotede> 宏修改 m4 默认的引号定界符,例如:

de style="line-height: 24.5px;"  >de>
  1. changequote(<!,!>)  
  2. a `quoted string  

m4 会将其处理为:

de style="line-height: 24.5px; white-space: initial;"  >de>
  1. a `quoted string  

因为此时,真正的引号是 de style="line-height: 24.5px;" ><!de> 与 de style="line-height: 24.5px;" >!>de>。

如果不向 de style="line-height: 24.5px;" >changequotede> 提供任何参数,就恢复了默认的引号定界符。例如:

de style="line-height: 24.5px;"  >de>
  1. changequote(<!,!>)dnl  
  2. a `quoted string  
  3.   
  4. changequote`'dnl  
  5. a `quoted string'  

m4 的处理结果为:

de style="line-height: 24.5px;"  >de>
  1. <pre name="code" class="cpp">a `quoted string  
  2.   
  3. a quoted string  

一般情况下,应该避免使用 de style="line-height: 24.5px;" >changequotede>,而是将引号字符定义为宏:

de style="line-height: 24.5px;"  >de>
  1. define(`LQ', `changequote(<,>)`dnl'  
  2. changequote`'')  
  3.   
  4. define(`RQ',`changequote(<,>)dnl`  
  5. 'changequote`'')  

       m4 遇到 de style="line-height: 24.5px;" >LQde> 宏将其展开为「`」字符,遇到 de style="line-height: 24.5px;" >RQde> 宏就将其展开为「'」字符。这两个宏的定义所体现的技巧是,临时的改变 m4 默认的引号定界符,然后再改回来。

不过,有时候需要全局性的修改 m4 的默认引号定界符,例如有些键盘上没有「`」字符,或者 m4 要处理的文本必须将「`」字符视为普通字符。使用de style="line-height: 24.5px;" >changequotede> 一定要小心陷阱:GNU m4 提供的 de style="line-height: 24.5px;" >changequotede> 与其早期版本以及 m4 的其他实现有区别。

为了可移植,要么向 de style="line-height: 24.5px;" >changequotede> 提供 2 个参数来调用它,要么就不提供任何参数,例如:

de style="line-height: 24.5px; white-space: initial;"  >de>
  1. changequote  

de style="line-height: 24.5px;" >changequoede> 会改变宏的定义,例如:

de style="line-height: 24.5px;"  >de>
  1. define(x,``xyz'')  
  2. x                    # -> xyz  
  3. changequote({,})  
  4. x                    # -> `xyz'  

       不要用同样的字符作为引号的定界符,这样做,就无法进行引号的嵌套了。

Markdown 用于格式化行内代码的标记用的就是相同的『左引号』与『右引号』……这样的错误,诞生于上个世纪 70 年代的 m4 没有犯。

       不要将引号定界符更改为以字母、下划线或数字开头的字符。m4 虽然不反对这样做,但是它不认为这种字符是引号定界符。数字作为引号定界符,虽然可以被 m4 认可,但是当它作为一个记号本身的组成元素时,它就失去了引号定界符的身份了。

现在的 GNU m4 可以支持非 ASCII 字符,因此也可以用它们来作为引号定界符,例如:

de style="line-height: 24.5px;"  >de>
  1. changequote(左引号, 右引号)  
  2. a 左引号quoted string右引号   # -> a quoted string  
  3.   
  4. define(我是宏, 我知道你是宏)  
  5. 我是宏  

       但是最好不要这么干,特别是不要将它们用于宏名。因为,使用 8 位宽的字符,就已经让 m4 行为有些怪异了。GNU m4 1.4.17 版本(本文写作过程中所用的 m4 版本)的手册中说:GNU m4 不理解多字节文本,它只是将文本视为以字节为单位的数据,并且支持 8 位宽的字符作为宏名与引号定界符,但de style="line-height: 24.5px;" >NULde> 字符(即de style="line-height: 24.5px;" >'\0'de>)除外。

m4 能处理中文,这是一种巧合。这种巧合应该只发生在 UTF-8 编码的输入流中。因为 UTF-8 的编码机制决定了中文字符的任何一个字节都与 ASCII 码不同。如果是 GB2312,GB18030 这样的字符集,或许就没有这么好的运气了


条件

m4 提供了两种条件宏,de style="line-height: 24.5px;" >ifdefde> 宏用于判断宏是否定义,de style="line-height: 24.5px;" >ifelsede> 宏是判断表达式的真假。

de style="line-height: 24.5px; white-space: initial;"  >de>
  1. ifdef(`a', b)  

对于上述条件宏,如果 de style="line-height: 24.5px;" >ade> 是已定义的宏,那么这条语句的展开结果是 de style="line-height: 24.5px;" >bde>。

de style="line-height: 24.5px; white-space: initial;"  >de>
  1. ifdef(`a', b, c)  

对于上述条件宏,如果 de style="line-height: 24.5px;" >ade> 是未定义的宏,这条语句的展开结果是 de style="line-height: 24.5px;" >cde>。

被测试的宏,它的定义可以是空字串,例如:

de style="line-height: 24.5px;"  >de>
  1. define(`def')  
  2. `def' is ifdef(`def', , not) defined.  # -> def is defined.  

de style="line-height: 24.5px;" >ifelse(a,b,c,d)de> 会比较字符串 de style="line-height: 24.5px;" >ade> 与 de style="line-height: 24.5px;" >bde> 是否相同,如果它们相同,这条语句的展开结果是字符串de style="line-height: 24.5px;" >cde>,否则展开为字符串de style="line-height: 24.5px;" >dde>。

de style="line-height: 24.5px;" >ifelsede> 可以支持多个分支,例如:

de style="line-height: 24.5px; white-space: initial;"  >de>
  1. ifelse(a,b,c,d,e,f,g)  

它等价于:

de style="line-height: 24.5px; white-space: initial;"  >de>
  1. ifelse(a,b,c,ifelse(d,e,f,g))   

数字

       m4 只认识文本,所以在它看来,数字也是文本。不过 m4 提供了内建宏 de style="line-height: 24.5px;" >evalde>,这个宏可以对整型数的运算表达式进行『求值』——求值结果在 m4 看来依然是文本。

例如:

de style="line-height: 24.5px;"  >de>
  1. define(`n', 1)dnl  
  2. `n' is ifelse(eval(n < 2), 1, less than, eval(n == 2), 1, , greater than) 2  

de style="line-height: 24.5px;" >eval(n < 2)de> 是对 de style="line-height: 24.5px;" >n < 2de> 这个逻辑表达式进行『求值』,结果是字符串 de style="line-height: 24.5px;" >1de>,因此de style="line-height: 24.5px;" >ifelsede> 的第一个参数与第二个参数相等,因此de style="line-height: 24.5px;" >ifelsede> 宏的展开结果是其第三个参数 de style="line-height: 24.5px;" >less thande>,所以展开结果为:

de style="line-height: 24.5px; white-space: initial;"  >de>
  1. n is less than 2  

我觉得没必要用 m4 来计算,因为它提供的计算功能太孱弱。可以考虑用 GNU bc 来弥补它的不足。在 m4 中,可以通过 de style="line-height: 24.5px;" >esyscmdde> 宏访问 Shell,例如:

de style="line-height: 24.5px; white-space: initial;"  >de>
  1. 2.1 ifelse(eval(esyscmd(`echo "2.1 > 2.0" | bc')), 1, `greater than', `less than') 2.0  

展开结果为:

de style="line-height: 24.5px; white-space: initial;"  >de>
  1. 2.1 greater than 2.0  

不过,de style="line-height: 24.5px;" >esyscmdde> 是 GNU m4 对 de style="line-height: 24.5px;" >syscmdde> 的扩展,别的 m4 的实现可能没有这个宏。


挑战

(1) 如果用 m4 处理 C 代码文件,将 de style="line-height: 24.5px;" >#de> 符号作为 m4 的行注释符,会有哪些显而易见的好处?

(2) 借助 GNU m4 提供的 de style="line-height: 24.5px;" >esyscmdde> 宏,结合 GNU bc,写一个可以计算数字平方根的的宏。



递归

       现在再强调一次,m4 会将当前宏的展开结果插入到待读取的输入流的前端。也就是说,m4 会对当前宏的展开结果再次进行扫描,以处理嵌套的宏调用。这个性质决定了可以写出让 m4 累死的递归宏。

例如:

de style="line-height: 24.5px;"  >de>
  1. define(`TEST', `TEST')  
  2. TEST  

       当 m4 试图对 de style="line-height: 24.5px;" >TESTde> 进行展开时,它就会永无休止的去展开 de style="line-height: 24.5px;" >TESTde>,而每次展开的结果依然是de style="line-height: 24.5px;" >TESTde>。

       既然有递归,那么就可以利用递归来做一些计算,为了让递归能够结束,这就需要 m4 能够支持条件。幸好,我们已经知道 m4 是支持条件的。下面,是一个递归版本的 Fibonacci 宏的实现与应用,它可以产生第 47 个 Fibonacci 数:

de style="line-height: 24.5px;"  >de>
  1. divert(-1)  
  2. define(`FIB',  
  3. `ifelse(`$1', `0',  
  4.          0,  
  5.         `ifelse(`$1', `1',  
  6.                  1,  
  7.                 `eval(FIB(eval($1 - 1)) + FIB(eval($1-2)))')')')  
  8. divert(0)dnl  
  9. FIB(46)  

       m4 的展开结果应该是 de style="line-height: 24.5px;" >1836311903de>。也许你要等很久才会看到这个结果。因为递归的 Fibonacci 数计算过程中包含着大量的重复计算,效率很低。

       不过,迭代版本的 Fibonacci 数计算过程也能写得出来:

de style="line-height: 24.5px;"  >de>
  1. divert(-1)  
  2. define(`FIB_ITER',  
  3. `ifelse(`$3', 0,  
  4.          $2,  
  5.      `FIB_ITER(eval($1 + $2), $1, eval($3 - 1))')')  
  6. define(`FIB', `FIB_ITER(1, 0, $1)')  
  7. divert(0)dnl  
  8. FIB(46)<span style="font-size:14px;"><code class="m4"><span class="hljs-string"></span></code></span>  

       迭代计算很快,在我的机器上只需要 0.002 秒就可以得出 de style="line-height: 24.5px;" >1836311903de> 这个结果。不过,如果想尝试比 46 更大的数,比如de style="line-height: 24.5px;" >FIB(47)de>,结果就会出现负数。这是因为 m4 目前只支持 32 位的有符号整数,它能表示的最大正整数是 2^31 - 1,而de style="line-height: 24.5px;" >FIB(47)de> 的结果会大于这个数。


循环

既然有递归,那么就可以用它来模拟循环。例如:

de style="line-height: 24.5px;"  >de>
  1. define(`for',  
  2. `ifelse($#,  
  3.         0,  
  4.         ``$0'',  
  5.         `ifelse(eval($2 <= $3),  
  6.                 1,  
  7.                `pushdef(`$1',$2)$4`'popdef(`$1')$0(`$1', incr($2), $3, `$4')')')')  

这个 de style="line-height: 24.5px;" >forde> 宏可以像下面这样调用:

de style="line-height: 24.5px; white-space: initial;"  >de>
  1. for(`i', 1, n, `循环内的计算')  

它类似于 C 语言中的 de style="line-height: 24.5px;" >forde> 循环:

de style="line-height: 24.5px;"  >de>
  1. for(int i = 1; i <= n; i++) {  
  2.         循环内的计算  
  3. }  

例如,可以用 de style="line-height: 24.5px;" >forde> 宏将 64 个 de style="line-height: 24.5px;" >-de> 符号发送到输出流:

de style="line-height: 24.5px; white-space: initial;"  >de>
  1. for(`i', 1, 64, `-')  

这个宏的展开结果为:

de style="line-height: 24.5px; white-space: initial;"  >de>
  1. ----------------------------------------------------------------  

       如果你用过 reStructuredText 标记语言,一定会知道怎么用 de style="line-height: 24.5px;" >forde> 宏构建一个协助你构造一个用于快速撰写 reStructuredText 标题标记的宏。

要理解 de style="line-height: 24.5px;" >forde> 宏的定义,有几个 m4 小知识需要补习一下。请向下看。


宏参数列表的特征值

       我们已经知道 de style="line-height: 24.5px;" >$1, $2, ..., $9de> 对应于宏参数列表中的各个参数(GNU m4 不限定参数的个数,其他 m4 实现最多支持 9 个参数)。如果对 C 或 Bash 有所了解,那么当我说de style="line-height: 24.5px;" >$0de> 是宏本身,估计不会觉得很奇怪。因此,在上一节de style="line-height: 24.5px;" >forde> 宏定义中,de style="line-height: 24.5px;" >$0de> 表示引用了宏名de style="line-height: 24.5px;" >forde>。不妨将 de style="line-height: 24.5px;" >$0de> 改成de style="line-height: 24.5px;" >forde> 试一下。

de style="line-height: 24.5px;" >$#de> 表示宏参数的个数。例如:

  1. define(`count', ``$0': $# args')  
  2. count        # -> count: 0 args  
  3. count()      # -> count: 1 args  
  4. count(1)     # -> count: 1 args  
  5. count(1,)    # -> count: 2 args<span style="font-size:14px;"></span>  

de style="line-height: 24.5px;" >#de> 是注释符,de style="line-height: 24.5px;" >->de> 后面的文本是 m4 对注释符号之前的文本处理后发送到输出流的结果。

值得注意的是,即使 de style="line-height: 24.5px;" >()de> 内什么也没有,m4 也会认为 de style="line-height: 24.5px;" >countde> 宏是有一个参数的,它是空字串。

de style="line-height: 24.5px;" >forde> 的定义中,第一处条件语句为de style="line-height: 24.5px;" >:de>

de style="line-height: 24.5px;" >de>

  1. ifelse($#,  
  2.         0,  
  3.         ``$0'',  
  4.         ... ...)  

它的作用就是告诉 m4,遇到 de style="line-height: 24.5px;" >forde> 的调用语句,如果 de style="line-height: 24.5px;" >forde> 的参数个数为 0,那么 de style="line-height: 24.5px;" >forde> 的展开结果为带引号的字符串:

de style="line-height: 24.5px; white-space: initial;"  >de>
  1. `for'  

       要理解为什么在条件语句中,de style="line-height: 24.5px;" >forde> 用两重引号包围起来,你需要再认真的复习一次 m4 的宏展开过程。如果用单重引号,那么以无参数的形式调用de style="line-height: 24.5px;" >forde>宏时,m4 会陷入对de style="line-height: 24.5px;" >forde> 宏无限次的展开过程中。


宏的作用域

所有的宏都是全局的

       如果我们需要『局部宏』该怎么做?也就是说,如何将一个宏只在另一个宏的定义中使用?局部宏的意义就类似于编程语言中的局部变量,如果没有局部宏,那么在一个全局的空间中,很容易出现宏名冲突,导致宏被意外的重定义了。

       为了避免宏名冲突,一种可选的方法是在宏名之前加前缀,比如使用 de style="line-height: 24.5px;" >localde> 作为局部宏名的前缀。不过,这种方法对于递归宏无效。更好的方法是用

       m4 实际上是用一个栈来维护宏的定义的。当前宏的定义位于栈顶。使用 de style="line-height: 24.5px;" >pushdefde> 可以将一个临时定义的宏压入栈中,在利用完这个临时的宏之后,再用de style="line-height: 24.5px;" >popdefde> 将其弹出栈外。例如:

de style="line-height: 24.5px;"  >de>
  1. define(`USED',1)  
  2. define(`proc',  
  3.   `pushdef(`USED',10)pushdef(`UNUSED',20)dnl  
  4. `'`USED' = USED, `UNUSED' = UNUSED`'dnl  
  5. `'popdef(`USED',`UNUSED')')  
  6. proc     # -> USED = 10, UNUSED = 20  
  7. USED     # -> 1  

       如果被压入栈的宏是未定义的宏,那么 de style="line-height: 24.5px;" >pushdefde> 就相当于 de style="line-height: 24.5px;" >definede>。如果 de style="line-height: 24.5px;" >popdefde> 弹出的宏也是未定义的宏,de style="line-height: 24.5px;" >popdefde> 就相当于de style="line-height: 24.5px;" >undefinede>,它不会产生任何抱怨。

       GNU m4 认为 de style="line-height: 24.5px;" >define(X, Y)de> 与 de style="line-height: 24.5px;" >popdef(X)pushdef(X, Y)de> 等价。其他的 m4 实现会认为de style="line-height: 24.5px;" >define(X)de> 等价于de style="line-height: 24.5px;" >undefine(X)define(X, Y)de>,也就是说,新的宏的定义会更新整个栈。de style="line-height: 24.5px;" >undefine(X)de> 就是取消de style="line-height: 24.5px;" >Xde> 宏的定义,使之成为未定义的宏。


让宏名更安全

m4 有一个 de style="line-height: 24.5px;" >-Pde> 选项,它可以强制性的在其内建宏名之前冠以 de style="line-height: 24.5px;" >m4_de> 前缀。例如下面的 M1.m4 文件:

de style="line-height: 24.5px;"  >de>
  1. define(`M1',`text1')M1          # -> define(M1,text1)M1  
  2. m4_define(`M1',`text1')M1       # -> text1  

直接用 m4 处理,结果为de style="line-height: 24.5px;" >:de>

de style="line-height: 24.5px;" >de>

  1. $ m4 M1.m4  
  2. text1          # -> define(M1,text1)M1  
  3. m4_define(M1,text1)text1       # -> text1  

如果用 de style="line-height: 24.5px;" >m4 -Pde> 来处理,结果为:

de style="line-height: 24.5px;"  >de>
  1. $ m4 -P test.m4  
  2. define(M1,text1)M1          # -> define(M1,text1)M1  
  3. text1       # -> text1  
  4. <code class="m4"><span class="hljs-comment">  
  5. </span></code>  

挑战

理解 de style="line-height: 24.5px;" >forde> 宏的定义。




难以驾驭的引号

       对于自己定义的宏,建议先在你的大脑中对它进行逐步展开,确信自己完全理解这个展开过程。如果大脑的堆栈不够用,可以用纸和笔记录展开过程。这样可以在很大程度上提高宏定义的正确性。

       m4 宏调用的复杂之处在于嵌套的宏调用——在一个宏的展开结果中调用了其他宏。例如,宏 de style="line-height: 24.5px;" >Ade> 的展开结果中调用了宏 de style="line-height: 24.5px;" >Xde>,如果期望de style="line-height: 24.5px;" >Xde> 先于 de style="line-height: 24.5px;" >Ade> 被 m4 展开,那么在 de style="line-height: 24.5px;" >Ade> 的定义中就不要在de style="line-height: 24.5px;" >Xde> 的外围加引号。如果在期望de style="line-height: 24.5px;" >Ade> 展开后,当 m4 再度读取 de style="line-height: 24.5px;" >Ade> 的展开结果的过程中再展开de style="line-height: 24.5px;" >Xde>,那么 de style="line-height: 24.5px;" >Xde> 的外围必须要有引号。再复杂一些,如果宏 de style="line-height: 24.5px;" >Xde> 的展开结果中又调用了宏 de style="line-height: 24.5px;" >Yde>,并且期望 de style="line-height: 24.5px;" >Yde> 是在 m4 再度读取 de style="line-height: 24.5px;" >Xde> 展开结果的过程中被展开,那么 de style="line-height: 24.5px;" >Yde> 的外围也必须要有一重引号,此时因为de style="line-height: 24.5px;" >Xde> 外围已经有了一重引号,那么de style="line-height: 24.5px;" >Yde> 实际上是处于两重引号的包裹之中。

       m4 处理引号的基本规则是:在读取带引号的文本片段 S 时,无论 S 中含有多少重引号,m4 只消除其最外层引号,然后将剩余的文本直接发送到输出流。这个规则很简单,之前已经提到过一次。需要注意的是,如果在宏的参数列表中出现了引号,一定要记住宏的参数列表总是在宏展开之前被处理的。看下面的例子:

de style="line-height: 24.5px;"  >de>
  1. define(`echo', `$1')  
  2. define(`test', echo($1))  
  3. test(test)  

       在 de style="line-height: 24.5px;" >testde> 宏定义过程中,de style="line-height: 24.5px;" >echo($1)de> 先被 m4 展开了,结果为空字串,导致 de style="line-height: 24.5px;" >testde> 宏定义语句中的宏体变成空字串,即:

de style="line-height: 24.5px; white-space: initial;"  >de>
  1. define(`test', `')  

接下来,de style="line-height: 24.5px;" >test(test)de> 是嵌套的宏调用,括号内的 de style="line-height: 24.5px;" >testde> 会先被展开,展开结果是空字串,导致括号外面的de style="line-height: 24.5px;" >testde> 被展开之前的形式变为:

de style="line-height: 24.5px; white-space: initial;"  >de>
  1. test()  

       此时,de style="line-height: 24.5px;" >testde> 宏接受了一个参数——空字串,然后它会被 m4 展开,展开结果为空字串。这个结果并非是因为 de style="line-height: 24.5px;" >testde> 宏接受了空字串参数所导致的。

现在改动一下 de style="line-height: 24.5px;" >testde> 的定义:

de style="line-height: 24.5px;"  >de>
  1. define(`echo', `$1')  
  2. define(`test',`echo($1)')  
  3. test(test)<span style="font-size:14px;"><code class="m4"></code></span>  

       由于引号的抑制作用,de style="line-height: 24.5px;" >testde> 宏体中的 de style="line-height: 24.5px;" >echode> 不会先于 de style="line-height: 24.5px;" >testde> 定义完成之前被 m4 展开。de style="line-height: 24.5px;" >test(test)de> 的宏展开次序依然同上,m4 先展开括号里面的de style="line-height: 24.5px;" >testde>,得到:

de style="line-height: 24.5px; white-space: initial;"  >de>
  1. test(echo())  


       然后,m4 不会去展开括号外层的 de style="line-height: 24.5px;" >testde>,而是先去展开括号里面的 de style="line-height: 24.5px;" >echode> 宏,因为它认为括号里面的文本是括号外面的de style="line-height: 24.5px;" >testde> 宏的参数,结果变为:

de style="line-height: 24.5px; white-space: initial;"  >de>
  1. test()  

       接下来,de style="line-height: 24.5px;" >test()de> 会被展开为空字串。

       下面改动一下 de style="line-height: 24.5px;" >testde> 宏调用语句:

de style="line-height: 24.5px;"  >de>
  1. define(`echo', `$1')  
  2. define(`test',`echo($1)')  
  3. test(`test')  

       这时,括号里面的 de style="line-height: 24.5px;" >testde> 就不再是宏调用了,而是括号外面的 de style="line-height: 24.5px;" >testde> 宏的一个参数。de style="line-height: 24.5px;" >test(de>testde style="line-height: 24.5px;" >)de> 宏会被展开为:

de style="line-height: 24.5px; white-space: initial;"  >de>
  1. echo(test)  

       由于 m4 会将宏的展开结果插入到剩余的输入流中继续读取并处理,所以上述结果被进一步处理为:

de style="line-height: 24.5px; white-space: initial;"  >de>
  1. echo(echo())  

       再进一步处理为:

de style="line-height: 24.5px; white-space: initial;"  >de>
  1. echo()  

       最终的处理结果依然是一个空字串。

       虽然这两次改动并没有得到新的结果,但是显然宏展开的过程并不相同。宏参数中的引号的作用并不是那么显而易见。大部分 m4 宏出错,宏参数中的引号往往是首恶元凶。要驾驭它,只能凭借自己的明确的逐步推导。这也导致了一个问题,很难用 m4 描述复杂的宏逻辑。

作为一次小挑战,请用笔在纸上推导下面 m4 宏的展开结果:

  1. define(`echo', `$1')  
  2. define(`test',`echo($1)')  
  3. test(``test'')  

       然后使用 de style="line-height: 24.5px;" >m4 -dV your-m4-filede> 印证自己的推导。注意, de style="line-height: 24.5px;" >m4 -dVde> 所显示的宏展开过程,会对每个宏的展开结果包装一层引号,这其实是多余的引号,它只代表 m4 对宏的展开结果总是字符串。


非法的宏名

       下面这个宏定义:

de style="line-height: 24.5px; white-space: initial;"  >de>
  1. define(`?N?', 1)  

       m4 会认为 de style="line-height: 24.5px;" >?N?de> 这个宏是不合法的,因为合法的宏的名字必须要遵守正则表达式 de style="line-height: 24.5px;" >[_a-zA-Z][_a-zA-Z0-9]*de>。不过,GNU m4 是仁慈的,对于不合法的宏,它依然能展开,前提是借助 m4 内建的de style="line-height: 24.5px;" >defnde> 宏:

de style="line-height: 24.5px;"  >de>
  1. ?N?           # -> ?N?  
  2. defn(`?N?')   # -> 1  

       非法的宏名可以用来模拟数组或 Hash 表,例如:

de style="line-height: 24.5px;"  >de>
  1. define(`_set', `define(`$1[$2]', `$3')')  
  2. define(`_get', `defn(`$1[$2]')')  
  3. _set(`myarray', 1, `alpha')  
  4. _get(`myarray', 1)                  # -> alpha  
  5. _set(`myarray', `alpha', `omega')  
  6. _get(`myarray', _get(`myarray',1))  # -> omega  
  7. defn(`myarray[alpha]')              # -> omega  
  8.   
  9. <code class="m4"><span class="hljs-comment"></span></code>  

外援

       GNU m4 内建了几个与 Shell 交互的宏,诸如 de style="line-height: 24.5px;" >syscmdde>, de style="line-height: 24.5px;" >esyscmdde>, de style="line-height: 24.5px;" >sysvalde>,de style="line-height: 24.5px;" >mkstempde> 等,其中最有用的是 de style="line-height: 24.5px;" >esyscmdde>,因为它不仅能访问 Shell,而且还能获取 Shell 命令产生的输出。例如,下面这行 m4 代码可以借助 Shell 调用 GNU guile——GNU 的 Scheme 解释器来计算阶乘:

de style="line-height: 24.5px;"  >de>
  1. esyscmd(`guile -c "(define (factorial n)  
  2.                      (if (= n 1)  
  3.                       1  
  4.                      (* n (factorial (- n 1)))))  
  5.                    (display (factorial 100))"')  

       如果你的系统中安装了 GNU guile,并且有一个 Shell 可用(既然是 m4 用户,系统中没有 Shell 说不过去的),那么 m4 对上述de style="line-height: 24.5px;" >esyscmdde> 宏的展开结果为:

  1. 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000  


这样写也行:

  1. define(`scheme_code',  
  2. `"`(define (factorial n)  
  3.    (if (= n 1)  
  4.     1  
  5.     (* n (factorial (- n 1)))))  
  6.  (display (factorial 100))'"')  
  7.   
  8. esyscmd(`guile -c' scheme_code)  


       凡是能在 Shell 中运行并产生输出的程序,皆能被 GNU m4 所用,这是不是很神奇?



文本处理

       我一直都忍着不去谈 GNU m4 针对文本处理提供的几个内建宏,主要是因为既然有 de style="line-height: 24.5px;" >esyscmdde> 这样的宏可用,那么类 Unix 系统中那些很无敌的文本处理工具,诸如 tr, cut, paste, wc, md5sum, sed, awk, grep/egrep 等等,它们皆能被 m4 所用,那么何必再多此一举?

倘若是为了让 m4 脚本更具备可移植性,那么最好是将一个比较完整的 Shell 环境移植到目标平台……对于主流操作系统而言,这并不是太困难的事,因为已经有了很多针对不同操作系统的完整的 Shell 环境实现。

如果依然坚持用 m4 的方式处理文本,建议阅读:『GNU m4 Text Handling』。



结束语

       这份 GNU m4 指南至此终结。作为学习者,务必要记住 m4 官方手册的告诫之语:有些人对 m4 非常着迷,他们先是用 m4 解决一些简单的问题,然后解决了一个比一个更大的问题,直至掌握如何编写一个复杂的 m4 宏集。若痴迷于此,往往会对一些简单的问题写出复杂的 m4 脚本,然后耗费很多时间去调试,反而不如直接手动解决问题更有效。所以,对于程序猿中的强迫症患者,要对 m4 有所警惕,它可能会危及你的健康。

如果不想让 m4 危及你的健康,永远要记住:宏是用来缩写那些复杂但是又经常重复出现的文本模式的。

  评论这张
 
阅读(29)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2018