Makefile 光学教程》之面向 Makefile 编程·Scheme R6RS 文档处理 [LaTeX]

此教程将计划以两部分内容呈现,目标是从零基础到 GNU make 最本原理的掌握,这是第二部分内容,分按不同的工程类型分成多个示范项目来展示。零基本可以先看第一部分:Basic Concepts:

  1.  ???? Basic Concepts

  2.  ???? Demo Projects

第二部分计划提供以下工程示范,当前即为第一个 Makefile 脚本应用示范:

  1.  ???? Scheme R6RS 语言规范文档处理 [LaTeX]

  2.  ???? Multi threaded Download

  3.  ???? C/C++ Project Templates

  4.  ???? Erlang Project Templates

  5.  ???? Unit Test

完整《Makefile 光学教程》以及 GNU M4 教程参考开源文档:https://github.com/Jeangowhy/opendocs/blob/main/Makefile.md

RnRS (the Revised^n Reports on Scheme) 作为 Scheme 社区的权威报告,对其语言规范的实现者具有积极指导意义。比如,按规范实现的 rsrn base 模块,就 提供各种数据类型相关操作的模块。Guile 3.0.9 版本的源代码文档中包含了 R5RS Texinfo 格式文档,可以作为趁手的备查文档。源代码中同样包含了官方的参考手册,info 格式可以很方便地转换成其它格式,比如 Markdown。

Make 提供了一套机制给开发者编写扩展程序,即各种基于 make 的工具开发,也就是手册 12 Extending GNU ‘make’ 和 13 Integrating GNU ‘make’ 中所阐述的内容,主要是 job slots 在进程间的共享。扩展 make 就是基于插件机制编写工具,并且通过脚本中的 load 指令加载和执行指定方法,或者默认的入口方法。官方已经在 GNU Make 4.2 集成 Guile。

Guile 是一种嵌入式脚本语言,属于 Scheme programming language 的一种,即 LISP 语言的一种方言。这类语言使用的语法非常新奇(古典),例如,调用加法算术函数 `(+ 1 2)` 得到结果为 3,嵌套调用就继续加圆括号。

目前 R6RS 规范报告文档共享在 https://www.r6rs.org/ 网站上,文档分为四个部分:

1. Revised6 Report on the Algorithmic Language Scheme

2. Revised6 Report on the Algorithmic Language Scheme — Standard Libraries

3. Revised6 Report on the Algorithmic Language Scheme — Non-Normative Appendices

4. Revised6 Report on the Algorithmic Language Scheme — Rationale

R6RS 文档原始格式是 48 个 TEX 文档,外加两个书目 Bibliology,计算其它转换脚本就有 67 个原始文件。

1. https://www.latex-project.org/ 

1. https://tikzit.github.io/

2. https://www.overleaf.com/learn/latex/Learn_LaTeX_in_30_minutes

3. https://www-cs-faculty.stanford.edu/~knuth/index.html

4. https://www-cs-faculty.stanford.edu/~knuth/taocp.html

5. https://lamport.azurewebsites.net/pubs/pubs.html

TeX 是一个排版系统,也是 LaTeX 的基础。TeX 作者高德纳(Donald Ervin Knuth)的传奇一生中写作了一部计算机科学巨著 The Art of Computer Programming (TAOCP)。在准备出版第四卷时,出版社给了他一本已经出版的第二卷第二版书过目过目,发现那书的颜值一言难尽,觉得现有的计算机排版系统不太行,为了使自己的毕生心血看着美观,自己写一个排版系统。1978 年 TeX 第一版发布,就得到了许多人的追捧,1982 年高老爷子紧接着发布了 TeX 的第二版 TeX82。10 年后,1989 年发布了 TeX3.0,老爷子宣布,除了修改 bug 停止 TeX 的开发,因为 TeX3.0 已经非常稳定了。

LaTeX 是基于 TeX 之上定义的一组宏集,相当于对 TeX 进行了一次封装。是出版物的高质量排版系统,一个文档准备系统,包括为制作技术和科学文件而设计的功能。LaTeX 是科学文献交流和出版的事实标准。LaTeX 是免费软件。LaTeX 作者是美国计算机科学家莱斯利·兰伯特 Leslie Lamport。

TeX 名字源自 technology 的希腊词根,而将 Lamport 大佬的名字和 TeX 混合则得到了 LaTeX 的名字。到现在,已经出现一堆数不过来的和 TeX 扯关系的应用。

LaTeX 也是宏编程的一种形式,它大量使用斜杆前缀定义宏符号,An introduction to LaTeX 文档给出以下 Hello World 文档示范:

LaTeX 目录分区条目的宏定义:

https://www.overleaf.com/learn/latex/Sections_and_chapters

现在希望将 R6RS 这些文件的内容统一归纳到一个文件,这样可以使用 Sublime Text 阅读文档时提供的快捷跳转功能,避免了文档来回切换的时间损失,更严重的是切换动作导致注意力的涣散,使文档阅读效率大大下降。

文档压缩包内 Makefile 脚本已经定义好了各种格式转换的规则,但对于我的目标不是很重要,只需要它定义的文件列表:

可以使用 Node.js 平台提供的 watch 工具监视脚本,Linux 系统内置 watch 命令,只是用法上有些差别。只要监视的文件有改动就执行相应的命令,这会很方便地调试 Makefile 脚本。

使用过滤器只可以监视指定的文件,过滤器文件是一个返回过滤函数的 Node.js 模块脚本,过滤函数名称随意,但需要在作为 exports 返回,然后通过 -f 或者 –filter 将文件名传递给 watch 工具:

过滤器中的注解符号是 TypeScript 编译器支持的类型修饰符号,Sublime Text 安装 LSP 插件和 TypeScript 语言服务器后就可以提示智能提示,并且在 tsc 编译器中也可以做类型检查工作。

为了将这些 TEX 文档按目录顺序写入同一个 TEX 文档,当然这过过程引入的文档头部定义可能会导致文档定义不符合规范,需要在 Makefile 文档中增加以下规则定义:

现在开始需要使用到自动变量了,它们在命令块中引用当前规则包含的各种信息:

1.  `$<` 自动变量表示依赖列表中的第一个依赖项;

2.  `$^` 自动变量表示整个依赖列表,列表中各依赖项之以空格隔开;

3.  `$+` 类似 $^,只是按顺序包含目标在 Makefile 中的依赖列表,配合链接程序使用;

4.  `$*` 模式匹配 % 符号匹配到的内容,称为主干 stem 并会替换依赖文件 % 符号;

5.  函数内可以接收 $1 ~ $9 这几个参数,GNU m4 则没有这个数量限制,$0 还是一样指代宏名。

这里使用了 Makefile 内置的 .PHONY 虚构目标,它的功能及目的就是不对目标文件是否存在、更新状态等等进行隐式的检测,而是在构建目标时无条件地执行命令块中定义的 shell 命令。目前只是使用 echo 命令打印文档列表,接下来就需要考虑是否需要进行格式转换?如果需要就调用系统中安装好的转换工具程序。当前就不需要做格式转换处理,Sublime Text 有可以提供阅读 TEX 格式文档的辅助插件。

所以,只需要将文档内容直接写入指定文件,Makefile 脚本中有多种执行命令的形式:

1. 直接使用操作系统提供的标准文件重定向功能,如 > 和 >> 分别表示写入、附加写入;

2. 使用 shell 命令,如 cat 等等将内容写入指定文件;

3. 使用 Makefile 内置的 file 函数,也使用标准文件重定向一样尖括号表示读写操作;

4. 使用 Make 提供的插件扩展接口,编写自己的插件实现文件读写功能;

要小心使用这些功能、函数,否则不小心传递错误参数就可以导致文件内容被覆盖,或者制造一个巨无霸文件。另外,在同一个命令块中,echo 方式输出的内容会在 file 函数输出内容之后,与命令出现的先后顺序无关,由低层数据操作逻辑决定的。

接下来,需要引入一个顺号作为 TeX 文档中通过 \chapter 标记记录当前嵌入文档的序号。但是 Makefile 除了字符串,并没有直接提供数值运算的功能,解决数值运算有以下几种方法:

1. 使用 shell 函数调用外部的数值计算能力,如 @echo “1+3=$(shell echo $$((1+2)))”;

2. 使用内置宏函数、自定义函数构造出数值运算功能;

3. 使用 Make 提供的插件扩展接口,编写自己的插件实现数值运算功能;

Make 不支持在执行构建目标的命令中修改变量,这为数值处理设置了一些障碍。

注意:shell 使用 `$((1+2))` 这样的表达式做数值运算,Makefile 中就需要将 $ 转义为 $$。变量 赋值和引用变量语法上也有差别,后者需要 $var 这样的表达,同样需要转义。因为 shell 命令计算结果是临时的,所有需要将它保存到文件中重复利用。即使用 .ONESHELL 或者 export 导出 shell 变量也不行,因为 .ONESHELL 只能保证命令块在当前目标构建的时候同在一个 shell 进程中捃命令,一旦更换构建目标,所有环境变量都被重置。

使用 eval 函数,比如简单的使用它来增加 info 函数调用的脚本,另外更重要的是 eval 函数提供了一种在命令块中修改变量值的途径,此外别无它法:

但是,eval 不能循环使用同一个变量,即不能从一个变量取值并且又给它赋值:

重新整理一下以上内容,编写一个不需要通过 shell 写文件来实现的步进计数函数:

说明一下 inc 自增函数的逻辑:首先是 eval 函数定义了一段“将要”被 make 执行的代码,即 ID = xxx 的变量赋值语句。并且这个值需要借助 shell 的 `$((x+y+z))` 这种算术支持。其中运行使用到的值有两个:一是来自函数调用时 call 函数传递来过的参数 $1,它表示步长值。然后另一个值来自一个为了避免 eval 函数循环引用而加入的 inc_ID 变量,这个变量使用了 $0 自动变量表示,它指代函数名称 inc,组合得到这个变量的名称。

最后,整理以上代码片段,就可以得到需要的 Makefile 脚本:一个带有数值运算功能的脚本,它可以将 LeX 有秩序地按目录编号合并到统一的文档文件中:

资源下载: