fq 开发路线图与已知缺陷全解析:从 doc/TODO.md 看 jq for binary formats 的演进方向
【免费下载链接】fqfq - jq for binary formats. Tool, language and decoders for working with binary formats.项目地址: https://gitcode.com/gh_mirrors/fq/fq
fq 是一个面向二进制格式的 jq 式工具与语言,用于解码、查询和转换各类二进制文件。本文以仓库内 doc/TODO.md 这份开发路线图文档为骨架,系统梳理 fq 当前已知的缺陷(Known bugs)、CLI/REPL、语言、函数、解码引擎、格式解码器、测试、文档与 gojq 子项目层面的改进计划,并逐一结合 pkg/interp、format 等目录下的源码实现展开印证。读完本文,你将能理解 fq 内部各模块(选项系统、REPL、解码引擎、格式注册)的真实运作方式、已知的边界问题,以及社区/贡献者可以切入的开发方向。
一、这份 TODO 文档在仓库中的定位
doc/TODO.md是 fq 项目的"已知问题与未来构想"清单,全文分为两大部分:
- Known bugs to fix:17 条已经被确认、但尚未修复的缺陷,覆盖解码正确性、REPL 交互、自动补全、选项作用域、性能等多个维度;
- TODO and ideas:按 CLI、CLI and REPL、Language、Functions、Tests、Documentation、Decode、Formats、Scripts、gojq、Big things 共 11 个子主题组织的功能规划,粒度从"一行配置改动"到"整个 Web 界面"不等。
它对应的开发指南是 doc/dev.md(新增格式解码器的步骤、解码器 API 约定、测试与 fuzz 流程),两者配合阅读可以还原 fq 从"主干功能"到"细节打磨"的完整演进脉络。本文聚焦 TODO 文档本身,源码仅作为佐证。
二、已知缺陷(Known bugs to fix):17 条未修复问题逐项解读
TODO 文档的第一部分是已确认的 bug 清单。这些条目虽然短,但每一条都指向具体的代码行为或交互边界,下面逐条结合源码展开。
2.1 十六进制切片后解码产生错误字节
fq -n '"aabbccdd" | hex | tobytes[1:] | decode("bytes") | tobytes'得到二进制aabbcc,而正确结果应为bbccdd。
该命令的意图是:把字符串aabbccdd按 hex 解码为 4 字节二进制,丢弃首字节后剩余 3 字节应为bb cc dd,但实际输出却是aabbcc(丢弃的是尾部而非头部)。TODO 作者推测是decode(此处为 raw 解码)被 root value 缓冲区搞混了。从实现看,二进制值在 pkg/interp/decode.go 中被表示为带 lazy 求值的gojqx.Lazy值,只有真正被消费(如tobytes)时才读取位范围,因此"基于位范围 + 根缓冲区"的切片语义极易在嵌套派生值上出错。这与 2.10 中提到的<array decode value>[{start: ...: end: ...}]区间语法问题同源,都属于"解码值切片/区间"这一底层设施不完善。
2.2 Buffer 与字符串的二义性
Buffers/string duality is confusing, most string functions should be wrapped to understand binary.
fq 中二进制数据(buffer)与字符串在查询层并存,但大多数字符串函数(如length、split、正则相关等)目前只能处理字符串而不能直接处理二进制 buffer。TODO 提出的方向是"把大部分字符串函数包装成能理解二进制"。这也是 pkg/interp/binary.jq 中tobits/tobytes体系(unit、keep_range、pad_to_units参数)需要与字符串函数协同的原因。
2.3 REPL 取消时子 REPL 清理不彻底
REPL cancel seems to sometimes exit a sub-REPL without properly cleanup options.
fq 的 REPL 支持嵌套(在表达式中通过| repl进入子 REPL,见 pkg/interp/repl.jq 中_repl对_options_stack的 push/pop 管理)。当用户在子 REPL 中按下取消(ctrl-c)时,有时会直接退出子 REPL 而没有正确清理_options_stack,导致选项状态泄漏到外层。与之相关的是 2.9 的"ctrl-c 只能中断求值解释器,无法中断父解释器的 display 输出"以及 2.10 的"ctxstack 索引取消顺序错误"——这三条共同指向用户中断(context cancel)机制需要整体重构,即 TODO 中单独列出的Rework cli/repl user interrupt (context cancel via ctrl-c)。
2.4 值错误只能通过._error访问
Value errors, can only be accessed with
._error.
当解码失败时,fq 并不会抛出异常,而是把错误信息挂在解码结果的._error字段上。从 pkg/interp/format_decode.jq 可以看到生成的from_<format>系列函数正是利用这一点做了"失败即报错"的封装:
def from_\(.key)($opts): decode(\(.key | tojson); $opts) | if ._error then error(._error.error) end;也就是说,decode("mp3"; {})返回的结果可能带._error,而from_mp3会把._error转成真正的 jq 错误。错误的具体结构在 pkg/interp/decode.go 中通过valueError与decode.FormatsError组装(包括多条格式候选解码失败时的vs数组)。TODO 认为这种"错误挂字段"的方式不够直观,未来可能引入更自然的错误通道。
2.5tovalue({bits_format: ...})只作用于根值
tovalue({bits_format: "base64"})only affect root value.
bits_format决定二进制值以何种字符串形式呈现(base64、hex、md5、byte_array、snippet、string、truncate 等)。问题是tovalue传入的bits_format选项只对根值生效,嵌套的二进制字段不受影响。完整的bits_format取值定义位于 pkg/interp/options.jq 的_opt_options中,测试用例见 pkg/interp/testdata/binary.fqtest(对test.mp3的audio_data依次尝试md5/hex/base64/snippet/byte_array等格式)。相关的全局选项默认值也在 pkg/interp/options.jq 的_opt_build_default_fixed中定义:
| 选项 | 默认值 | 说明 |
|---|---|---|
array_truncate | 50 | 数组显示截断长度 |
string_truncate | 50 | 字符串显示截断长度 |
addrbase/sizebase | 16 / 10 | 地址、大小显示进制 |
completion_timeout | 1(秒) | REPL 补全等待超时 |
decode_group | "probe" | 默认解码格式组 |
color | 随 TTY 与NO_COLOR自动判断 | 是否输出颜色 |
byte_colors | 三段式规则 | 字节值的着色规则(CSV 范围数组) |
2.6 非全局变量的自动补全与scope失效
Auto complete of non-global variables is broken.
scopeis broken for variables.
REPL 的自动补全实现在 pkg/interp/repl.jq 的_complete/_complete_scope中:_complete_scope通过scope | map(split("/")[0]) | unique收集当前作用域内的定义名与 jq 关键字。但scope对局部变量(如as $x引入的变量)并不可靠,导致$开头的变量补全失效;补全实现里也留有# TODO: handle variables via ast walk?的注释,说明作者认为正解应是从 AST 层面解析变量。相关的改进项还有 "Auto complete $variables"(见 3.2)。
2.7 多 JSON 输入时jq与fq行为不一致
echo '{} {} {}' | jqvsecho '{} {} {}' | fqworks differently. fq currently decodes one root format and might add unknown gap fields etc.
jq 会把每行 JSON 当作独立输入逐个输出;而 fq 默认把整个输入流交给probe组解码为单一根格式,还可能为未知间隙自动添加gap字段。TODO 提出或许json格式应当特殊处理,与 jq 的多输入语义对齐(这与 pkg/interp/options.jq 中slurp、null_input、filenames等选项的解析逻辑相关)。
2.8format/0与 jq 内建format/1的命名冲突
format/0overlap with jq builtinformat/1. What to rename it to?decode_format?
fq 提供了一个 0 参数的format函数(返回当前值的解码格式名),而 jq 内建了format/1(如"x" | format("%02d")这类字符串格式化)。二者共存造成语义冲突,TODO 提出的候选改名是decode_format。这属于命名层面的兼容性设计问题。
2.9 REPL 中大量输出无法被中断
repl expression returning a value that produced lots of output can't be interrupted. This is because ctrl-c currently only interrupts the eval interpreter, outputted value is printed (display) by parent interpreter.
表达式求值(eval)与结果展示(display)在 REPL 中分属不同层级:ctrl-c 能中断子解释器的求值,但一旦进入父解释器的display输出阶段(如打印.frames这种超大值),中断就失效了。TODO 在 pkg/interp/repl.jq 的_repl_eval中已经尝试"run display in sub eval so it can be interrupted"(把 display 放进子 eval 以便可中断),但根本解决仍依赖整体 context cancel 重构。
2.10 解码值区间切片语法损坏
<array decode value>[{start: ...: end: ...}]syntax a bit broken.
TODO 希望支持用对象形式对解码值取位区间切片([{"start": ..., "end": ...}]),但当前实现"有点坏"。这与 2.1 的 hex 切片 bug 同属位范围切片问题,也和 pkg/interp/query.jq 的查询语法解析相关。
2.11 REPL 补全可能产生副作用
REPL completion might have side effects. Make interp.Function type know and wrap somehow? input, inputs, open, ...
补全过程会实际执行候选函数查询来推断返回值类型(pkg/interp/repl.jq 的_query_index_or_key会对每个候选执行_eval($q; {}))。像input、inputs、open这类有副作用的函数(读取输入、打开文件)被补全触发时会消费输入或泄漏文件句柄。TODO 的设想是让interp.Function类型"知道自己是否有副作用"并加以包装防护——这与后续 3.4 中"open何时关闭文件、open泄漏、安全模式解释器、自动补全中是否允许open"等条目是一组问题。
2.12 group 参数机制需要重做
Rework group arguments so that
{is_probe:true}is not needed. Look up group name and see if it has an argument somehow?
probe是 fq 内置的解码格式组,用于自动探测输入属于哪种格式。当前探测行为依赖在选项中传递{is_probe:true}之类的内部标志(对应 pkg/interp/options.jq 中decode_group选项的 "probe" 默认值),TODO 希望改成"根据 group 名自动判断是否带参数"的声明式方式,去掉显式内部标志。
2.13Interp.Options性能问题
Optimize
Interp.Optionscalls, now called per display. Cache per eval? needs to handle nested evals.
Interp.Options目前每次display都会被调用,涉及_options_stack的合并计算(见 pkg/interp/options.jq 的options函数,它把stdout尺寸、选项栈与显式 opts 做add合并)。TODO 建议按 eval 缓存,但需处理嵌套 eval 的上下文切换。
三、CLI 与 REPL 改进计划
TODO 的第二大部分是对各子系统的功能构想,其中 CLI/REPL 相关的条目密度最高。
3.1 CLI
- Reset color at prompt(context cancel):提示符处重置终端颜色,结合 context cancel 一起处理,避免中断后终端颜色状态脏掉。相关实现位于 pkg/interp/repl.jq 的
_prompt(通过_ansi_if与options中的colors方案上色)。
3.2 CLI 与 REPL
- ctxstack index cancel 顺序错误,应改为跳过:中断发生时上下文栈的索引清理顺序不正确,TODO 认为更合理的做法是直接跳过而非逐层取消。
- Pager(分页器)支持长输出:希望引入
$PAGER或某种显式语法(如.. | less)来分页浏览长输出。这对应dump输出超大根值时(如.frames)的体验问题。 dump大根值输出不可取消:与 2.9 同一根源,dump由父 REPL 执行,无法被 ctrl-c 中断。- 错误位置
^指针:编译错误时在输入行下方用^标注出错列。实际上 pkg/interp/repl.jq 的_repl_on_compile_error已经实现了这个功能(含 unicode 模式下的⬆箭头),TODO 条目属于当时的规划,如今已有雏形。 - 可配置的历史文件名:REPL 历史记录文件目前未提供配置入口。
- 补全
$variables:支持$变量的自动补全,与 2.6 的 scope 问题相关。 - 补全需要转义的 key:对需要引号转义的字段名目前"直接过滤掉",希望补全时能自动加引号改写前面的输入。
- 补全对象时自动追加
.:当候选唯一且是对象时,自动补全一个.以提示继续下钻(pkg/interp/repl.jq 的_query_index_or_key已据此返回.或[])。 - 支持
JQ_COLORS且扩展为name=形式:fq 已有自己的colors选项(null、false、number、string、objectkey、error、dumpheader、prompt_repl_level等键,见 pkg/interp/options.jq),希望兼容 jq 的JQ_COLORS环境变量并扩展出按值类型/语义命名的配色方式。
四、语言与函数层规划
4.1 语言层面
- 清理/让二进制 buffer 语义自洽:与 2.2 呼应,需要从语言层统一 buffer 与字符串的表示与操作。
- gojq 用 golang
int作切片索引,非 64 位 CPU 上可能出问题:这与 gojq 子项目的移植深度有关,属于架构层备注。
4.2 函数层面
- buffer 截断、左右填充:希望新增 buffer 的
truncate、左/右pad类函数。 toimage(终端内联图片):可以用 CLI 输出"\x1b]1337"(iTerm2 的 inline image 协议)实现,但 TODO 认为也许更适合做成 UI 功能。toplot(终端内绘图):类似toimage的可视化构想。dump支持二进制、列代码通用化:希望dump、hexdump(可能还有bindump)共享同一套列渲染代码,且能处理二进制数据。dump对行范围不连续处着色/提示:当输出覆盖的位范围有跳跃时给出视觉提示。hexdump等处理非字节对齐数据:当前hexdump假定字节对齐,需要支持 1 字节以内的位宽数据。- 重做 cipher 函数接口:TODO 给出了两种候选风格——
ctr(aes("key"), "iv")或cipher(ctr("iv"), aes("key")),即"算法/模式/参数"如何组合的 API 设计问题。 open何时关闭文件、open的泄漏问题(文件与 ctxreadseeker):打开的文件句柄与可取消读取器需要明确的生命周期管理(对应internal/ctxreadseeker、internal/aheadreadseeker等 IO 栈组件)。- 安全模式解释器:设想一个禁止
open等危险操作的沙箱化解释器,同时配套"自动补全中是否允许open"的策略(见 2.11)。 - 摘要树(summary tree):按格式输出各部分的摘要统计(样本数量等)。
- 以紧凑形式列出所有唯一路径:便于快速浏览二进制结构中所有可达的查询路径。
五、测试与文档规划
5.1 测试
update测试不保留readlines的注释顺序:-update模式重写期望输出时注释会被打乱(相关测试框架见 pkg/fqtest)。- 空文件测试:需要覆盖空输入文件的解码/错误行为。
- CLI 测试:raw 输出写入、颜色输出等端到端用例。
- 交互式测试:REPL 等交互场景的自动化测试(仓库中已有
pkg/cli/test_repl.exp、test_cli_ctrlc.exp、test_cli_ctrld.exp等 expect 脚本作为基础)。
5.2 文档
help("topic")函数:在 REPL 内通过 jq 函数调用的形式查看帮助(fq 的 CLI 帮助系统见 doc/fq.1.tmpl.adoc,格式帮助则通过//go:embed嵌入各format/*/*.md)。- 从源码生成文档:用
make doc生成 README 与 man page(需要 FFmpeg 与 Graphviz,见 doc/dev.md 与 Makefile)。 -n、inputs/0、input/0行为文档化:与 jq 保持一致(相关选项定义见 pkg/interp/options.jq 的null_input、filenames)。- 提及
empty.something:jq 的empty与字段访问组合的边界行为。 - 采用 JQ-Distilled 的记号法:TODO 引用了一个 jq 教学文档的表示风格来规范 fq 的 jq 相关文档写法。
- 带交互示例的手册:README 中已有交互式示例(如
./fq命令与demo.svg),TODO 希望手册级文档也配套可交互示例。
六、解码引擎(Decode)重构方向
TODO 中解码引擎层面的规划最能反映 fq 的架构演进思路:
- 用接口节省内存:设想引入
Value V接口,使 U、Str 等类型有各自的实现,替代当前统一的对象模型。当前值模型是scalar.S+*decode.Compound(见 doc/dev.md 的 "Decoder API" 一节),TODO 认为可以更省内存。 - "装饰"(sym、显示格式)数组化:符号值(sym)与显示格式目前以字段形式挂在值上,希望改为数组式的装饰列表,便于扩展。
- 保存原始文件名:目前只在 description 字段中粗略记录来源。
- 更合理的"合成值"(synthetic values):当前合成值长度为 0,语义不完整。
- 清理并重想嵌套 buffer(zip、ogg 这类 muxed 格式):嵌套缓冲区的读取与共享是格式组合的基础设施。
- 大小端位域辅助(elf 等):为按位域解析的格式(如 ELF)提供统一的大小端位域读取助手。
- 清理校验和机制:TODO 希望校验和直接作为普通字段,并在不匹配时输出 warning 而不是失败。
- 用 jq 写解码器:设想直接用 jq 的数组/对象语法编写解码器,传递解码上下文、收集字段并构建树。目前
format_decode.jq只生成decode/from_<format>的调用封装,真正的解码器仍是 Go 代码。 - 控制/限制嵌套解码深度:如
probe({depth:1})或按格式跳过解码,用于处理超大文件的部分解码。 - 解码过程中无法使用 range:由于解码时范围尚未计算完成,
range查询在解码进行中不可用。 - 跟踪值的编码信息:为每个值记录其编码(
u16le、utf8、varint等),便于展示与再编码。 - 可选忽略范围检查:提供"解码到读取错误为止"的模式,例如截断的 mp4
mdat也能尽力解码而不是失败。
七、格式解码器(Formats)扩展清单
TODO 列出了待新增/改进的具体格式:
- 新增解码器:
asn1_ber/asn1_der/asn1_cer(ASN.1 目前已有 format/asn1/asn1_ber.go,DER/CER 是后续拆分)、flatbuffer、capnproto、dsf。 - 格式参数传递:支持向格式解码器传递参数。
- jq 中的值解码器:如
u(32)、u32,在查询层直接按数值类型解码片段。 - warning 与 error 体系:为
mp4(sample counts)、flac(截断的 picture、混用采样率/位深等)增加一致性校验告警。 protobufschema 支持、matroskacrc、mp4styp segment 测试。- 格式成熟度文档化:为每个格式标注成熟度/完整度。
json格式规范化:TODO 直言"json is a bit of a special case now"(与 2.7 的多输入问题相关)。- exif in mp4(heif/heic):TODO 给出了一个完整的端到端示例查询,展示了 fq 如何组合
grep、parent、select、tobytes区间切片与exif解码器:
. as $r | grep("iloc") | parent.items[] | select(.id == (first($r | grep("exif";"i")) | parent.id)).extends[0] as $e | $r | tobytes[$e.offset+10:$e.offset+$e.length] | exif该查询先从ilocbox 中定位exif数据项(grep("exif";"i")为大小写不敏感搜索,grep实现在 pkg/interp/grep.jq),再通过tobytes按偏移与长度切出 EXIF 字节流交给exif解码器。这也是 pkg/interp/binary.jq 中tobytes(unit: 8、pad_to_units: 0)的实际用法范例。
八、脚本(Scripts)与 gojq 子项目
8.1 脚本层
- 基于常见字段名的探测工具:
probe之外,提供按常见字段名猜格式的辅助脚本。 - MIME codec 编解码器:支持
avc1.PPCCLL这类 RFC 6381 风格的 MIME 编码器/解码器字符串。 - 针对 mp4、matroska 的校验脚本:用 fq 语言本身编写格式验证脚本。
8.2 gojq 子项目
fq 使用的 gojq fork(github.com/wader/gojq的fq分支)相关的 TODO 包括:
- 常见错误在本地重新实现:TODO 注明 "Common errors with gojq? re-implemented now",即部分错误处理已改为在 fq 内自行实现。
0b字面量问题:0b在 bin/hex/... 字面量变更后出现1.7976931348623157e+308之类的异常值,属于 gojq fork 的数值字面量解析回归。- 仿照 gojq 的
builtin.go做提速:把常用内建函数做成本地实现以提升性能。 - 移除
scopedump:清理调试用的作用域转储函数。
九、更大愿景(Big things)
TODO 的最后一部分是偏长期的产品形态构想:
- fq play 网站:在线演示/练习站点。
- UI/Web 界面:树形界面、多 REPL 窗口、在 hex 视图中更优雅地展示重叠字段。
- Jupyter notebook 集成:让 fq 成为 notebook 中的内嵌工具。
- FUSE 接口:把二进制文件"挂载"成文件系统,通过目录/文件访问其结构。
- 懒解码(Lazy decode):对已知大小的结构延迟解码,既能提升性能也能节省内存(可通过重新解码替代)。
十、对使用者与贡献者的意义
doc/TODO.md不仅是开发者的待办清单,对 fq 用户也有实际参考价值:
- 规避已知边界:上述 2.1(hex 切片)、2.4(
._error访问错误)、2.5(bits_format仅作用根值)、2.7(多输入行为差异)等问题意味着在使用对应功能时要留意行为与直觉不符之处。 - 理解选项与 REPL 机制:pkg/interp/options.jq 集中定义了全部可配置项(
-o name=value),pkg/interp/repl.jq 展示了 REPL 的补全、提示符、slurp/spew等交互设计。 - 贡献切入点清晰:TODO 条目大多有明确验收标准(如"
tobytes[1:]后应为bbccdd"、"format/0改名"),配合 doc/dev.md 中的新增格式步骤(创建format/<name>目录、注册到 format/format.go 与 format/all/all.go、生成testdata/*.fqtest、make lint与make fuzz GROUP=<name>),可以快速上手提交修复或新格式。
总的来说,doc/TODO.md呈现了一个仍在快速演进中的二进制解析工具:核心解码与查询能力已经成型,但 buffer 语义、REPL 交互、中断模型与嵌套解码等"深水区"仍在持续打磨,而格式解码器生态的扩展(DER/CER、flatbuffer、capnproto、dsf)则预示着 fq 的适用范围还在不断拓宽。
【免费下载链接】fqfq - jq for binary formats. Tool, language and decoders for working with binary formats.项目地址: https://gitcode.com/gh_mirrors/fq/fq
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考