第 17 篇:翻译 ParsedObjectStack——不可变链表栈与循环引用检测
本系列第 17 篇。上一篇完成了
ASCIILocationInformation。这一篇翻译ParsedObjectStack——二进制 plist 解析器用来检测循环引用的辅助类,70 行。
一、ParsedObjectStack 是什么
二进制 plist 中的对象可以相互引用,形成引用图。如果出现循环引用(A 引用 B,B 又引用 A),解析器需要检测并报错。ParsedObjectStack 就是这个检测工具——一个不可变链表栈。
上游用 Java 实现,核心设计:
- 私有构造 + 静态工厂
empty() push(obj)不修改原栈,而是返回新节点- 如果 push 的 ID 已在栈上,抛
PropertyListFormatException
二、翻译要点
不可变链表
cangjie
public class ParsedObjectStack {
private var parent: Option<ParsedObjectStack>
private var object: Int64
private init(parent: Option<ParsedObjectStack>, object: Int64) {
this.parent = parent
this.object = object
}
public static func empty(): ParsedObjectStack {
ParsedObjectStack(None, -1)
}
public func push(obj: Int64): ParsedObjectStack {
this.throwIfOnStack(obj)
ParsedObjectStack(Some(this), obj)
}
}每次 push 创建新节点,parent 指向前一个节点。这是典型的函数式不可变数据结构。
递归检查
throwIfOnStack 递归遍历链表,检查 obj 是否已存在:
cangjie
private func throwIfOnStack(obj: Int64): Unit {
match (this.parent) {
case Some(p) => ParsedObjectStack.checkAndRecurse(this, p, obj)
case None => ()
}
}
private static func checkAndRecurse(self_: ParsedObjectStack, p: ParsedObjectStack, obj: Int64): Unit {
if (self_.object == obj) {
throw PropertyListFormatException("...cyclic reference...")
}
p.throwIfOnStack(obj)
}三、踩坑实录
坑 1:case None => {} 被解析为 lambda
空代码块 {} 在 match case 右侧也被解析为 lambda。解决方案:用 () 代替空操作。
cangjie
// ❌ case None => {} // 被解析为 lambda
// ✅ case None => () // Unit 字面量坑 2:仓颉的 catch 语法
仓颉的 catch 语法是 catch (e: ExceptionType),不是 Java 的 catch (ExceptionType e)。但更好的做法是用 @ExpectThrows 宏来测试异常。
四、验证:5 个测试,123/123 全绿
[ PASSED ] testEmptyStack
[ PASSED ] testPushSingle
[ PASSED ] testPushChain
[ PASSED ] testPushDuplicateThrows
[ PASSED ] testPushSameIdTwice
Summary: TOTAL: 123, PASSED: 123, FAILED: 0下一篇:翻译 ByteOrderMarkReader——Unicode BOM 检测器。