fq 开发路线图与已知缺陷全解析:从 doc/TODO.md 看 jq for binary formats 的演进方向
2026/9/24 17:19:41 网站建设 项目流程

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 项目的"已知问题与未来构想"清单,全文分为两大部分:

  1. Known bugs to fix:17 条已经被确认、但尚未修复的缺陷,覆盖解码正确性、REPL 交互、自动补全、选项作用域、性能等多个维度;
  2. 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)与字符串在查询层并存,但大多数字符串函数(如lengthsplit、正则相关等)目前只能处理字符串而不能直接处理二进制 buffer。TODO 提出的方向是"把大部分字符串函数包装成能理解二进制"。这也是 pkg/interp/binary.jq 中tobits/tobytes体系(unitkeep_rangepad_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 中通过valueErrordecode.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.mp3audio_data依次尝试md5/hex/base64/snippet/byte_array等格式)。相关的全局选项默认值也在 pkg/interp/options.jq 的_opt_build_default_fixed中定义:

选项默认值说明
array_truncate50数组显示截断长度
string_truncate50字符串显示截断长度
addrbase/sizebase16 / 10地址、大小显示进制
completion_timeout1(秒)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 输入时jqfq行为不一致

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 中slurpnull_inputfilenames等选项的解析逻辑相关)。

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; {}))。像inputinputsopen这类有副作用的函数(读取输入、打开文件)被补全触发时会消费输入或泄漏文件句柄。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性能问题

OptimizeInterp.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_ifoptions中的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选项(nullfalsenumberstringobjectkeyerrordumpheaderprompt_repl_level等键,见 pkg/interp/options.jq),希望兼容 jq 的JQ_COLORS环境变量并扩展出按值类型/语义命名的配色方式。

四、语言与函数层规划

4.1 语言层面

  • 清理/让二进制 buffer 语义自洽:与 2.2 呼应,需要从语言层统一 buffer 与字符串的表示与操作。
  • gojq 用 golangint作切片索引,非 64 位 CPU 上可能出问题:这与 gojq 子项目的移植深度有关,属于架构层备注。

4.2 函数层面

  • buffer 截断、左右填充:希望新增 buffer 的truncate、左/右pad类函数。
  • toimage(终端内联图片):可以用 CLI 输出"\x1b]1337"(iTerm2 的 inline image 协议)实现,但 TODO 认为也许更适合做成 UI 功能。
  • toplot(终端内绘图):类似toimage的可视化构想。
  • dump支持二进制、列代码通用化:希望dumphexdump(可能还有bindump)共享同一套列渲染代码,且能处理二进制数据。
  • dump对行范围不连续处着色/提示:当输出覆盖的位范围有跳跃时给出视觉提示。
  • hexdump等处理非字节对齐数据:当前hexdump假定字节对齐,需要支持 1 字节以内的位宽数据。
  • 重做 cipher 函数接口:TODO 给出了两种候选风格——ctr(aes("key"), "iv")cipher(ctr("iv"), aes("key")),即"算法/模式/参数"如何组合的 API 设计问题。
  • open何时关闭文件open的泄漏问题(文件与 ctxreadseeker):打开的文件句柄与可取消读取器需要明确的生命周期管理(对应internal/ctxreadseekerinternal/aheadreadseeker等 IO 栈组件)。
  • 安全模式解释器:设想一个禁止open等危险操作的沙箱化解释器,同时配套"自动补全中是否允许open"的策略(见 2.11)。
  • 摘要树(summary tree):按格式输出各部分的摘要统计(样本数量等)。
  • 以紧凑形式列出所有唯一路径:便于快速浏览二进制结构中所有可达的查询路径。

五、测试与文档规划

5.1 测试

  • update测试不保留readlines的注释顺序-update模式重写期望输出时注释会被打乱(相关测试框架见 pkg/fqtest)。
  • 空文件测试:需要覆盖空输入文件的解码/错误行为。
  • CLI 测试:raw 输出写入、颜色输出等端到端用例。
  • 交互式测试:REPL 等交互场景的自动化测试(仓库中已有pkg/cli/test_repl.exptest_cli_ctrlc.exptest_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)。
  • -ninputs/0input/0行为文档化:与 jq 保持一致(相关选项定义见 pkg/interp/options.jq 的null_inputfilenames)。
  • 提及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查询在解码进行中不可用。
  • 跟踪值的编码信息:为每个值记录其编码(u16leutf8varint等),便于展示与再编码。
  • 可选忽略范围检查:提供"解码到读取错误为止"的模式,例如截断的 mp4mdat也能尽力解码而不是失败。

七、格式解码器(Formats)扩展清单

TODO 列出了待新增/改进的具体格式:

  • 新增解码器asn1_ber/asn1_der/asn1_cer(ASN.1 目前已有 format/asn1/asn1_ber.go,DER/CER 是后续拆分)、flatbuffercapnprotodsf
  • 格式参数传递:支持向格式解码器传递参数。
  • jq 中的值解码器:如u(32)u32,在查询层直接按数值类型解码片段。
  • warning 与 error 体系:为mp4(sample counts)、flac(截断的 picture、混用采样率/位深等)增加一致性校验告警。
  • protobufschema 支持matroskacrcmp4styp segment 测试
  • 格式成熟度文档化:为每个格式标注成熟度/完整度。
  • json格式规范化:TODO 直言"json is a bit of a special case now"(与 2.7 的多输入问题相关)。
  • exif in mp4(heif/heic):TODO 给出了一个完整的端到端示例查询,展示了 fq 如何组合grepparentselecttobytes区间切片与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 中tobytesunit: 8pad_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/gojqfq分支)相关的 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 用户也有实际参考价值:

  1. 规避已知边界:上述 2.1(hex 切片)、2.4(._error访问错误)、2.5(bits_format仅作用根值)、2.7(多输入行为差异)等问题意味着在使用对应功能时要留意行为与直觉不符之处。
  2. 理解选项与 REPL 机制:pkg/interp/options.jq 集中定义了全部可配置项(-o name=value),pkg/interp/repl.jq 展示了 REPL 的补全、提示符、slurp/spew等交互设计。
  3. 贡献切入点清晰:TODO 条目大多有明确验收标准(如"tobytes[1:]后应为bbccdd"、"format/0改名"),配合 doc/dev.md 中的新增格式步骤(创建format/<name>目录、注册到 format/format.go 与 format/all/all.go、生成testdata/*.fqtestmake lintmake 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),仅供参考

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

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

立即咨询