☰
Zuo:为脚本与构建定制的微型 Racket
2026/10/13 3:07:57 网站建设 项目流程
  • 编程语言
  • 语言运行时
  • 编译器
  • 解释器
  • 标准库

【免费下载链接】racket

The Racket repository

项目地址:https://gitcode.com/gh_mirrors/ra/racket
点击查看免费下载

Zuo 是 Racket 官方仓库中自带的一个“微型 Racket”,它保留了#lang模块语言机制,但将实现压缩到一个可单文件编译的 C 程序,并内置文件系统、进程控制等脚本原语与一套make风格的嵌入式构建 DSL。本文以racket/src/zuo/README.md为主体,结合racket/src/zuo目录下的源码,系统讲解 Zuo 的定位、运行方式、构建安装、堆映像(heap image)加速、交叉编译、嵌入宿主应用以及其分层语言与宏系统实现,帮助读者把它用作轻量脚本语言或 Racket 自身的构建工具。

Zuo 是什么:为什么需要“更小的 Racket”

Zuo(名称源自中文“做/作”,即“make”之意,见 overview.scrbl)的定位很明确:通常情况下你应该用 Racket 写脚本;但当你需要一个远比 Racket 小的运行时,或者要“脚本化 Racket 自身的构建”时,Zuo 就是答案。它是一个带make风格嵌入式 DSL 的微型 Racket,内置了文件处理与进程运行原语。

Zuo 是 Racket 的“变体”体现在:程序文件以#lang开头,#lang之后的模块路径决定文件内容的解析与展开方式——make风格的 DSL 正是通过这种方式定义的,连基础 Zuo 语言本身也是由一层层#lang叠加而成,其中早期的一层就实现了宏。这一点在源码中得到印证:lib/zuo/base.zuo 只有寥寥几行——它从zuo/kernel起步,用module->hash拿到语言构造器后生成基础语言;而 lib/zuo/private/base/main.zuo 则直接include了base-common的实现。

需要强调的是,尽管程序以#lang开头,Zuo 拥有完全独立的实现,Zuo 程序并不能通过 Racket 运行(详见 overview.scrbl)。

运行 Zuo 的几种方式

Zuo 可执行文件只运行模块,运行入口的规则如下(README 原文 + overview.scrbl 补充):

启动方式行为
无命令行参数加载当前目录下的main.zuo
第一个参数是文件路径运行该文件(须以#lang开头)
第一个参数是目录运行该目录下的main.zuo
-c标志第一个参数被当作模块文本直接运行(1.1 版本加入)
第一个参数为空字符串""从标准输入读取模块文本(空串不是合法文件路径,因而被特殊解释)

额外的参数通过runtime-env过程传递给目标 Zuo 程序。./zuo build install就等价于运行build/main.zuo并传入参数install。无论最初运行哪个脚本,只要该脚本带有main子模块(用module+声明),该子模块也会被运行。

特别地,zuo .与zuo ./main.zuo等价,因此zuo .可以作为make的便捷替代——仍然能传递参数。

从示例脚本入门

README 推荐了四个示例,其中两个非常简单,适合快速上手:

hello.zuo(racket/src/zuo/local/hello.zuo)展示了最小程序:

#lang zuo "Hello, world!" ;; 若不想要引号: ;; (alert "Hello, world!") ;; 若不想显示为蓝色: ;; (displayln "Hello, world!")

文件仅以#lang zuo开头,随后一个字符串字面量就是整个程序;alert(高亮显示)与displayln是带输出的替代写法。

tree.zuo(racket/src/zuo/local/tree.zuo)是一个功能完整的tree命令实现,示范了 Zuo 脚本的典型结构:

  • 顶层(provide tree)把函数作为库导出;
  • (module+ main ...)声明main子模块,当脚本作为主程序运行时解析命令行参数并调用tree;
  • command-line宏模拟 Racket 的command-line形式,但因为没有状态,必须显式把累加器accum在线程中传递——:init (hash)初始化累加器,:once-each声明每个标志至多出现一次,[accum ("-a") "帮助文本" (hash-set accum 'all? #t)]为标志-a绑定处理动作,:args ([dir "."])声明位置参数;
  • (hash-ref (runtime-env) 'script)取回当前脚本路径用于错误信息。

同时该示例展示了 Zuo 的算术局限:只支持 64 位有符号整数,所以human-readable函数只能自己用quotient/modulo做“K/M”单位换算(源码注释直言“Arithmetic is not Zuo's strong suit”)。目录内容通过ls与stat获取,用glob->matcher(见 lib/zuo/glob.zuo)过滤点文件,build-path拼接路径。

构建与安装 Zuo

直接编译

最简方式:用 C 编译器编译zuo.c(racket/src/zuo/zuo.c)即可,除系统头文件与 C 库头文件外不需要任何额外文件,也不需要特殊编译标志,但-o zuo、-O2之类是推荐的。这个“单文件可编译”的事实并非宣传——zuo.c是完整的自包含实现(本仓库中该文件约 7679 行,文件头注释即说明“kernel 实现尽量分层”),其中约包含 100 个内核原语。

configure / make 方式

也可以使用configure、make、make install:make目标在编译zuo.c之后基本都会调用一个 Zuo 脚本。Makefile.in 展示了这一机制:

zuo: $(srcdir)/zuo.c $(CC_FOR_BUILD) $(CFLAGS_FOR_BUILD) $(CPPFLAGS_FOR_BUILD) \ -DZUO_LIB_PATH='"$(srcdir)/lib"'"'"'" -o zuo $(srcdir)/zuo.c $(LDFLAGS_FOR_BUILD) $(LIBS_FOR_BUILD) check: zuo ./zuo . to-run/zuo to-run/zuo . check

注意zuo:目标编译时通过-DZUO_LIB_PATH指定库集合路径;而check:则是“先编译 zuo,再用 zuo 去构建 to-run/zuo,再用 to-run/zuo 去跑测试”,典型地演示了自举式的 Zuo 构建流程。

如果你不使用configure,而是把zuo.c直接编译到当前目录的zuo,那么./zuo build.zuo与./zuo build.zuo install(Windows 上省略./)等价于默认配置下的make与make install。

无参数运行时的提示

如果直接运行./zuo(无参数),它会加载当前目录下的 main.zuo,后者会用alert打印一段欢迎信息,提示你“没有参数时默认加载 main.zuo”,并说明如何传参、如何用""直接输入程序。

make风格构建 DSL 与 build.zuo 剖析

Zuo 的核心卖点之一是自带的make风格 DSL,而 Zuo 自己就用它来构建自己。build.zuo 是一个完整的实例:

  • 开头(require "local/image.zuo")复用image.zuo中导出的image-target;
  • (provide-targets targets-at)导出构建目标生成函数targets-at,它接受构建目录与变量哈希表,返回目标列表;
  • 配置来源:若存在configure生成的Makefile,则用config-file->hash读取;否则按平台取默认值——Unix 默认prefix=/usr/local、CC=cc、CFLAGS=-O2,Windows 默认prefix=C:\Program Files\Zuo、CC=cl.exe、CFLAGS=/O2;
  • target形式定义目标:(target 输出路径 (lambda (path token) (rule 依赖列表 重建函数))),依赖中的quote-module-path表示“本脚本自身”,input-data-target表示一段配置数据;
  • 定义了两个可执行目标:to-run/zuo(把lib目录的相对路径编译进二进制,便于就地运行)与to-install/zuo(内嵌安装后的库路径);
  • phony-rule定义伪目标:zuos-to-run-and-install作为默认目标同时构建两个可执行文件;check伪目标运行测试套件 tests/main.zuo;install伪目标处理DESTDIR(用于打包暂存)、mkdir-p建目录、cp/cp*复制可执行文件与lib/zuo库目录(macOS 上先rm旧文件)。

测试套件本身也是 Zuo 脚本:tests/main.zuo通过require链拉入 30 多个测试模块(equal、integer、hash、process、filesystem、macro、make、build、form 等),全部通过后(alert "... tests passed!")结束——这与check目标的(process-status ...)校验、失败即(error "check failed")的机制配合,构成了 Zuo 的自测闭环。

库集合与启动性能

除内建语言模块zuo/kernel外,Zuo 通过库集合(collection of libraries)查找语言与模块。默认情况下,Zuo 以可执行文件所在目录的相对路径lib作为库集合树根;可用-X命令行标志指定其他集合路径。在zuo.c中对应ZUO_LIB_PATH宏(默认"lib")与ZUO_LIB_PATH环境变量:zuo_configure()会先检查环境变量再回落到编译期默认值,且#define EMBEDDED_IMAGE 0是 local/image.zuo 生成嵌入代码时锚定的标记行。

堆映像(heap image):消除启动开销

你可以创建一个把一组库作为堆映像(heap image)嵌入的 Zuo 实例,两个优势:

  • 无需额外的库模块目录;
  • Zuo 启动极快,可与最快的命令行程序相竞争。

local/image.zuo 生成一个.c文件,它是zuo.c的副本加上嵌入的模块。其核心机制在embed-image函数中可见:

  • 默认嵌入'zuo模块及其依赖,可用++lib <module-path>指定更多(多次出现即累加);
  • 生成副本时默认禁用默认集合根路径,除非给local/image.zuo传--keep-collects;
  • 生成方式:以管道方式启动一个子进程 Zuo,向其写入#lang zuo/kernel代码,用module->hash装载指定库,最后调用dump-image-and-exit把镜像写到 stdout;
  • 随后逐行扫描zuo.c,找到#define EMBEDDED_IMAGE 0那一行替换为#define EMBEDDED_IMAGE 1,并把镜像字节以embedded_boot_image[]数组嵌入 C 源码;--keep-collects缺失时还会追加#define ZUO_LIB_PATH NULL禁用库路径;
  • 命令行参数还包括-o <file>(输出文件)、--image <file>(直接嵌入已有镜像文件而非新建)、--deps <file>(记录构建依赖,供增量构建的 up-to-date 判断);
  • image-target函数把同一套配置封装为构建目标,供build.zuo使用,其重建函数通过build/dep把实际读过的模块文件登记为增量依赖。

当你使用configure与make(或./zuo build.zuo)时,默认构建目标创建嵌入zuo库的to-run/zuo,以及to-install/zuo——后者在make install/./zuo build.zuo install之后有正确的内部路径去查找其他库。

不嵌入的堆映像:-B/--boot

堆映像也可以不嵌入:Zuo 内核原语dump-heap-and-exit创建堆映像(包含所有已装载模块),-B或--boot命令行标志让 Zuo 启动时使用给定引导映像;也可以把dump-image-and-exit创建的镜像通过local/image.zuo --image嵌入。无论独立文件还是嵌入.c源码,引导映像都是机器无关的。

交叉编译

使用./configure --host=...交叉编译时,还需要给./configure添加类似CC_FOR_BUILD=cc的参数,为构建机(build machine)上的zuo指定编译器;必要时还可指定CFLAGS_FOR_BUILD、LDFLAGS_FOR_BUILD与LIBS_FOR_BUILD。这些变量在 configure.ac 中通过AC_ARG_VAR声明(另有CPPFLAGS_FOR_BUILD),未显式提供时默认取$(CC) -O2等宿主值,并随AC_CONFIG_FILES写入生成的Makefile与main.zuo。

configure.ac还提供了两个与嵌入相关的开关:

  • --enable-embed=<libs>:指定嵌入哪些库,默认"zuo",no表示不嵌入(对应EMBED_LIBS="");
  • --enable-big:在zuo之外再嵌入zuo/hygienic。

最终EMBED_LIBS经AC_SUBST进入生成的Makefile,被 build.zuo 中的(hash-ref config 'EMBED_LIBS "zuo")读取并传给image-target。

把 Zuo 嵌入到更大的应用

Zuo 可以嵌入更大的应用,带或不带内嵌引导映像均可。做法是:用ZUO_EMBEDDED预处理宏(定义为任意值)编译zuo.c或local/image.zuo的输出;这种情况下会用到zuo.h头文件,嵌入方应用也应使用zuo.h。嵌入 API 的文档以注释形式写在 zuo.h 中。

zuo.h定义了完整的三步启动协议:

  1. zuo_ext_primitive_init():初始化原语,之后可用zuo_ext_add_primitive(proc, arity_mask, name)添加自定义原语。注意:为保证镜像可用,原语必须始终按相同顺序添加,镜像只在“具有相同原语集合”的环境中有效;
  2. zuo_ext_image_init(char *boot_image_file):装载引导映像;传NULL使用默认或内嵌映像。此后可用zuo_ext_false/true/null/void/eof/empty_hash取常量,用zuo_ext_integer/cons/string/symbol/hash_ref/hash_set构造与访问数据,用zuo_ext_kernel_env()取内核环境(但此时还不要装载、求值或使用任何模块);
  3. zuo_ext_runtime_init(lib_path, runtime_env):确定runtime-env与库查找路径,lib_path传#f可禁用库加载,runtime_env哈希表作为runtime-env结果的起点('exe、'args、'script等键需自行放入)。此后全部功能可用:用zuo_ext_eval_module从文本求值模块,用zuo_ext_apply调用过程。

由于zuo_ext_t*值在求值期间可能被垃圾回收或移动,跨求值保存值必须用zuo_ext_stash_push/zuo_ext_stash_pop。zuo.h内置两个#if 0的完整示例:一个用#lang zuo模块经dynamic-require取main后调用并打印(+ 1 2)的结果;另一个注册返回 5 的random-five原语并用#lang zuo/kernel模块消费它。ZUO_EXT_APPLY_ALWAYS_EXPECTS_VALUES宏记录了 1.2 版之前zuo_ext_apply的一个修复(此前对非原语过程会误把过程与实参当表达式求值)。

分层语言与两套宏系统

zuo/kernel(由zuo.c实现)是一种语法极简的语言,配约 100 个原语过程。Zuo 面向脚本,因而文件系统、I/O、进程原语很重——按 overview.scrbl 的统计,约一半原语服务于这类任务,另有约 1/3 原语用于数字、字符串与哈希表。

语言层叠顺序(README 称“基础 Zuo 语言由一层层#lang叠加而成,早期一层实现宏”),结合lib/zuo/private/目录可还原为:

  1. zuo/kernel:zuo.c内建的最小语言;
  2. looper 层(looper.zuo):补充简化版letrec、cond、let*;
  3. stitcher 层(stitcher.zuo):looper 的唯一使用者,加入自带隐式begin的lambda、多子句let、let*、多绑定子句letrec、and、or、when、unless及一种define和include;
  4. zuo/private/base/base-common:基础语言实现,包含and-or、define、let、opt-lambda、quasiquote、struct、syntax-error、check-dups等模块(目录 racket/src/zuo/lib/zuo/private/base 与base-common、base-hygienic并列);
  5. zuo/base(lib/zuo/base.zuo):由base-common的make-language组装;
  6. zuo:#lang zuo的最终语言,附带zuo/build等库。

宏系统有两套实现,源码基本同源,只是以“作用域与绑定的表示”参数化(结合zuo/datum与 stitcher 层的include实现):

  • zuo使用的非卫生宏:作用域模型表达能力较弱(写“生成宏的宏”时才会察觉差别),但快得多,对多数脚本场景足够;
  • zuo/hygienic提供的卫生宏:基于与 Racket 相同的 set-of-scopes 模型。

zuo与zuo/hygienic模块可以在一个程序里混用,但不能在一个语言里使用另一个语言的宏。更一般地,Zuo 定义了#lang协议,允许从字符/字节层面构建任意新语言,只要最终能用zuo/kernel表达。

Zuo 的数据类型与模块协议

内核支持的数据类型(见 overview.scrbl):布尔;64 位二进制补码整数(模运算);字节字符串(可选#前缀与\n、\r、\t、\"、\\、八进制转义);符号(分常驻与非常驻);列表;以符号为键的持久化映射哈希表(内部其实不真正做哈希);过程(包括以过程形式实体化的一等续延);variable(有名字、一次性设置、单值容器);opaque 对象(键值对,只有持键者能取到值,键通常靠词法作用域保密);handle(文件、进程等系统资源句柄)。值得注意的缺失:浮点数、字符、Unicode 字符串、向量。路径用字节字符串表示(Windows 宽字符路径隐含 UTF-8 编码)。

模块在核心层面就是一张哈希表,不同语言层可以自由赋予键含义;核心模块系统只对一个键'read-and-eval赋予语义——它是实现#lang语言的模块提供的三参数过程(模块文本字符串、#lang与语言名之后正文起点位置、映射到求值结果的模块路径),返回代表求值后模块的哈希表。module->hash原语负责按需装载:先检查已装载,未装载则读取源开头解析#lang得到语言模块路径,递归装载语言模块后以其'read-and-eval处理源文本。启动序列还赋予第二个键'submodules语义(键到 thunk 的映射),Zuo 运行初始脚本时查找'main子模块并调用其 thunk。zuo、zuo/base、zuo/hygienic语言约定'dynamic-require键映射到dynamic-require函数(1.2 版加入),用于访问导出的值。

路径处理的设计约定

脚本与构建系统绕不开路径问题,Zuo 做了几个明确取舍(README 未展开,但 overview.scrbl 的“Path Handling”一节有完整说明):

  • 依赖语法规范化:a/b之上构建../c得到a/c;若a/b是指向x/y/z的目录符号链接,文件系统会解出a/x/y/c,与 Zuo 的结果不同——目录符号链接会与 Zuo 的路径函数产生分歧,文件符号链接无此问题,文档建议干脆别用目录链接;
  • 进程内工作目录不可变:相对路径因此在更多场景下可靠,系统调用保持相对形式交给操作系统解析;启动子进程时可指定其工作目录,此时需用find-relative-path手动调整传给子进程的相对路径;
  • 尽量使用并传播相对路径:脚本启动方式影响结果——zuo scripts/go.zuo启动时quote-module-path报相对路径,zuo /home/.../go.zuo则报绝对路径;zuo/build记录依赖信息时同样遵循“相对引用记相对、绝对引用记绝对”;
  • source/build 目录显式分离:zuo/build鼓励显式区分“源目录”与“构建目录”,二者都不必是当前工作目录,加上工作目录不变,共同促成可组合的构建脚本。

深入阅读:文档与测试

  • 安装 racket/src/zuo/zuo-doc 目录为 Racket 包,即可渲染 Zuo 的正式文档(lang-zuo.scrbl、lang-zuo-kernel.scrbl、lang-zuo-hygienic.scrbl、lang-zuo-datum.scrbl、reader.scrbl、zuo-lib.scrbl、overview.scrbl等);
  • 完整测试套件位于 racket/src/zuo/tests,入口 tests/main.zuo 覆盖内核、语法、宏、构建 DSL、文件系统、进程、镜像等;
  • 语言分层实现位于 racket/src/zuo/lib/zuo/private,库模块(cmdline、glob、shell、config、c、jobserver、thread 等)位于 racket/src/zuo/lib/zuo。

综上,Zuo 用约 100 个内核原语加一层层#lang语言,在“单文件 C 实现 + 自举的 make 风格构建 DSL + 可嵌入的堆映像”三者间取得了独特的平衡:它既是 Racket 仓库(racket/src/zuo)的自举构建工具,也是一个可独立分发、可嵌入宿主程序、启动速度可与最快速命令行程序竞争的微型脚本语言。

  • 编程语言
  • 语言运行时
  • 编译器
  • 解释器
  • 标准库

【免费下载链接】racket

The Racket repository

项目地址:https://gitcode.com/gh_mirrors/ra/racket
点击查看免费下载

相关推荐

上一篇:Roc 语言 for 循环中的 var 变量跨迭代持久化与条件更新:基于 REPL 快照测试的实战解析
下一篇:Ray 集群部署指南:从单机到云端的集群架构、部署平台与快速上手

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询