Lean 中的格式模型
389491f870d55a07 · 对应提交的原文 ↗格式模型描述压缩字节如何决定未压缩字节,没有堆或指针。 它是需要被实现细化的规范,不是流式 API 的实现。 规则见格式指南,调用、缓冲区和返回值见 API 模型。 已接受结果与剩余工作记录于进度。
总入口模块是 ZlibF/Spec/Format.lean。
单个符号步骤的扩展单独导入。下列定义位于 ZlibF.Spec 命名空间,
局部符号步骤关系则位于 ZlibF.Spec.Token。
模块映射
| 模块 | 内容 |
|---|---|
Bits.lean |
字节到位的顺序、数值读取器、大端和小端字节编码 |
Checksum.lean |
逐字节 Adler-32 和 CRC-32、更新函数与追加定律 |
Huffman.lean |
规范码、可接受长度集合、固定码、长度和距离表 |
Deflate.lean |
符号和块关系、raw 前缀及精确流关系 |
Wrapper.lean |
zlib 和 gzip 头部、尾部与精确流关系 |
Token.lean |
单 token 解码和拒绝,以及将它们连接到块语法的义务 |
Examples.lean |
具体接受与拒绝示例 |
从字节到输出
flowchart TD
B["bytesToBits:字节转位"] --> K["Blocks:头部与主体"]
K --> S["Body.stored:对齐的字节"]
K --> F["Body.fixed:固定码"]
K --> D["Body.dynamic:长度与编码"]
D --> C["CodeLens:展开码长符号"]
F --> Y["Symbols:字面量、复制、结束"]
D --> Y
S --> P["Deflate.Prefix:最终填充与字节尾随部分"]
Y --> P
P --> R["Deflate.Decodes:空尾随部分"]
P --> Z["Zlib.Decodes:Adler-32 尾部"]
P --> G["Gzip.Decodes:CRC-32 与长度尾部"]
readBits、readBytes 和 decodeSym 等读取器返回 none,或返回一个值与剩余位组成的二元组。
关系构造器要求相应的读取器等式。无效块类型或保留的长度/距离符号没有对应构造器。
先把一次块解码的输入和结果分开:inp 是尚未读取的位序列,rest 是解码停止时剩下的位,out 是新产生的字节。dict 是预置字典,h 是此前已经产生的历史字节;L、D 分别为字面量/长度和距离字母表保存各符号的码长,码长以比特计。
Symbols L D dict h inp rest out 从历史 h 开始,使用字面量/长度码长 L 和距离码长 D,
并在块结束符号处结束。它将 inp 消费到 rest,留下输出 out。
copyBack 每复制一个字节,就先追加它,再读取下一个,因此重叠匹配具有通常的逐字节含义。
Blocks dict h inp rest out 组合各主体,直到遇到 BFINAL = 1 的块。
块语法使用从完整输入字节得到的位。在该入口条件下,inp.length % 8 是到下一个字节边界前的剩余位数;
直接存储主体丢弃相应数量的位。例如刚读取完三位块头,当前字节还剩五位时,先跳过这五位,再读取按字节排列的长度字段。
公开格式关系对于最后一个块之后的要求不同:
| 关系 | 含义 |
|---|---|
Deflate.Prefix dict stream tail out |
从 stream 开始解码块,忽略最后字节的填充,要求剩余字节等于 tail |
Deflate.Decodes dict stream out |
同一关系,且 tail = [] |
Zlib.Decodes dict stream out |
恰好一个 zlib 流,包含已检查头部、可选字典 ID 和 Adler-32 尾部 |
Gzip.Decodes stream out |
恰好一个 gzip 成员,包含其可选头部 CRC 以及 CRC-32/长度尾部 |
包装关系使用 Deflate.Prefix,将所需尾部字节作为剩余序列参数 tail。
它们仍描述精确流:排除尾部之后的字节。
关于 Z_STREAM_END 后剩余输入的 API 定理,需要独立的已消费前缀陈述。
规则映射
F-* 规则定义在格式指南中。本表将每项规则定位到相应形式化定义,便于从格式要求找到 Lean 陈述。
| 规则 | 定义或义务 |
|---|---|
F-bit-order |
byteBits、bytesToBits、readBits、natToBitsMSB |
F-block-type-3 |
Body 没有类型 3 的构造器 |
F-stored-complement |
Body.stored:nlen = 65535 - len |
F-code-fits |
count、codeStart、codeOf、Fits |
F-code-complete |
Complete、CodeKind、Acceptable |
F-fixed-lengths |
fixedLitLens、fixedDistLens、Body.fixed;Symbols.copy 限制长度和距离符号 |
F-dynamic-limits |
DynamicCountsOK,由 Body.dynamic 要求 |
F-dynamic-repeat |
CodeLens.rep16、.rep17、.rep18;前一项与总项数前提 |
F-dynamic-eob |
HasEndOfBlock,由 Body.dynamic 要求 |
F-dynamic-valid |
Acceptable .codeLength、.litLen、.dist,由 Body.dynamic 要求 |
| 动态头部顺序 | Body.dynamic、clOrder、readCodeLenLens |
F-length-table、F-length-284 |
lengthBases、lengthExtras、Symbols.copy;基值 227 加额外值 31 仍为 258 |
F-distance-table |
distBases、distExtras、Symbols.copy |
F-match-copy |
copyBack |
F-distance-bound |
Symbols.copy 要求距离至多为 (dict ++ h).length |
| 块顺序与最终性 | Blocks.more、Blocks.last |
F-zlib-header |
Zlib.HeaderOK、Zlib.windowSize、Zlib.Decodes |
F-zlib-check |
Zlib.Decodes 要求尾部序列为 be32 (adler32 out).toNat |
| gzip 头部 | Gzip.header、Gzip.Decodes 中的头部约束 |
F-gzip-check |
Gzip.Decodes 要求尾部序列为 le32 (crc32 out).toNat ++ le32 (out.length % 2^32) |
F-adler-incremental |
adler32Update、adler32Update_append |
F-crc-incremental |
crc32Update、crc32Update_append |
F-deflate-valid |
Deflate.Valid、Zlib.Valid、Zlib.ValidWith、Gzip.Valid |
F-deflate-matches |
压缩器义务;格式语法没有额外限制 |
F-deflate-flush |
流调用序列的义务;完成刷新不等于完整最终流 |
Valid 谓词将 Decodes 的关注方向反过来:压缩参数必须解码为所给输入。
它们不选择唯一编码。Zlib.ValidWith 提供预置字典;其他列出的 Valid 谓词不使用字典。
模型选择与边界
历史与小窗口。 Symbols.copy 使用完整 dict ++ h 历史,不显式表示保存窗口的状态。
距离表将引用上限设为 32768。zlib 头的窗口字段限制初始字典后缀,
但没有给 Symbols.copy 增加距离前提。这明确保留了已有模型选择:
默认解码可以使用当前调用中超过较小保存窗口范围的输出,但下一次调用可能已无法取得那些字节。
C 也有独立的 INFLATE_STRICT 检查。全部配置窗口和输出缓冲区边界的细化未完成。
仅有声明式关系不能建立每种切分下的运行接受性。
字典。 有 FDICT 时,Zlib.Decodes dict 检查整个所提供字典的 Adler-32,
但仅将最后 Zlib.windowSize cmf 字节传给 DEFLATE 关系。
没有 FDICT 时,传入 [],忽略 dict。Adler-32 为 1 的字典可能为空。
无封装的 Deflate.Decodes 直接接收字典历史。Gzip 从空历史开始。
表的接受规则。 Acceptable 对三种编码类型都允许全零长度集合,但这种集合无法解码任何符号。
长度为 1 的单个已使用符号只在字面量/长度和距离类型下允许。
HasEndOfBlock 单独排除空的动态字面量/长度码。
表被接受或局部符号步骤成功,都不蕴含整个块有效。
填充。 直接存储块对齐和最后字节填充会丢弃位,不要求这些位为零。
额外的完整字节由显式的尾随序列 tail 处理,不视为填充。
gzip 元数据。 Gzip.Decodes 要求四个 MTIME 字节、保留标志位为零、
额外字段长度小于 2^16,以及所选名称或注释中不含嵌入零字节。
除此之外,FTEXT、MTIME、XFL 和 OS 不受约束。
标志未置位的可选字段见证不贡献字节;空见证可以满足其约束。
一个关系覆盖一个成员,而非拼接后的整个 gzip 文件。
局部解码。 Token.Step 描述一个字面量、匹配或块结束,不假设后续输入有效。
Token.Rejected 描述不可逆的无效编码或历史不可用情况。
目标 step_composes_symbols、symbols_exposes_step 和 rejected_has_no_symbols
将这些局部关系连接到原始 Symbols 语法。
这一边界防止以将来能够有效完成为假设,来论证 API 调用已经返回的输出。
格式关系之外的义务
| 关注点 | 规范边界 |
|---|---|
错误码、诊断字符串、Z_NEED_DICT 和字典调用顺序 |
Inflate API |
| 缓冲区大小、部分输入、自动包装检测、位预置和恢复 | 流式 API和 API 模型 |
| 压缩匹配选择、刷新完成和输出上界 | Deflate API |
| 拼接成员、透明读取和文件错误 | Gzip 文件 API |
| 校验和合并入口 | 校验和 API |
解码表分配和 ENOUGH 容量上界 |
表证明指南 |
| 翻译后实现的内存安全、所有权和终止性 | 安全性和证明方法 |
Examples.lean 提供规范的具体正反例检查。其证明使用内核求值、关系构造器或反演。
这些例子帮助审阅定义,不能建立它们与每个 RFC 情况或翻译后实现的对应关系。
差分测试同样提供一致性证据,而非证明。
实现细化的验收状态见进度。