Fmt 0.9.0导致代码崩溃,如何适配Format.pp_set_formatter_stag_functions
问题根因
Fmt 0.9.0版本新增了自定义的Format.stag扩展变体,用来内部维护样式渲染器的状态计数。你之前的实现直接替换了formatter的全部语义标签处理逻辑,要么对非String_tag的标签抛异常/清栈,要么直接忽略不处理,都会打断Fmt的内部状态流转:
- 抛异常会直接中断执行
- 直接忽略会导致Fmt的内部标签计数不匹配,触发它内部的断言失败报错
解决方案
不要直接替换整个formatter_stag_functions记录,而是链式调用原有标签处理函数:先保存formatter上已有的标签处理逻辑,你只处理自己需要的String_tag类型,其他所有未知标签都交给原有函数处理。
修改后代码示例
(* Format.stag is an extensible variant type, we only want to handle Format.String_tag *) exception UnhandledExtension of Format.stag ... let conditionally_raise e stack = match raise_errors with | true -> reset ppf; raise e | false -> Stack.clear stack in (* 先保存原有标签处理函数 *) let original_stag_funs = Format.pp_get_formatter_stag_functions ppf () in let color_tag_funs : Format.formatter_stag_functions = { mark_open_stag = (fun stag -> match stag with | Format.String_tag s -> begin match Lexer.tag_to_code @@ String.lowercase_ascii s with | Ok s -> Stack.push s stack; collapse stack | Error e -> conditionally_raise e stack; collapse stack end (* 未知标签交给原有处理函数,不修改自己维护的栈 *) | other -> original_stag_funs.mark_open_stag other ); mark_close_stag = (fun stag -> match stag with | Format.String_tag _ -> if not (Stack.is_empty stack) then ignore @@ Stack.pop stack; collapse stack (* 未知标签的关闭逻辑也交给原有处理函数 *) | other -> original_stag_funs.mark_close_stag other ); print_open_stag = (fun stag -> match stag with | Format.String_tag _ -> () | other -> original_stag_funs.print_open_stag other ); print_close_stag = (fun stag -> match stag with | Format.String_tag _ -> () | other -> original_stag_funs.print_close_stag other ); } in (* 替换为新的处理函数 *) Format.pp_set_formatter_stag_functions ppf color_tag_funs;
补充说明
- 该实现兼容所有依赖
Format.stag扩展的库,不止是Fmt 0.9.0+版本,所有使用自定义语义标签的第三方库都不会和你的颜色处理逻辑冲突 - 测试场景下Alcotest会提前初始化Fmt的标签处理逻辑,普通运行场景下没有其他标签处理函数时,原有函数就是标准库的默认实现,不会影响原有功能
内容的提问来源于stack exchange,提问作者Anentropic
相关产品推荐
相关产品推荐

