ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Ladybird AK 库字符串格式化系统详解:格式语法、编译期检查与自定义 Formatter 实现

Ladybird AK 库字符串格式化系统详解:格式语法、编译期检查与自定义 Formatter 实现 Ladybird AK 库字符串格式化系统详解格式语法、编译期检查与自定义 Formatter 实现【免费下载链接】ladybirdTruly independent web browser项目地址: https://gitcode.com/GitHub_Trending/la/ladybirdLadybird 的 AK 基础库提供了一套类printf的现代 C 字符串格式化系统被ByteString::formatted()、StringBuilder::appendff()、dbgln()等广泛使用。本篇基于仓库文档 StringFormatting.md 展开并结合 AK/Format.h、AK/Format.cpp 与测试 Tests/AK/TestFormat.cpp完整讲解格式字符串的语法、格式说明符的组合规则、编译期参数一致性检查的原理以及如何为自己的类型编写AK::Formatter特化。一、基本用法位置替换与转义系统文档指出这套格式化语法借鉴了 Cstd::formatter的标准格式说明符语法但存在一些差异。最基础的用法是格式字符串中每出现一个{}就按顺序替换为下一个参数并将其转换为字符串形式ByteString::formatted(Well, {} my {} friends!, hello, 42) Well, hello my 42 friends!;如果需要在输出中包含字面量{使用双写转义{{对称地}}输出字面量}ByteString::formatted({{ {}, hello) { hello;这一转义在实现中由FormatParser::consume_literal()处理解析器在扫描字面量文本时遇到{{或}}会跳过两个字符继续遇到单个{或}才判定字面量段结束见 Format.cpp。还可以用显式索引引用参数实现重复引用或调整顺序ByteString::formatted({2}{0}{1}, a, b, c) cab;运行时解析时{}这类不带索引的替换字段会被赋予下一个隐式索引consume_specifier()中若没有读到数字索引就将索引标记为use_next_index再由参数包按顺序分配见 Format.cpp。二、格式说明符的完整结构要控制参数的具体呈现方式在索引可选之后加冒号:后面跟格式说明符。文档给出的例子ByteString::formatted({:.4}, cool dude) cool; ByteString::formatted({0:.4}, cool dude) cool;按出现顺序一个完整的格式说明符最多包含以下七个部分填充字符与对齐方式Fill and alignment符号Sign#哈希alternate form0零填充宽度Width精度Precision类型说明符Type specifier每一部分都是可选的可以任意组合但必须按上述顺序出现。这个顺序约束在源码中体现得非常直白StandardFormatter::parse()就是按填充/对齐 → 符号 →#→分隔符 →0→ 宽度 →.精度 → 类型字符的固定顺序逐段消费输入流见 Format.cpp。填充与对齐说明符开头可以是一个填充字符后跟对齐字符。填充字符可以是除{和}之外的任意字符用于在内容不足目标宽度时填充剩余空间默认填充为空格。对齐字符有三种左对齐右对齐^居中对齐实现层面StandardFormatter::parse()先探测peek(1)是否落在^三者之前的位置来识别填充对齐组合然后分别消费对齐符号并映射为FormatBuilder::Align::{Left,Center,Right}见 Format.cpp。测试用例覆盖了各种填充组合EXPECT_EQ(ByteString::formatted({:*8}, 13), 13******); EXPECT_EQ(ByteString::formatted({:*^8}, 13), ***13***); EXPECT_EQ(ByteString::formatted({:*8}, 13), ******13);来自 TestFormat.cpp符号始终在数字前显示正负号-仅对负数显示符号空格负数显示负号非负数前补一个空格对应实现中的FormatBuilder::SignMode::{OnlyIfNeeded, Always, Reserved}三态见 Format.h。哈希##表示使用替代形式alternate form。对整型它在符号之后加上进制前缀二进制0b八进制0十六进制0x测试中可以看到具体效果{:#4X}格式化的0xff输出0XFF{:#b}输出0b11111111见 TestFormat.cpp。零填充00表示用前导零填充数字。测试用例还揭示了一个实现细节零填充时前缀紧跟在符号之后例如{:#06x}格式化-64得到-0x000040即前缀0x不会被计入填充区这一行为在FormatBuilder::put_u64()中有专门注释说明见 Format.cpp。宽度与精度宽度width定义输出的最小字符数精度是.后跟一个数字对浮点数表示小数精度对字符串表示最大宽度。值得注意的是宽度和精度都可以写成替换字段{}可带参数索引从而用一个整型参数在运行时指定数值EXPECT_EQ(ByteString::formatted({:*{1}}, 13, static_castsize_t(10)), ********13); EXPECT_EQ(ByteString::formatted({:0{}}, 1, 3), 001);来自 TestFormat.cppStandardFormatter::parse()中通过consume_replacement_field()读取这类内嵌索引再用TypeErasedParameter::to_size()取出整数值作为宽度或精度见 Format.cpp。类型说明符文档给出的类型说明符总表如下类型效果示例输出nothing默认格式Anything! :^)b二进制110、0b000110B二进制大写110、0B000110d十进制42、0000042o八进制043x十六进制ff0、0x00000ff0X十六进制大写FF0、0X00000FF0c字符as字符串well, hello friends!p指针0xdeadc0def定点浮点1.234、-infa十六进制浮点A十六进制浮点大写hex-dump十六进制转储fdfdfdfd、3030 00并非所有类型说明符都能与所有输入类型搭配使用——例如对StringView指定符号模式或零填充会触发VERIFY_NOT_REACHED()见FormatterStringView::format()Format.cpp。除文档列出的说明符外从源码结构看StandardFormatter还解析一个文档未单独列出、但实现与测试都在使用的千位分隔符标记m_use_separator字段例如EXPECT_EQ(ByteString::formatted({:}, 4096), 4,096); EXPECT_EQ(ByteString::formatted({:x}, 16777216), 1,000,000);来自 TestFormat.cppStandardFormatter字段定义见 Format.hhex-dump说明符则由FormatBuilder::put_hexdump()实现每字节输出两位十六进制指定宽度时每行附加一列可打印字符视图见 Format.cpp 与测试 TestFormat.cpp。三、编译期检查格式串与参数数量的一致性文档强调格式串在编译期被检查确保与参数数量匹配。这一机制由 AK/CheckedFormatString.h 实现所有格式化入口接收的都不是裸字符串字面量而是CheckedFormatStringParameters...模板类型它用consteval构造函数在编译期执行检查见 CheckedFormatString.h。在ENABLE_COMPILETIME_FORMAT_CHECK宏启用时check_format_parameter_consistency()用consteval的count_fmt_params()逐字符扫描格式串做四类检查任一失败都会以compiletime_fail报错见 CheckedFormatString.h存在未闭合的花括号Extra unclosed braces in format string存在多余闭合花括号Extra closing braces in format string索引引用了不存在的参数Format string references nonexistent parameter有参数从未被引用Format string does not reference all passed parameters。第 4 条有一个巧妙的前提只有格式串显式使用了索引引用如{1}{0}时才会逐参数做包含性检查纯隐式{}场景下隐式索引单调递增不可能跳号用引用总数 参数总数即可覆盖。测试中需要故意绕过编译期检查时会用StringView后缀sv显式构造非检查路径并留有注释说明// Compiletime check bypass: ignoring a passed argument. EXPECT_EQ(ByteString::formatted({0}{0}{0}sv, a, b), aaa);来自 TestFormat.cpp从源码结构看count_fmt_params()的扫描算法本身就是一个小型状态机用固定大小的Arraysize_t, 128记录已用索引支持嵌套替换字段last_format_specifier_start最多支持 4 层嵌套且为避免循环依赖而内置了一个纯 constexpr 的局部Array实现见 CheckedFormatString.h。四、运行时架构参数类型擦除与递归渲染理解了语法后运行时执行路径可以从 AK/Format.h 和 AK/Format.cpp 中还原出来参数包装VariadicFormatParams将每个实参转换为TypeErasedParameter——一个带类型标签的联合体把实参归约为UnsignedInteger / SignedInteger / Boolean / Character / Float / Double / StringView / CString / CustomType九类见 Format.h。参数个数上限为max_format_arguments 256由static_assert保证见 Format.h。驱动循环vformat_impl()递归地交替执行消费一段字面量 → 解析一个替换字段 → 渲染对应参数直到格式串结束见 Format.cpp。参数分发每个TypeErasedParameter通过visit()按类型标签分派内置类型走__format_valueT()实例化对应的FormatterT特化CustomType则调用构造时绑定的格式化函数指针见 Format.h。渲染输出所有最终写操作汇聚到FormatBuilder它同时支持 UTF-8 的StringBuilder和 UTF-16 的Utf16StringBuilder两种目标核心方法包括put_string、put_u64、put_i64、put_fixed_point、put_f32_or_f64、put_f80、put_hexdump声明见 Format.h。一个值得一提的架构细节是debug-only formatter机制FormatterErrorOrT, ErrorType声明了static constexpr bool is_debug_only() { return true; }而VariadicFormatParams的构造函数带有编译期断言——若在普通非调试格式化的上下文中混入 debug-only 参数会直接给出编译错误提示Maybe one of your format values is an ErrorOr ?防止误把ErrorOr打进生产日志见 Format.h 与 Format.h。dbgln()、dbg()、outln()等调试/输出入口均使用AllowDebugOnlyFormatters::Yes见 Format.h。五、为自定义类型提供格式化能力文档给出了最典型的自定义格式化方式为类型Foo提供AK::FormatterFoo特化。最简单的情况是复用已有的类型 → 字符串函数template struct AK::FormatterWeb::CSS::Selector : FormatterStringView { ErrorOrvoid format(FormatBuilder builder, Web::CSS::Selector const selector) { return FormatterStringView::format(builder, selector.serialize()); } };其原理是默认的FormatterT是一个含哨兵成员的模板用于HasFormatter检测为具体类型显式特化即视为可格式化见 Format.h。文档还指出更高级的 formatter 可以通过读取StandardFormatter的字段来检查格式标志。StandardFormatter携带解析后的全部状态对齐、符号模式、填充字符、零填充、宽度、精度、ModeDefault/Binary/…/HexDump等见 Format.h。仓库测试里有一个使用格式化字符串递归拆解的例子展示了继承FormatterFormatString来组合格式化的写法FormatString的 formatter 接受格式串加参数再递归vformat见 Format.hstruct C { int i; }; template struct AK::FormatterC : AK::FormatterFormatString { ErrorOrvoid format(FormatBuilder builder, C c) { return AK::FormatterFormatString::format(builder, C(i{})sv, c.i); } }; EXPECT_EQ(ByteString::formatted({:*10}, C { 42 }), C(i42)***);来自 TestFormat.cpp从源码结构看AK 还为一批通用容器和包装类型内置了 formatter均可直接出现在格式串中ReadonlySpanT/SpanT/VectorT输出[ 1, 2, 3, 4 ]形式的元素列表见 Format.h 与测试 TestFormat.cppCheckedT溢出时输出{ OVERFLOW }见 Format.hOptionalT空值输出None见 Format.hError输出错误描述见 Format.hReadonlyBytes/Bytes默认按 hex-dump 呈现见 Format.hDurationAK/Time.h中的时长类型测试覆盖了科学计数、符号与填充组合如{:^#8}格式化 1512ms 得 1.512s 见 TestFormat.cpp。六、判断类型是否可格式化HasFormatter 与 FormatIfSupported当格式串的调用方无法在编译期确定参数类型可格式化例如泛型代码仓库提供了两个工具AK::HasFormatterT布尔常量表示T是否有 formatter。其实现利用了默认模板中的哨兵成员若FormatterT未被特化则存在FormatterT::__no_formatter_defined据此偏特化为false见 Format.hFormatIfSupportedT包装器格式化时若T支持则输出其格式结果否则输出若干个?。文档中的示例与测试完全对应测试中的struct A/struct B分别代表无/有 formatter 的类型// B has a Formatter defined, but A does not. ByteString::formatted({}, FormatIfSupported { A {} }) ?; ByteString::formatted({}, FormatIfSupported { B {} }) B;实现上FormatterFormatIfSupportedT根据HasFormatterT选择两个偏特化中的某一个支持的转发给FormatterT不支持的固定输出?sv见 Format.h 与测试 TestFormat.cpp。七、常用入口 API 一览文档开头列举了三个最常用的格式化入口它们在仓库中均可直接定位API位置说明ByteString::formatted()AK/ByteString.h格式化并返回新的ByteStringStringBuilder::appendff()AK/StringBuilder.h将格式化结果追加到构建器dbgln()/dbg()AK/Format.h调试输出允许 debug-only formatteroutln()/warnln()AK/Format.h输出到stdout/stderrAndroid 平台映射到__android_log见 AK/Format.h此外还有若干实用宏outln_if(flag, ...)、warnln_if(flag, ...)、dbgln_if(flag, ...)基于if constexpr在编译期消除不满足条件的分支dbgln_dump(expr)则输出#expr: {}形式的表达式与值见 AK/Format.h。八、总结Ladybird 的字符串格式化系统是一套语法兼容std::format、检查前移到编译期、运行时零字符串解析负担的设计语法层{index:fillalignsign#0width.precision type}七段式说明符全部可选但顺序固定另有千位分隔与hex-dump等扩展编译期CheckedFormatString以consteval扫描格式串把括号不匹配、引用不存在参数、参数未使用这类错误变成编译错误运行期TypeErasedParameter九类标签联合体 FormatParser/FormatBuilder的递归渲染管线配合AllowDebugOnlyFormatters断言防止ErrorOr等类型泄漏到生产日志扩展点特化AK::FormatterT即可让任意类型参与格式化HasFormatterT与FormatIfSupported则支撑泛型场景下的安全格式化。完整的格式化行为契约沉淀在 Tests/AK/TestFormat.cpp 中覆盖整数进制、对齐填充、浮点精度、十六进制转储、容器/包装类型等场景是验证本文所述行为最可靠的依据。【免费下载链接】ladybirdTruly independent web browser项目地址: https://gitcode.com/GitHub_Trending/la/ladybird创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表