M4 宏处理器

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

M4 宏处理器

Brian W. Kernighan
Dennis M. Ritchie

Bell Laboratories
Murray Hill, New Jersey 07974

翻译:寒蝉退士

译者声明:译者对译文不做任何担保,译者对译文不拥有任何权利并且不负担任何责任和义务。
原文:http://cm.bell-labs.com/7thEdMan/vol2/m4

摘要

M4 是在 UNIX ® 和 GCOS 上可用的宏处理器。它的主要用途是在无参数宏不够强力的情况下用做 Ratfor 的前端。它也被用于各异的语言如 C 和 Cobol。M4 特别适合于函数式语言如 Fortran、PL/I 和 C,因为宏是以函数表示法指定的。

M4 提供了即使在更大的宏处理器中都少见的特征,它包括了

  • 参数

  • 条件测试

  • 算术功能

  • 字符串和子串函数

  • 文件操纵

本文是 M4 用户手册。

July 1, 1977


介绍

宏处理器是增强一门编程语言,使它更加美味或更加可读,或者把它裁剪得适合特定应用的有用的方式。C 语言中的 #define 语句和 Ratfor 中类似的 define 就是任何宏处理器都提供的基本设施的例子 — 它把文本替代为其他文本。

M4 宏处理器是对叫做 M3 的宏处理器的扩展,它是 D. M. Ritchie 为 AP-3 小型机写的;M3 基于的是为 [1] 实现的宏处理器。不熟悉宏处理基本概念的读者可以读一下其中的某些讨论。

M4 适合于做 Ratfor 和 C 的前端,而且还成功的用于 Cobol。除了直接把一个字符串替代为另一个字符串之外,它还提供带有参数的宏、有条件的宏扩展、算术、文件操纵和某些特 殊的字符串处理函数。

M4 的基本操作把输入复制到输出。但在读入输入的时候,检查每个字母数字(alphanumeric)的“记号”(token) (就是说字母和数字的字符串)。如果它是一个宏的名字,则把这个宏的名字替代为它所定义的文本,把结果的字符串压回到输入中被重新扫描。调用宏可以带有参数,在这种情况下,参数被收集并在重新扫描之前替换入定义的文本中正确的位置上。

M4 提供了一组大约二十个内置宏来进行各种有用的操作;此外,用户可以定义新宏。内置和用户定义的宏以完全一样的方式工作,除了某些内置宏有对处理器状态的副作用之外。

用法

UNIX 上使用

m4 [files]

依次处理每个参数文件;如果没有参数或参数是‘−’,则在此位置上读取标准输入。处理后的文本写到标准输入,它可以被后续处理用下列方式捕获

m4 [files] >输出文件

GCOS 上用法是一样的,但程序叫做 ./m4

定义宏

M4 的首要内置函数是 define,它被用来定义新宏。输入

define(name, stuff)

导致把字符串 name 定义为 stuffname 的所有后续出现都被替代为 stuffname 必须是字母数字的并且必须开始于字母(下划线 _ 被当作字母)。stuff 是包含配对的圆括号的任何文本;它可以伸展到多行之上。

所以作为典型的例子,

define(N, 100)
...
if (i > N)

定义 N 为 100,并在后面的 if 语句中使用了这个“符号常量”。

define 必须立即跟随着左圆括号,它通知 define 有参数。如果一个宏或内置的名字没有立即跟随着‘(’,则假定它没有参数。这是上面 N 的情况;它实际上是一个没有参数的宏,所以在后面用到它的时候不要跟随着 (...)。

---- 第 1 页完 ----

你还应注意宏名字只在它出现被字母数字围绕的时候才被识别。例如,在

define(N, 100)
...
if (NNN > 100)

变量 NNN 绝对与定义的宏 N 无关,即使它包含很多个 N

可以把事物定义为另一个事物。例如

define(N, 100)
define(M, N)

定义 M 和 N 二者都是 100。

如果 N 被重新定义了会怎样呢? 或者换个方式说,M 被定义为 N 还是 100? 在 M4 中,后者是真的 — M 是 100,所以即使 N 随后改变了,M 也不变。

引起这种行为的原因是,M4 尽可能早的把宏名字展开为它们的定义文本。这意味着在收集 define 的参数时见到字符串 N 的时候,他立即被替代为 100;如同你首先写的是

define(M, 100)

如果这不是你实际上想要的,还有两种方式可用。首先,特定于这种情况,可以对换两个定义的次序:

define(M, N)
define(N, 100)

现在 M 被定义为字符串 N,所以当你以后查找 M 的时候,你将总是得到那时 N 的值(因此 M 会被替代为 N,它又会被替代为 100)。

引用

更一般的解决方式是通过引用define 的参数来延迟它们的展开。围绕着单引号 ` 和 ´ 的任何文本都不被立即展开,而是剥除这些引号。如果你写

define(N, 100)
define(M, `N´)

围绕着 N 的引号在收集参数的时候被剥除,它们已经完成了使命,M 被定义为字符串 N,而不是 100。M4 的普遍规则是在它求值某个东西的时候总是剥除一层单引号。即使是在宏外部也是这样。如果你希望字 define 出现在输出中,你必须在输入中引用它,比如

`define´ = 1;

作为同一事情的有些令人惊讶的另一个实例,考虑重定义 N:

define(N, 100)
...
define(N, 200)

可能有些遗憾,在第二个定义中的 N 在被见到之后马上就被求值;就是说,它被替代为 100,所以如同你写的是

define(100, 200)

这个语句被 M4 忽略,因为你只能定义看起来是名字的东西,并且这明显的不是你想要的效果。要真正的重定义 N,你必须通过引用延迟这个求值:

define(N, 100)
...
define(`N´, 200)

在 M4 中,引用宏的第一个参数经常是明智的。

如果出于某种原因使用 ` 和 ´ 不方便,可以用内置 changequote 改变引用字符:

changequote([, ])

使左右方括号成为引号字符。恢复最初的字符只需要

changequote

有两个增补的与 define 有关的内置。undefine 去除某个宏或内置的定义:

undefine(`N´)

去除 N 的定义。(为什么引号是绝对必须的?) 内置可以用 undefine 去除,比如

---- 第 2 页完 ----

undefine(`define´)

但是一旦你去除了它,就再也不能让它回来了。

内置 ifdef 提供了确定一个宏是否定义了的方式。特别是,M4 在相应的操作系统上有预定义的名字 unixgcos,你可以如下这样确定正在使用的是哪个:

ifdef(`unix´, `define(wordsize,16)´ )
ifdef(`gcos´, `define(wordsize,36)´ )

使一个定义适合特定的机器。不要忘记引号!

ifdef 实际上允许三个参数;如果这个名字未定义,ifdef 的值就是第三个参数,比如

ifdef(`unix´, on UNIX, not on UNIX)

参数

迄今为止我们已经讨论了宏处理的最简单形式 — 把一个字符串替代为另一个(固定的)字符串。用户定义的宏也可以有参数,因此不同的调用可以有不同的结果。 在一个宏的替代文本内(define 的第二个参数),$n 的任何出现都被替代为实际使用这个宏的时候的第 n 个参数。所以,如下定义的宏 bump

define(bump, $1 = $1 + 1)

生成把它的参数加上 1 的代码:

bump(x)

x = x + 1

宏可以有同你想要的一样多的参数,但只有前九个是可以访问的,从 $1$9。(宏名字自身是 $0,但不常用)。没有被提供的参数被替代为空字符串,所以可以通过简单的串联它的参数来定义一个宏 cat,比如:

define(cat, $1$2$3$4$5$6$7$8$9)

所以

cat(x, y, z)

等价于

xyz

$4$9 是空的,因为没有提供相应的参数。

丢弃在参数收集期间出现的前导的未引用的空格、tab 或换行。保留所有其他空白。所以

define(a,    b    c)

定义 ab    c

参数用逗号分隔,但是圆括号可以被正确处理,所以逗号可以用圆括号“保护”起来而不终止一个参数。就是说如下之中

define(a, (b,c))

只有两个参数;第二个参数是文字的 (b,c)。当然可以通过引用它来插入裸露的逗号或圆括号。

算术内置

M4 (只)提供两个做整数算术的内置函数。最简单的是 incr,它向数值参数增加 1。要处理常见的编程情况,你希望定义一个变量为“比 N 多一”,可以写

define(N, 100)
define(N1, `incr(N)´)

则定义 N1 为比 N 的当前值多一。

更一般的算术机制是叫做 eval 的内置函数,它有能力做关于整数的任意算术。它提供如下算符(按优先级递减的次序)

一元  和  或 (指数)(模)(非) 或 (逻辑与) 或 (逻辑或)

可以用圆括号在需要的地方组合算符。给 eval 的所有操作数(operand)最终必须是数值。真关系(如 1>0)的数值值为 1,而假为 0。eval 的精度在 UNIX 上是 32 位在 GCOS 上是 36 位。

---- 第 3 页完 ----

作为一个简单例子,假设我们希望 M2**N+1

define(N, 3)
define(M, `eval(2**N+1)´)

作为法则,建议你引用对宏定义的文本,除非它确实非常简单(就是说是个数);这通常会给出你想要的结果,这是个好习惯。

文件操纵

你可以通过内置 include 在任何时候包含一个新文件到输入中:

include(filename)

filename 的内容插入到 include 命令的位置中。这个文件的内容经常是一组定义。include 的值(就是它的替代文本)是这个文件的内容;它可以 捕获于这些定义中。

include 中的文件不能被访问是致命的错误。要获得对这种情况的某种控制,可以使用替代形式的 sincludesinclude (“silent include”)在这个文件不能被访问的时候什么都不说并继续。

还可以在处理期间把 M4 的输出转向到临时文件,输出紧随这个命令后收集到的材料。M4 维护九个这种转向文件,编号从 1 到 9。如果你写

divert(n)

所有后续的输出被添加到 n 所引用的临时文件的结束处。通过另一个 divert 停止转向到这个文件;特别是 divertdivert(0) 恢复到正常的输出处理。

被转向的文本通常都在处理结束时按数字次序串联起来输出。但是也可以在任何时间取回被转向的文本,就是说把它们添加到当前转向文件之上。

undivert

按数字次序取回所有转向文本,带有参数的 undivert 按给定的次序取回选择的转向文本。去转向的活动丢弃已被转向的材料,转向到编号不在 0 到 9 之间的转向文件的东西也被丢弃。

undivert 的值是被转向的材料。此外,被转向的材料被宏所重新扫描。

内置 divnum 返回当前活跃的转向文件的编号,在正常处理期间它是零。

系统命令

你可以通过 syscmd 内置运行在本地操作系统中任何程序。例如

syscmd(date)

UNIX 上运行 date 命令。通常使用 syscmd 来为随后的 include 建立文件。

为了实现制作唯一的文件名字,提供了内置的 maketemp,带有同系统函数 mktemp 一致的规定: 在参数中的字符串 XXXXX 被替代为当前进程的进程 id。

条件

有一个叫做 ifelse 的内置使你能进行任意的条件测试。在最简单的形式中

ifelse(a, b, c, d)

比较两个字符串 ab。如果它们是同样的,ifelse 返回字符串 c;否则返回 d。因此我们可以定义叫做 compare 的一个宏,它比较两个字符串并返回“yes”或“no”,分别在它们为相同的或不同的的时候。

define(compare, `ifelse($1, $2, yes, no)´)

注意引号,它防止 ifelse 的过早求值。

如果省略第四个参数,它被当作空。

ifelse 实际上可以用任何数目的参数,因此提供了有限形式的多路判断的能力。在输入中

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

如果字符串 a 匹配字符串 b,结果为 c。否则,如果 d 同于 e,结果为 f。否则结果为 g。如果省略了最后的参数,结果为空,所以

ifelse(a, b, c)

如果 a 匹配 b 则为 c,否则为空。

---- 第 4 页完 ----

字符串操纵

内置 len 返回构成它的参数的字符串的长度。因此

len(abcdef)

是 6,而 len((a,b)) 是 5。

可以使用内置 substr 来生成字符串的子串。substr(s, i, n) 返回 s 的从第 i 个位置(起始于零)开始的 n 个字符长的子串。如果省略了 n,返回余下的字符串,所以

substr(`now is the time´, 1)

ow is the time

如果 in 超出了范围,各种偶然的事情都可能发生。

index(s1, s2) 返回在 s1 中字符串 s2 出现的索引(位置),或者 −1 如果它没有出现的话。同于 substr,字符串起始于 0。

内置 translit 进行字符转换。

translit(s, f, t)

通过把在 f 中找到的任何字符都替换为 t 中对应的字符修改 s。比如

translit(s, aeiou, 12345)

替代元音为相应的数字。如果 t 比 f 短,在 t 中没有条目的字符会被删除掉;作为极限情况,如果 t 更本就不存在,把来自 f 的字符都从 s 中删除掉。所以

translit(s, aeiou)

删除 s 中的元音。

还有一个内置叫做 dnl,它删除跟随它的直到并包括下一个换行的所有字符;它主要用于丢弃空行,否则会使 M4 输出混乱。例如,如果你写

define(N, 100)
define(M, 200)
define(L, 300)

在每一行结束处的换行不是定义的一部分,所以它们被复制到输出,而这可能是我们不希望的。如果你在这些行上添加 dnl,这些换行就不见了。

完成这个目的的另一种方式,是 J. E. Weythman 提出的

divert(-1)
define(...)
...
divert

打印

内置的 errprint 把它的参数写出到标准错误文件。所以你可以写出

errprint(`fatal error´)

dumpdef 是个调试辅助函数,它转储定义的项的当前定义。如果它没有参数,则打印所有的项;否则你将得到同参数同名的项。不要忘记引用上这些名字!

内置总结

每个条目都前导上描述它的页号。

  • 3 changequote(L, R)

  • 1 define(name, replacement)

  • 4 divert(number)

  • 4 divnum

  • 5 dnl

  • 5 dumpdef(`name´, `name´, ...)

  • 5 errprint(s, s, ...)

  • 4 eval(numeric expression)

  • 3 ifdef(`name´, this if true, this if false)

  • 5 ifelse(a, b, c, d)

  • 4 include(file)

  • 3 incr(number)

  • 5 index(s1, s2)

  • 5 len(string)

  • 4 maketemp(...XXXXX...)

  • 4 sinclude(file)

  • 5 substr(string, position, number)

  • 4 syscmd(s)

  • 5 translit(str, from, to)

  • 3 undefine(`name´)

  • 4 undivert(number,number,...)

致谢

我们感谢 Rick Becker、John Chambers、Doug McIlroy,特别是 Jim Weythman,他率先使用 M4 导致了很多有价值的改进。我们还要感谢 Weythman 对代码做的实质贡献。

---- 第 5 页完 ----

引用

[1]    B. W. Kernighan and P. J. Plauger, Software Tools, Addison-Wesley, Inc., 1976.

转载于:https://my.oschina.net/zhoukuo/blog/330097

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/545964.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

oracle的nvl和nvl2

NVL( string1, replace_with) 功能:如果string1为NULL,则NVL函数返回replace_with的值,否则返回string1的值,如果两个参数都为NULL ,则返回NULL。NVL2(expr1,expr2,expr3)功能:oracle中常用函数&#xff0c…

Java SecurityManager checkAwtEventQueueAccess()方法与示例

SecurityManager类的checkAwtEventQueueAccess()方法 (SecurityManager Class checkAwtEventQueueAccess() method) checkAwtEventQueueAccess() method is available in java.lang package. checkAwtEventQueueAccess()方法在java.lang包中可用。 checkAwtEventQueueAccess() …

绝了,几款主流的 JSON 库性能对比!

本篇通过JMH(Oracle官方测试框架)来测试一下Java中几种常见的JSON解析库的性能。每次都在网上看到别人说什么某某库性能是如何如何的好,碾压其他的库。但是百闻不如一见,只有自己亲手测试过的才是最值得相信的。JSON不管是在Web开…

DWZ使用笔记

DWZ使用笔记 一、前言 在近期的一个项目中,引入了DWZ这个富client框架,算是一次尝试吧。期间也遇到不少问题,总算一一攻克了。特以此文记之。本人用的是dwz-ria-1.4.5Asp.net webform,写这篇笔记时最新版本号已经是1.4.6了。DWZ官…

Dynamic_Performance_Tables_not_accessible_问题_解决不能动态统计

Dynamic Performance Tables not accessible, Automatic Statistics Disabled for this session You can disable statistics in the preference menu,or obtanin select priviliges on the v$session,v$sesstat and v$statname tables第一种处理方法(不推荐&#x…

ruby三元操作符_在Ruby中使用操作符将元素添加到数组实例中

ruby三元操作符In the previous articles, we have gone through ways through which we can create Array instances. Some of them were Public instance methods and some were Public class methods. We should also know how they both differ from each other. Now we kn…

阿里的简历多久可以投递一次?次数多了有没有影响?可以同时进行吗?

最近,无论是读者群,还是公众号后台,很多人都比较关注以下几个问题:阿里的简历是半年只能投递一次吗?阿里的面试可以多个部门同时进行吗?面试没过,又被系统捞起来了,我该怎么办&#…

c语言给定一个非空整数数组_C程序检查给定整数的所有位是否为一(1)

c语言给定一个非空整数数组Problem statement: Write a C Program to check if all the bits of a given integer is one (1). 问题陈述:编写一个C程序来检查给定整数的所有位是否都是一(1) 。 Solution: We can use bitwise operator here to solve the problem. …

记一次蚂蚁金服面试被虐经历

本文来自作者投稿,原作者:yes面试前的小姐姐来说说前不久蚂蚁金服一面的情况。说来也是巧合,当时在群里有位蚂蚁金服的小姐姐发了个内推,看了下JD感觉可以试试于是就私聊了小姐姐发简历内推了。我16年也就是大三上就开始实习了&am…

本地连接和音量图标显示

一种:“控制面板”——“声音和音频设备”——“将音量图标放入任务栏”曾经手动关掉了。只要打开就行了。 第二种:“将音量图标放入任务栏”打勾,无效。丢失sndvol32.exe,文件路径C:\WINDOWS\system32,可以在别的机子…

用python + openpyxl处理excel(07+)文档 + 一些中文处理的技巧

2019独角兽企业重金招聘Python工程师标准>>> 寻觅工具 确定任务之后第一步就是找个趁手的库来干活。 Python Excel上列出了xlrd、xlwt、xlutils这几个包,但是 它们都比较老,xlwt甚至不支持07版以后的excel它们的文档不太友好,都可…

Spring Boot 2.3.3 正式发布!

Spring Boot 2.3.3 稳定版已发布&#xff0c;可从 repo.spring.io 和 Maven Central 获取。<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.3.RELEASE</versio…

木板最优切割利润最大_最多进行K笔交易的股票最大买卖利润

木板最优切割利润最大This is a very popular interview problem to find maximum profit in stock buying and selling with at most K transactions. This problem has been featured in the interview rounds of Amazon. 这是一个非常受欢迎的面试问题&#xff0c;目的是在最…

禁止访问磁盘的注册表

百度的了一个禁止访问磁盘的注册表问题怎么禁止访问磁盘&#xff0c;手动操作就会&#xff0c;可是有好几十台啊。手动搞&#xff0c;那个累啊。求个高手&#xff0c;帮我弄个注册表或者BAT文件执行都可以&#xff0c;禁止访问D盘跟E盘。网上找了很多资料&#xff0c;都叫用工具…

C语言文本文件与二进制文件转换

本程序要自己创建个文本格式的输入文件a1.txt&#xff0c;编译后能将文本文件前255字节以内的字符转换成相应的AscII码值的二进制表示&#xff0c;并存入输出文件a2.txt中。然后再将二进制文件还原并存入a3.txt文件。实现文件之间的转换。 具体代码如下&#xff1a; #include …

[数据库]Oracle和mysql中的分页总结

Mysql中的分页物理分页•在sql查询时&#xff0c;从数据库只检索分页需要的数据•通常不同的数据库有着不同的物理分页语句•mysql物理分页&#xff0c;采用limit关键字•例如&#xff1a;检索11-20条 select * from user limit 10,10 ;* 每次只查询10条记录.当点击下一页的时候…

List 集合去重的 3 种方法

问题由来在实际开发的时候&#xff0c;我们经常会碰到这么一个困难&#xff1a;一个集合容器里面有很多重复的对象&#xff0c;里面的对象没有主键&#xff0c;但是根据业务的需求&#xff0c;实际上我们需要根据条件筛选出没有重复的对象。比较暴力的方法&#xff0c;就是根据…

c printf 段错误_错误:预期声明在C中的printf之前指定

c printf 段错误The main cause of this error is - missing opening curly brace ({), before the printf() function. 导致此错误的主要原因是-在printf()函数之前缺少打开的花括号( { )。 Example: 例&#xff1a; #include <stdio.h>int main(void)printf("He…

常用的60招电脑操作

1、如果同时有多个窗口打开&#xff0c;想要关闭的话&#xff0c;可以按住shift不放然后点击窗口右上角的关闭图标。2、在保存网页前&#xff0c;可以按一下"ESC"键(或脱机工作)再保存&#xff0c;这样保存很快 3、用电脑听CD可以不用任何的播放软件&#xff0c;把音…

C语言入门——排序

排序的方法有很多种比较常见的便为&#xff1a;冒泡排序、选择排序、插入排序、快速排序。 今天我们就围绕着四种排序来说&#xff0c;如果有兴趣的话可以去查找一下其他排序。 在排序这方面我们主要讨论&#xff1a; 稳定&#xff1a;如果a原本在b前面&#xff0c;而ab&…