Skip to content

Commit ab70fba

Browse files
author
ren.ji
committed
Unify quotes to curly quotes; add ch. 41/52 exercises and #lang llm case
- Replace straight double quotes with curly quotes across all prose (code blocks and inline code untouched) - Add 'find the flaw' exercises to ch. 41 (silent-fail DSL fallback) and ch. 52 (SQL string concatenation vs parameterized queries) - Add #lang llm (Bowman) as a modern real-world example in ch. 42 - Fix ending-hook placement in ch. 10
1 parent 0e84bd8 commit ab70fba

35 files changed

Lines changed: 321 additions & 282 deletions

‎content/appendix/1.mdx‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@
1313
Noise 的架构可以概括为三层模型:
1414

1515
1. **Runtime Embedding (C Layer)**: 通过 C 接口启动和管理 Racket 运行时。
16-
2. **Data Bridge (Serialization Layer)**: 定义一套二进制协议,让 Swift 和 Racket 互相 " 听得懂 "。
16+
2. **Data Bridge (Serialization Layer)**: 定义一套二进制协议,让 Swift 和 Racket 互相 “ 听得懂 ”。
1717
3. **Execution Model (IPC/Actor Layer)**: 不直接从 Swift 调用 Racket 函数,而是建立一个 Client-Server 模型,通过管道(Pipes)通信。
1818

1919
让我们逐层拆解。
2020

2121
## 第一层:启动运行时 (The C Bridge)
2222

23-
任何嵌入式项目的第一步都是初始化。Noise 使用的是 **Racket CS (Chez Scheme)** 版本。在 Sources/Noise/Racket.swift 中,我们可以看到核心的初始化代码。Racket CS 的启动需要特定的 "boot files"(引导文件),这些文件包含了运行时的基础逻辑。
23+
任何嵌入式项目的第一步都是初始化。Noise 使用的是 **Racket CS (Chez Scheme)** 版本。在 Sources/Noise/Racket.swift 中,我们可以看到核心的初始化代码。Racket CS 的启动需要特定的 “boot files”(引导文件),这些文件包含了运行时的基础逻辑。
2424

2525
```swift
2626
// 实际实现
@@ -42,7 +42,7 @@ public init(execPath: String = "racket") {
4242
**关键点:**
4343

4444
- **资源管理**:你需要将这些 `.boot` 文件打包进你的应用资源包中,并在运行时获取它们的路径。
45-
- **线程模型**:`racket_boot` 运行的线程会成为 Racket 的 "Main Place"。后续的所有操作如果不加以控制,都会在这个线程上发生。
45+
- **线程模型**:`racket_boot` 运行的线程会成为 Racket 的 “Main Place”。后续的所有操作如果不加以控制,都会在这个线程上发生。
4646
- **线程安全**:Noise 提供了 `bracket`、`activate` 和 `deactivate` 方法来管理线程状态,避免与垃圾回收器竞争。
4747

4848
**给其他语言的启示:** 如果你的目标语言(如 Lua, Python, Guile)有 C API,第一步永远是构建一个最小的 C 包装器来处理 `init`/`boot`。
@@ -208,4 +208,4 @@ let result = await backend.simulateProcess(data)
208208
4. **Async Loop**: 在嵌入语言端实现一个 `While True` 循环读取输入、处理、写回输出。在宿主语言端实现 `Future/Promise` 映射。
209209
5. **Isolation**: 确保每个请求在独立的上下文中执行,失败不会崩溃整个系统。
210210

211-
Noise 不仅仅是一个 Racket 包装器,它展示了现代多语言编程的最佳实践:**类型安全、异步设计、代码生成、错误隔离**。这才是 " 嵌入 " 一门语言的正确姿势。
211+
Noise 不仅仅是一个 Racket 包装器,它展示了现代多语言编程的最佳实践:**类型安全、异步设计、代码生成、错误隔离**。这才是 “ 嵌入 ” 一门语言的正确姿势。

‎content/appendix/11.mdx‎

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,21 @@
22

33
很多语言在介绍时都会强调语法、范式、库生态,但当你真正开始研究一门语言的 **实现本身** 时,关注点会自然转向另一些问题:
44

5-
> 这门语言的 " 本体 " 是什么?
5+
> 这门语言的 “ 本体 ” 是什么?
66
> 程序最终跑在什么之上?
77
> 编译和运行的边界在哪里?
88
99
Racket 是一门 **非常适合从工程层面解剖的语言**,因为它对这些问题的答案并不隐藏,甚至可以说是刻意暴露出来的。
1010

11-
## 一、Racket 不是 " 一个编译器 ",而是一套语言运行系统
11+
## 一、Racket 不是 “ 一个编译器 ”,而是一套语言运行系统
1212

1313
从工程上看,Racket 更接近于:
1414

1515
> **一个可扩展的语言平台(language platform)**
1616
1717
而不是:
1818

19-
> " 把源码翻译成机器码的单一编译器 "
19+
> “ 把源码翻译成机器码的单一编译器 ”
2020
2121
Racket 官方发行的内容,大致可以拆成四个层次:
2222

@@ -28,7 +28,7 @@ Racket 语言
2828
└─ 底层宿主(Chez Scheme / C 运行时 / OS)
2929
```
3030

31-
你研究的 "Racket 本身 ",本质上就是在这四层之间穿行。
31+
你研究的 “Racket 本身 ”,本质上就是在这四层之间穿行。
3232

3333
## 二、Racket 程序最终编译成了什么?
3434

@@ -41,7 +41,7 @@ Racket 语言
4141
* → 编译为 **Racket 字节码(.zo)**
4242
* → 由 Racket VM 执行(通过 JIT)
4343

44-
`.zo` 文件就是 Racket 世界里的 " 目标文件 "。
44+
`.zo` 文件就是 Racket 世界里的 “ 目标文件 ”。
4545

4646
你可以把它理解为:
4747

@@ -60,15 +60,15 @@ Racket 语言
6060

6161
**注意关键点:**
6262

63-
> `.zo` 并不是 "S 表达式文本的序列化 ",
63+
> `.zo` 并不是 “S 表达式文本的序列化 ”,
6464
> 而是 **接近运行时的数据结构快照**。
6565
6666
**重要细节:**
6767

6868
* Racket BC(Before Chez)和 Racket CS(Chez Scheme)的 `.zo` 格式 **不兼容**
6969
* 这也是为什么在切换 Racket 实现时需要重新编译所有依赖
7070

71-
这也是为什么 Racket 的 " 编译 " 和 " 加载 " 界线很模糊。
71+
这也是为什么 Racket 的 “ 编译 ” 和 “ 加载 ” 界线很模糊。
7272

7373
### 3️⃣ 可以编译成机器码吗?
7474

@@ -116,14 +116,14 @@ Chez Scheme:提供"高效的 Scheme 执行引擎"
116116
Racket:将自己的语言语义完整地实现在 Chez 之上
117117
```
118118

119-
Racket 并不是 " 基于 Chez 构建 ",而是:
119+
Racket 并不是 “ 基于 Chez 构建 ”,而是:
120120

121121
> **把 Chez 当作一个高质量的底层 substrate,
122122
> 但在其上重写了几乎所有语言层面的东西。**
123123
124124
类比:就像 PyPy 用 RPython 实现 Python,但 Python 语言本身的语义是 PyPy 团队定义的。
125125

126-
### 2️⃣ Racket Runtime(真正的 " 语言引擎 ")
126+
### 2️⃣ Racket Runtime(真正的 “ 语言引擎 ”)
127127

128128
Racket 自己实现了大量运行时组件,包括:
129129

@@ -134,7 +134,7 @@ Racket 自己实现了大量运行时组件,包括:
134134
* **合约系统**(contract system)
135135
* **custodian**(资源管理)
136136

137-
这些并不属于 Scheme 语言规范,而是 Racket 的 " 语言级基础设施 "。
137+
这些并不属于 Scheme 语言规范,而是 Racket 的 “ 语言级基础设施 ”。
138138

139139
> 所以 Racket 的运行时 **远大于一个解释器**。
140140
@@ -147,7 +147,7 @@ Racket 自己实现了大量运行时组件,包括:
147147

148148
但这些被严格封装在运行时中,**用户层几乎感知不到**。
149149

150-
## 四、Racket 的 " 编译 " 是一条流水线,而不是一步
150+
## 四、Racket 的 “ 编译 ” 是一条流水线,而不是一步
151151

152152
如果从工程实现的角度抽象,Racket 的编译流程更像这样:
153153

@@ -165,11 +165,11 @@ Linklet / Bytecode(.zo)
165165

166166
### 核心特征:
167167

168-
* **宏在 " 扩展期(expansion time)" 执行**,而非传统意义的 " 编译期 "
168+
* **宏在 “ 扩展期(expansion time)” 执行**,而非传统意义的 “ 编译期 ”
169169
* 扩展可能发生在你 `raco make` 时,也可能发生在 `require` 加载模块时
170170
* **Phase separation**:Racket 的宏系统区分多个执行阶段(phase 0, 1, 2...)
171171
* 语言本身可以改变编译过程
172-
* 不存在一个 " 不可修改的语法阶段 "
172+
* 不存在一个 “ 不可修改的语法阶段 ”
173173

174174
**Linklet 的引入(Racket CS):**
175175

@@ -181,7 +181,7 @@ Linklet / Bytecode(.zo)
181181

182182
这也是为什么在 Racket 中:
183183

184-
> " 语言实现 " 和 " 用户程序 " 之间的界线是模糊的。
184+
> “ 语言实现 ” 和 “ 用户程序 ” 之间的界线是模糊的。
185185
186186
## 五、模块是 Racket 工程的基本单位,而不是文件
187187

@@ -206,7 +206,7 @@ Linklet / Bytecode(.zo)
206206

207207
如果你要理解 Racket 的工程结构,**模块系统是必读内容**。
208208

209-
## 六、raco:Racket 工程的 " 构建系统 + 包管理器 "
209+
## 六、raco:Racket 工程的 “ 构建系统 + 包管理器 ”
210210

211211
从工程角度,`raco` 承担的角色非常关键:
212212

@@ -220,11 +220,11 @@ Linklet / Bytecode(.zo)
220220

221221
> **raco 是 Racket 的工程入口,而不是 REPL。**
222222
223-
当你研究 Racket 的 " 最终产物是什么 " 时,答案几乎总是:
223+
当你研究 Racket 的 “ 最终产物是什么 ” 时,答案几乎总是:
224224

225-
> " 取决于 raco 是如何调用编译与打包流程的。"
225+
> “ 取决于 raco 是如何调用编译与打包流程的。”
226226
227-
## 七、如果你想 " 深入 Racket 工程 ",应该关注什么?
227+
## 七、如果你想 “ 深入 Racket 工程 ”,应该关注什么?
228228

229229
从专家视角给你一个现实的路线图:
230230

@@ -253,23 +253,23 @@ Linklet / Bytecode(.zo)
253253

254254
### 可以暂时忽略的早期误区
255255

256-
* 纠结 " 是不是静态编译语言 "
256+
* 纠结 “ 是不是静态编译语言 ”
257257
* 把 Racket 和 C / Rust 对等比较编译产物
258258
* 把 `.rkt → exe` 当成核心问题
259259

260260
## 八、一句工程化的总结
261261

262262
如果用一句话描述 Racket:
263263

264-
> **Racket 是一套 " 可被用户扩展的语言实现 ",而不仅是一门语言。**
264+
> **Racket 是一套 “ 可被用户扩展的语言实现 ”,而不仅是一门语言。**
265265
266266
你写的不只是程序,而是在参与:
267267

268268
* 编译流程
269269
* 语言语义
270270
* 运行时构建
271271

272-
这也是为什么,真正理解 Racket 之后,再回头看很多 " 传统语言 " 的工程设计,会觉得它们的边界异常僵硬。
272+
这也是为什么,真正理解 Racket 之后,再回头看很多 “ 传统语言 ” 的工程设计,会觉得它们的边界异常僵硬。
273273

274274
## 附录:关键术语对照
275275

‎content/appendix/2.mdx‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## 引言
44

5-
Noise 是一个 Swift 包装器,用于简化在 Swift 应用中嵌入 Racket CS 运行时的复杂过程。乍一看,它似乎只是简单地 " 包装了一个 Racket 库 ",但实际上,整个集成过程涉及多个层次和组件,理解其工作原理需要深入分析编译和运行时两个阶段。
5+
Noise 是一个 Swift 包装器,用于简化在 Swift 应用中嵌入 Racket CS 运行时的复杂过程。乍一看,它似乎只是简单地 “ 包装了一个 Racket 库 ”,但实际上,整个集成过程涉及多个层次和组件,理解其工作原理需要深入分析编译和运行时两个阶段。
66

77
本文将深入探讨 Noise 的核心架构,解答以下关键问题:
88
- Racket CS 的最小运行时包含哪些文件?
@@ -204,7 +204,7 @@ RACKET_API_EXTERN void racket_embedded_load_file(const char *path, int as_predef
204204
205205
Swift 会自动将这些函数映射为 Swift 函数调用,可以直接在 Swift 代码中使用。
206206
207-
**总结**:头文件是 Swift 和 C 之间的 " 语言桥梁 ",定义了双方都能理解的接口。
207+
**总结**:头文件是 Swift 和 C 之间的 “ 语言桥梁 ”,定义了双方都能理解的接口。
208208
209209
## 四、谁来加载 Boot 文件?何时加载?
210210
@@ -260,7 +260,7 @@ public struct NoiseBoot {
260260
4. 设置参数(如 `exec_file`, `argv` 等)
261261
5. 启动 Racket VM,进入可运行状态
262262

263-
**关键理解**:boot 文件是 Racket VM 的 " 启动镜像 ",包含了解释器和基础库的字节码。没有它们,Racket VM 就像一个空壳,无法执行任何代码。
263+
**关键理解**:boot 文件是 Racket VM 的 “ 启动镜像 ”,包含了解释器和基础库的字节码。没有它们,Racket VM 就像一个空壳,无法执行任何代码。
264264

265265
## 五、运行流程详解
266266

‎content/appendix/5.mdx‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ Noise 选择了一种轻量但精巧的方式。它使用 `raco ctool --mods`
6868
raco ctool --c-mods modules.c main.rkt
6969
```
7070

71-
这条命令会将 `main.rkt` 及其所有依赖,全部 " 序列化 " 进一个巨大的 C 数组,生成一个 `modules.c` 文件。
71+
这条命令会将 `main.rkt` 及其所有依赖,全部 “ 序列化 ” 进一个巨大的 C 数组,生成一个 `modules.c` 文件。
7272

7373
当你编译这个 C 文件并链接进你的 App 时,Racket 代码就变成了二进制的一部分,不需要任何外部 `.zo` 文件就能运行!
7474

@@ -80,7 +80,7 @@ Noise 之所以选择打包 `.zo` 文件而不是编译成 C 模块,是为了
8080
- **编译速度**:`raco ctool --mods` 相比完整的 C 编译链要快得多
8181
- **动态依赖**:`raco ctool --mods` 会自动处理模块依赖关系,无需手动管理
8282

83-
但这也解释了为什么用户经常遇到 **"Version Mismatch"**。因为 `.zo` 文件是二进制格式,必须和 C 运行时的版本严格对应。正如 README.md 所警告的,共享库和 boot 文件必须与你编译 Racket 代码使用的 Racket 版本相匹配。
83+
但这也解释了为什么用户经常遇到 **“Version Mismatch”**。因为 `.zo` 文件是二进制格式,必须和 C 运行时的版本严格对应。正如 README.md 所警告的,共享库和 boot 文件必须与你编译 Racket 代码使用的 Racket 版本相匹配。
8484

8585
你看到的关于 `libtool` 合并 `libffi` 的复杂命令(见 README.md),本质上也是为了创建一个包含所有必要 symbols 的静态库,供 Swift 链接。
8686

@@ -91,4 +91,4 @@ Noise 之所以选择打包 `.zo` 文件而不是编译成 C 模块,是为了
9191
- 对于初学者,它是理解 Racket CS 编译流程的关键
9292
- 对于架构师,它是实现 **静态链接 Racket**、**单文件分发** 或者 **深度嵌入** 的神兵利器
9393

94-
在 Noise 中,`raco ctool --mods` 扮演着模块打包器的核心角色,负责将多个 Racket 模块及其依赖关系打包成一个可部署的 `.zo` 文件。理解 `ctool` 及其背后的 Boot File 机制,能让你在遇到链接错误、版本冲突或者诡异的 Crash 时,拥有一双看透本质的 " 火眼金睛 "。
94+
在 Noise 中,`raco ctool --mods` 扮演着模块打包器的核心角色,负责将多个 Racket 模块及其依赖关系打包成一个可部署的 `.zo` 文件。理解 `ctool` 及其背后的 Boot File 机制,能让你在遇到链接错误、版本冲突或者诡异的 Crash 时,拥有一双看透本质的 “ 火眼金睛 ”。

‎content/appendix/8.mdx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ raco ctool --runtime ${RUNTIME_PATH} --runtime-access ${RUNTIME_NAME} --mods ${A
114114

115115
- `raco ctool` 是 Racket 的编译工具。
116116
- 它将所有的 Racket 源代码及其依赖(包括标准库中用到的部分)打包编译成一个单一的二进制文件(`.zo` 格式)。
117-
- 这个 `.zo` 文件包含了整个 " 后端 " 逻辑和运行时环境。
117+
- 这个 `.zo` 文件包含了整个 “ 后端 ” 逻辑和运行时环境。
118118
- 这也是为什么最终的 App 不需要安装 Racket 解释器也能运行的原因,所有需要的都被打进去了。
119119

120120
**2. 生成 Swift 代码 (Backend.swift)**
@@ -249,7 +249,7 @@ ${APP_SRC}/Backend.swift: ${RKT_SRC}/${RKT_MAIN}
249249
- 这会生成 `MyiOSApp/Backend.swift`。
250250
5. **Xcode 配置**:
251251
- 将 `Backend.swift` 拖入 Xcode 工程。
252-
- 将 `res` 文件夹拖入 Xcode 工程(确保选择 "Create folder references",这样文件夹结构会被保留,且里面的新文件会被自动识别)。
252+
- 将 `res` 文件夹拖入 Xcode 工程(确保选择 “Create folder references”,这样文件夹结构会被保留,且里面的新文件会被自动识别)。
253253
6. **迭代开发**:
254254
- 每次修改 Racket 代码后,运行 `make` 更新资源和绑定代码。
255255
- 在 Xcode 中运行 App 即可看到最新效果。

‎content/appendix/9.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Noise 精读(十):实战:从零构建 Swift + Racket 计算器应用
22

3-
今天,我们来点实际的:如何从零开始构建一个 " 计算器 " 应用,前端使用 SwiftUI,后端完全用 Racket 实现。
3+
今天,我们来点实际的:如何从零开始构建一个 “ 计算器 ” 应用,前端使用 SwiftUI,后端完全用 Racket 实现。
44

55
这将不需要任何复杂的 C 语言胶水代码,我们将体验纯粹的 **Swift + Racket** 开发流。
66

0 commit comments

Comments
 (0)