GNU m4 教程 Notes

reference

GNU m4 简介

m4 是一种宏处理器, 它扫描用户输入的文本并将其输出, 期间如果遇到宏就将其展开后输出.

除展开宏, m4 内建的宏能够加载文件, 执行Shell命令, 做整数运算, 操纵文本, 形成递归等.

m4 基本工作过程

在”C Programming Language” 中将(stream), 定义为与磁盘或其它外围设备关联的数据的源或目的地. 也就可以理解为文件.

由此, 输入流就是与磁盘或其他外围设备关联的数据的源.

输出流就是与磁盘或其他外围设备关联的数据的源或目的地.

m4 工作空间

需要一个来状态寄存器判断当前从输入流中读取的文本是宏还是非宏.

有一个缓存空间, 用于提高文本效率. 其容量为512KB, 当它满了的时候, m4会自动将其中的内容妥善保存到一份临时文件中备用. 这里官方的概念为转移(Diversion).

使用divert宏, 在各缓存中切换, 有十种级别的缓存.

1
divert(3)

最后, m4会将各个缓存中的文本汇总到0号缓存中.

缓存的汇总过程是按照缓存级别进行的. 最后会将0号缓存中的内容依序发送到输出流中.

文本流经m4的过程不可逆.

逆向工程能在一定程度上复原某个程序的代码, 但它却永远无法基于宏的展开重现宏的定义.

暗黑缓存

编号为负数, 不限数量.

m4 不会将暗黑缓存汇总的内容发送到输出流.

暗黑缓存的主要作用是作为宏定义的空间.

长度为零的字符串被发送到输出流, 不会对其产生影响.

1
2
3
divert(0)
define(say_hello_world, Hello World!)
say_hello_world

divert(0) 及其他语句后面有一个换行符, 会对输出产生影响.

1
$ m4 hello.m4

使用暗黑缓存以避免一个换行符的影响:

1
2
3
4
divert(-1)
define(say_hello_world, Hello World!)
divert(0)
say_hello_world

调用时避免换行符的影响:

  • divert(0)后不换行
  • 使用m4内建的dnl宏
    1
    2
    3
    4
    divert(-1)
    define(say_hello_world, Hello World!)
    divert(0)dnl
    say_hello_world

define关键字用于定义宏:

1
define(hello, HELLO)

前一个是宏, 后一个是展开的值.

define本身就是一个宏, 也会被m4展开, 只不过它的展开结果是一个空字符串.

有参数的宏

宏可以有参数.

遵循POSIX标准的m4, 允许一个宏最多有9个参数. 在宏体中可使用$1,…,$9来引用. GNU的m4不限制宏的参数数量.

注意, c宏与m4宏的调用有点区别. 在C中, 调用一个宏, 宏名与其后的”(“可以有空格, 而m4宏的调用不允许这样.

m4的宏体是一个带引号的字符串, 做引号与~同键, 有引号与"同键.

,会被m4捕获为宏参数分隔符, 而引号可使之逃逸.

宏的陷阱

m4允许宏的重定义, 结果是新的宏定义会覆盖旧的.

m4的宏命名规则: 只允许使用字母, 数字以及下划线构造宏名, 并且宏名只能以字母或下划线开头.

对宏进行重定义时, 需要借助引号.

引号的作用:

  • m4 将一切没有引号的文本都视为宏。对于已定义的宏,m4 会将其展开;对于未定义的宏,m4 会按其字面将其输出。
  • 加了引号的文本,m4 不再检测它们是不是宏,而是将其作为普通文本按字面输出。

也就是, 加了引号的文本, 会被m4认为普通字符输出.

记号

m4输入流原理:
m4 对输入流是以记号(Token)为单元进行读取的。一般情况下,m4 会将读取的每个记号直接发送到输出流,但是当 m4 发现某个单词是已定义的宏名时,它会将这个宏展开。在对宏进行展开的过程中,m4 可能会需要读入更多的文本以获取宏的参数。宏展开的结果会被插入到输入流剩余部分的前端,也就是说,宏展开后所得到的文本会被 m4 重新读取,解析为记号,继续处理。

在宏参数列表中, 在,之后的空格是无意义的字符.

空的字符串, 虽然不具备被 m4 发送到输出流的资格, 但是它可以作为其他记号的边界记号使用.

注释符

# 是行注释符, m4 的注释文本也会被发送到输出流.

可使用changecom宏修改 m4 默认的注释符:

1
changecom(`@@')

使用块注释符:

1
changecom(/*,*/)

不回显注释文本, 使用dnl:

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

引号, 逃逸以及非ASCII字符

使用changequote宏修改m4默认的引号定界符:

1
changequote(<!,!>)

不向其提供参数, 即恢复默认引号定界符.

条件

ifdef宏用于判断宏是否定义, ifelse宏判断表达式的真假.

1
ifdef(`a', b)

如果a是已定义宏, 那么这条语句的展开结果是b.
ifelse(a,b,c,d)会比较字符串ab是否相同, 如果它们相同, 这条语句的展开结果是字符串c, 否则则展开为字符串d.

ifelse可以支持多个分支.

数字

m4内建宏eval, 对整型数的运算表达式进行求值.

m4中可以通过esyscmd宏访问Shell.

递归

m4会将当前宏的展开结果插入到待读取的输入流的前端.


GNU m4 教程 Notes
http://example.com/2022/08/07/GNU-m4-教程-Notes/
作者
Jie
发布于
2022年8月7日
许可协议