资讯动态

Verible 外部库管理策略与 editscript 序列差异算法详解

发布时间:2026/10/14 22:33:54 来源:尧图企业网站定制
开发工具Lint静态分析硬件开发【免费下载链接】veribleVerible is a suite of SystemVerilog developer tools, including a parser, style-linter, formatter and language server项目地址https://gitcode.com/gh_mirrors/ve/verible点击查看免费下载Verible 对外部依赖采取“Bazel 依赖解析优先、仓库内 vendored 拷贝兜底”的两级管理策略而external_libs/目录正是该策略的落地位置。本文基于 external_libs/README.md 展开结合 external_libs/editscript.h、external_libs/editscript_test.cc 及其在verible/common/strings/diff.h、formatter 等处的实际调用链讲清 Verible 如何组织外部库、editscript 这个模板化序列 diff 算法库的内部原理以及它在 Verible 增量格式化、修复补丁输出等场景中的具体用途。读完本文你将能够理解如何在 Bazel 工作区中定位并使用该库并能读懂其 Myers O(ND) 二分递归 diff 的实现细节。一、Verible 的外部依赖管理策略Bazel 优先vendored 拷贝兜底external_libs/README.md 的核心内容只有两点首选方式外部依赖应通过 Bazel 工作区原文明确提到 WORKSPACE 文件机制来引入这是 Bazel 工程的标准做法兜底方式当某些场景下该工作流不被良好支持时external_libs/目录内直接存放来自外部来源的库的拷贝vendored copy。也就是说external_libs/不是“临时堆代码的地方”而是一个有明确准入标准的目录只有当某个外部库无法或不便通过 Bazel 的依赖解析机制引入时才允许在这里保留一份受仓库自身版本控制、并附带许可证声明的副本。当前仓库的实际依赖形态需要说明的是当前仓库快照的依赖声明已从早期的 WORKSPACE 文件演进为BzlmodBazel 模块化依赖管理。根目录下的 MODULE.bazel 声明了模块名verible版本head并以bazel_dep规则引入 abseil-cpp、nlohmann_json、protobuf、re2、rules_bison、rules_flex、googletest 等外部依赖。这印证了 README 所述“首选 Bazel 机制”的策略——绝大多数字符串、JSON、正则、词法/语法生成工具链依赖都走模块解析。那么为什么仍需要一个 vendored 目录答案就在目录里唯一的库editscript。它是一个纯头文件header-only、模板化的序列差异sequence diff算法源自 Google 的 diff-match-patch 项目见文件头部 editscript.h 的版权与出处注释Apache 2.0 许可特别注明GetBisectSplitPoints部分派生自 diff_match_patch.cpp。这类小型算法库单独发布一个 Bazel 模块的收益很低直接放入仓库内管理反而更简洁因此成为 README 所说“该工作流不被良好支持”情形的典型代表。BUILD 规则如何构建和测试external_libs/BUILD 完整定义了该包的构建目标# This package contains externally owned libraries. # # editscript - is a template-generic sequence diff algorithm based on # diff-match-patch. load(rules_cc//cc:defs.bzl, cc_library, cc_test) package( default_applicable_licenses [//:license], # 复用顶层 Apache-2.0 许可证声明 default_visibility [//:__subpackages__], # 只对本仓库各包可见 features [layering_check], ) cc_library( name editscript, hdrs [editscript.h], # header-only只有头文件无实现源文件 ) cc_test( name editscript_test, srcs [editscript_test.cc], deps [ :editscript, abseil-cpp//absl/strings, googletest//:gtest, googletest//:gtest_main, ], )几个值得注意的工程细节cc_library只列了hdrs没有任何srcs这是 header-only 库在 Bazel 中的标准写法——消费者如 verible/common/strings/BUILD 中的//external_libs:editscript依赖只需包含头文件即可编译default_visibility限定为//:__subpackages__即外部库只能被本仓库内部包使用不会泄露到仓库外测试目标依赖 abseil 的str_join与 googletest与 MODULE.bazel 中声明的外部模块一一对应。在本地验证时可以运行bazel build //external_libs:editscript bazel test //external_libs:editscript_test二、editscript 库 API 剖析Edit 数据结构与 GetTokenDiffseditscript 的整个公开 API 非常精简一个Operation枚举、一个Edit结构体、一个Edits类型别名以及唯一的入口函数模板GetTokenDiffs。文件注释给出了它的定位editscript.h#L24-L26返回将一组 token 容器变换为另一组所需的最小编辑操作数copy、delete、insert要求 token 流支持随机访问迭代器cf.diff -d -e。2.1 核心数据结构enum class Operation { EQUALS, DELETE, INSERT }; struct Edit { Operation operation; // One of: EQUALS, DELETE, or INSERT. int64_t start; // Start offset into tokens1 (tokens2 for INSERT). int64_t end; // End offset into tokens1 (tokens2 for INSERT). }; using Edits std::vectorEdit;摘自 editscript.h#L40-L62这里有一条极易混淆但至关重要的索引约定头文件注释 L43-L48 明确强调EQUALS和DELETE的[start, end)半开区间指向 tokens1源序列INSERT的[start, end)指向 tokens2目标序列把所有EQUALS与DELETE区间的 token 按序拼接恰好还原 tokens1把所有EQUALS与INSERT拼接恰好还原 tokens2。Edit实现了operator与operator按 operation、start、end 三元组比较这使得Edits可以直接参与EXPECT_EQ断言与有序容器——这一点在测试代码中得到大量利用。2.2 入口函数GetTokenDiffstemplate typename TokenIter Edits GetTokenDiffs(TokenIter tokens1_begin, TokenIter tokens1_end, TokenIter tokens2_begin, TokenIter tokens2_end);声明见 editscript.h#L80-L82头文件中的示例直观展示了返回值语义对 tokens1 Hello world.与 tokens2 Goodbye world.结果是{Edit(Operation::DELETE, 0, 5), // token1[0,5) Hello Edit(Operation::INSERT, 0, 7), // token2[0,7) Goodbye Edit(Operation::EQUALS, 5, 12)} // token1[5, 12) world.性质保证是“每一个 token 恰好属于一个 edit”即编辑序列是对两个序列的无重叠、全覆盖切分。模板参数TokenIter要求随机访问迭代器实现中需要begin offset、std::mismatch、std::search等操作因此它对char数组、std::vectorstd::string、std::vectorstd::string_view等各种 token 类型通用——这正是 README 所说“template-generic”的含义。三、算法内部实现剥离前后缀 → 快速路径 → Myers 二分实现全部集中在diff_impl命名空间的Diff模板类中editscript.h#L179-L479公开入口GetTokenDiffs只是薄薄一层封装L482-L490它调用Generate(0, n1, 0, n2, edits)并利用具名返回值优化NRVO避免拷贝。3.1 Generate先剥掉公共前缀与后缀GenerateL207-L255是递归驱动函数策略在源码注释中写得很清楚快速判定相等若两段长度相同且内容全等直接AppendEdit(EQUALS, ...)返回剥离公共前缀CommonAffixL160-L173用std::mismatch求公共前缀长度剥离公共后缀通过std::reverse_iterator反转迭代器复用同一个CommonAffix求公共后缀长度对中间块递归调用Compute最后把前缀EQUALS通过InsertEditAt插回、后缀EQUALS通过AppendEdit追加。AppendEdit与InsertEditAtL97-L138还实现了编辑合并如果新 edit 与前一个或后一个edit 操作相同且区间首尾相接就把它并入相邻 edit保证输出序列里不出现可合并的碎片这也是输出“最小编辑操作数”观感上紧凑的原因。3.2 Compute四条廉价快速路径ComputeL266-L327在动用昂贵的通用算法前先尝试多个 O(n) 级的捷径一边为空tokens1 空则整段INSERTtokens2 空则整段DELETE子串命中若 length1 length2用std::search在 tokens1 中查找 tokens2 整段——若命中说明 tokens2 是 tokens1 的真子串直接输出“DELETE 前段 EQUALS 命中段 DELETE 后段”对称地处理 length2 length1 的“插入包围”情形单 token 替换一侧长度为 1 且未命中相等时输出 DELETE INSERT。任何一条路径都不适用时进入Bisect。3.3 GetBisectSplitPointsMyers O(ND) 中间蛇GetBisectSplitPointsL339-L450是算法核心注释直接引用了文献依据“See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations”。它维护一个交错布局interleaved的w数组同时推进正向路径与反向路径delta length1 - length2总 token 数为奇数时由正向路径负责检测碰撞front (delta % 2 ! 0)每一轮 d 内正向循环沿 k1 对角线前进在相等 token 上贪婪“滑行”L381-L385 的 while 循环反向循环则从序列末尾镜像比较L418-L423一旦两条路径在图上重叠x1 x2即找到“middle snake”中间蛇返回分割点(x1, y1)若完全走不到重叠返回{-1, -1}表示两段毫无公共性。BisectL460-L474拿到分割点后有公共性就对左右两半分别递归Generate没有公共性则退化为最坏情形的DELETE 全部 INSERT 全部。这一“剥离前后缀 中间蛇二分”的组合正是 diff-match-patch 家族算法的标志性结构也让该实现既正确又对“差异集中、公共部分大”的 token 序列例如格式化前后几乎相同的源码高效。四、测试用例行为契约的可验证证据external_libs/editscript_test.cc 用 GoogleTest 覆盖了算法的主要行为契约每组断言都给出了期望的完整Edits序列是理解输出格式的最佳参照测试用例场景期望结果要点CheckEmptyIntVectorDiffResults两个空序列空EditsCheckEmptyNoCommonSubsequence串 vs 长句无公共 token{DELETE,0,5}, {INSERT,0,44}CheckCharArrayDiffResultsAndLongestCommonSubsequencechar数组按字符 diff最长公共子串为fox jumped L124-L129CheckNonConstStringVectorDiffResultsstd::vectorstd::string按词 diff验证非 const 迭代器路径CompleteDeletion/CompleteInsertion单边为空单条 DELETE 或单条 INSERTReplaceFromOneDifferentElement/ReplaceToOneDifferentElement单 token 替换DELETE INSERT 各一条CompleteReplacement完全无公共 token{DELETE,0,2}, {INSERT,0,3}StrictSubsequence/StrictSupersequence真子序列/真超序列DELETE 夹 EQUALS或 EQUALS 夹 INSERT以StrictSubsequenceL243-L258为例tokens1 {the,fox,jumped,over,the,dog,.}tokens2 {fox,jumped,over}期望输出{DELETE,0,1}, {EQUALS,1,4}, {DELETE,4,7}——恰好对应 3.2 节“tokens2 是 tokens1 真子串”那条快速路径。测试文件开头还自定义了Operation/Edit/Edits的operatorL33-L59使失败时的断言信息可读这一批打印函数后来被 verible/common/strings/diff_test.cc 直接复用其注释写明 “Print functions copied from external_libs/editscript_test.cc”。五、editscript 在 Verible 中的实际应用editscript 的价值最终体现在消费者代码里。整个仓库中依赖//external_libs:editscript的主要是 verible/common/strings/BUILD第 57、68 行两处即diff库与其测试。5.1 LineDiffs把 token diff 落到“行”这个粒度verible/common/strings/diff.h 定义了LineDiffs结构L35-L46它持有before_text/after_text两个string_view、按行切分后的before_lines/after_lines以及一份diff::Edits行编辑序列。构造函数实现verible/common/strings/diff.cc#L46-L52正是把 editscript 用在行向量上LineDiffs::LineDiffs(std::string_view before, std::string_view after) : before_text(before), after_text(after), before_lines(SplitLinesKeepLineTerminator(before_text)), after_lines(SplitLinesKeepLineTerminator(after_text)), edits(diff::GetTokenDiffs(before_lines.begin(), before_lines.end(), after_lines.begin(), after_lines.end())) {}在此之上diff.h 还提供了三个工具函数DiffEditsToAddedLineNumbersL53把diff::Edits翻译为“新增行号”的区间集合注意注释明确说明diff::Edits是 0-based 而返回值是 1-based 行号DiffEditsToPatchHunksL78-L79按common_context行数把整段编辑序列切分为补丁 hunk其注释还坦承“这个函数可以上游回馈给 editscript 库因为它与被 diff 的类型无关”L76-L77LineDiffsToUnifiedDiffL81-L84输出标准 unified diff 文本。PrintEdit的实现diff.cc#L63-L77也印证了前文说的索引约定INSERT 用after_lines定位DELETE/EQUALS 用before_lines定位行首标记分别为、-、空格。5.2 formatter用 diff 结果驱动增量重排editscript 最有实战味道的消费方是 Verible 格式化器。verible/verilog/formatting/formatter.cc#L169-L188 的ReformatVerilogIncrementally// Differences from the first formatting. const verible::LineDiffs formatting_diffs(original_text, formatted_text); // Added lines will be re-applied to incremental re-formatting. LineNumberSet formatted_lines( verible::DiffEditsToAddedLineNumbers(formatting_diffs.edits));逻辑是先对原文与首轮格式化结果做行级 diff由 editscript 产生的编辑序列提取出“被格式化处理过的行号集合”再据此对第二轮重排施加行范围约束避免无谓地重排整个文件。verible/common/formatting/verification.cc#L32 则用同样手段对比“格式化结果”与“再格式化结果”验证格式化是否收敛幂等。可以说editscript 是 Verible formatter 收敛性验证与增量格式化机制的底层组件。5.3 lint 修复补丁unified diff 的输出verible/common/analysis/violation-handler.cc 在输出 lint 诊断的修复建议与写补丁文件时第 40-42 行、100-101 行同样通过LineDiffsLineDiffsToUnifiedDiff生成 1 行上下文的 unified diff——这些 diff 文本最终呈现给用户或写入补丁流其底层编辑序列仍来自 editscript。5.4 一个易混淆点ReplacementEdit 不是 editscript 的 Editverible/common/analysis/lint-rule-status.h#L42-L43 的注释专门澄清了这一点ReplacementEdit与 editscript 的Edit不同它直接保存替换字符串因此不需要“after”文本即可使用。阅读 lint autofix 相关代码时注意区分这两个结构可避免对索引语义产生误解。六、小结如何在仓库中定位这套机制策略文档external_libs/README.md——Bazel 依赖优先不支持时才 vendored库本体external_libs/editscript.hheader-onlyApache 2.0派生自 diff-match-patch构建定义external_libs/BUILDbazel build //external_libs:editscript可编译bazel test //external_libs:editscript_test可验证依赖声明现状根目录 MODULE.bazelBzlmodeditscript 是少数不走模块解析、直接入树的库主要消费方verible/common/strings/diff.h / verible/common/strings/diff.ccLineDiffs、patch hunk、unified diff下游是 verible/verilog/formatting/formatter.cc增量重排、verible/common/formatting/verification.cc收敛验证与 verible/common/analysis/violation-handler.cc修复补丁输出。这套“文档说明准入策略 header-only 算法库 完整测试契约 清晰的下游消费链”的组合是 Verible 管理外部代码的一个小而完整的样本外部代码进入仓库的门槛、许可证归属、可见性范围//:__subpackages__和行为验证editscript_test在仓库内都有据可查。赞分享开发工具Lint静态分析硬件开发【免费下载链接】veribleVerible is a suite of SystemVerilog developer tools, including a parser, style-linter, formatter and language server项目地址https://gitcode.com/gh_mirrors/ve/verible点击查看免费下载相关推荐vLLM序列管理长序列处理与截断策略vLLM序列管理长序列处理与截断策略 引言大模型长序列处理的挑战 在大型语言模型LLM推理过程中长序列处理面临着内存限制与计算效率的双重挑战。随着输入人工智能大模型模型推理服务推理引擎本地部署终极指南如何通过sebastian/diff自定义差异计算策略终极指南如何通过sebastian/diff自定义差异计算策略 sebastian/diff是一个强大的PHP差异实现库专为需要高效计算和展示文本差异的开发开发工具测试Envoy 外部依赖管理策略详解声明、评审、维护与补丁治理Envoy 外部依赖管理策略详解声明、评审、维护与补丁治理 导读 Envoy 是一款云原生高性能边缘/中间/服务代理Cloud native high pe云原生服务网格网络微服务创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑