docs: explain standard EOF diff semantics
MoonBit Diff 是一个无第三方依赖的 MoonBit 文本差异库,提供 Unicode 安全的字符级编辑脚本、脚本压缩与统计、文本往返重建,以及支持自定义文件名、 上下文行数和多 hunk 的 Unified Diff 渲染。
A dependency-free, Unicode-safe text diff and Unified Diff library for MoonBit.
diff
Equal/Delete/Insert
compact
reconstruct_old/new
stats
apply
invert
unified_diff
unified_diff_with_context
unified_diff_standard
从 mooncakes.io 安装已发布版本:
moon add pei0331/moonbit_diff@0.5.0
在使用方的 moon.pkg 中导入:
moon.pkg
import { "pei0331/moonbit_diff" @diff, }
let edits = @diff.diff("Moon😀 diff", "Moon🌕 patch") let compacted = @diff.compact(edits) for edit in compacted { match edit { Equal(text) => println("Equal(\{text})") Delete(text) => println("Delete(\{text})") Insert(text) => println("Insert(\{text})") } }
输出:
Equal(Moon) Delete(😀) Insert(🌕) Equal( ) Delete(diff) Insert(patch)
diff 保留细粒度的一字符一操作语义,适合精确定位;compact 只合并相邻的同类 操作,不改变编辑脚本代表的新旧文本。
let edits = @diff.diff("A😀bc", "A😀🌕d") assert_eq!(@diff.reconstruct_old(edits), "A😀bc") assert_eq!(@diff.reconstruct_new(edits), "A😀🌕d") let summary = @diff.stats(edits) println("equal: \{summary.equal_count}") println("deleted: \{summary.delete_count}") println("inserted: \{summary.insert_count}")
stats 同时接受原始脚本和 compact 后的脚本,始终按 Unicode scalar value 计数,而不是按 UTF-16 code unit 计数。
apply 在生成结果的同时验证脚本中的 Equal 和 Delete 是否与指定源文本完全 一致,因此适合在脚本跨进程、持久化或延迟执行时防止误应用:
Equal
Delete
let old_text = "Moon😀 diff" let new_text = "Moon🌕 patch" let edits = @diff.diff(old_text, new_text) |> @diff.compact match @diff.apply(edits, old_text) { Ok(result) => println(result) Err(_) => println("source and edit script do not match") } let reverse_edits = @diff.invert(edits) assert_eq!(@diff.apply(reverse_edits, new_text), Ok(old_text))
apply 可能返回三类错误:
SourceExhausted(offset, expected)
SourceMismatch(offset, expected, actual)
SourceNotFullyConsumed(offset, remaining)
错误偏移量和其他字符级 API 一样,按 Unicode scalar value 计数。apply 同时支持 diff 产生的逐字符脚本与 compact 产生的合并脚本。
unified_diff 适合短文本和教学示例。它使用 old、new 作为文件标签,并在 一个 hunk 中展示全部行:
old
new
let patch = @diff.unified_diff( "one\ntwo\nthree", "one\n2\nthree", ) println(patch)
--- old +++ new @@ -1,3 +1,3 @@ one -two +2 three
较长文件建议使用 unified_diff_with_context:
let patch = @diff.unified_diff_with_context( old_source, new_source, "src/before.mbt", "src/after.mbt", 3, )
最后一个参数是每处修改前后的上下文行数。相距较远的修改会生成独立 hunk,重叠 的上下文窗口会自动合并。负数上下文按 0 处理。文件标签中的 CR/LF 会替换成 空格,避免破坏补丁头部结构。
0
两种 Unified Diff API 都会先将 CRLF、LF 和 CR 归一化为 LF;归一化后内容相同 时返回空字符串。
需要让输出与常见 diff -u 工具互操作时,使用 unified_diff_standard。它会区分 "a" 与 "a\n",并为未以换行符终止的行输出约定 marker:
diff -u
"a"
"a\n"
let patch = @diff.unified_diff_standard( "value=old", "value=new\n", "a/config.txt", "b/config.txt", 3, )
--- a/config.txt +++ b/config.txt @@ -1,1 +1,1 @@ -value=old \ No newline at end of file +value=new
unified_diff 和 unified_diff_with_context 保留早期版本的简化行为;新代码如需 准确表达文件末尾换行状态,建议优先使用 unified_diff_standard。
MoonBit 的 String.length() 返回 UTF-16 code unit 数量,直接按索引切片可能把 非 BMP 字符拆成无效代理片段。本库先通过 String::to_array() 转换为 Array[Char], 因此 😀、🌕、𐐷 等字符始终作为完整操作输出。
String.length()
String::to_array()
Array[Char]
😀
🌕
𐐷
这里的“字符”指 Unicode scalar value,不是扩展字素簇。由多个 scalar value 组成 的用户感知字符,例如带肤色修饰符的 emoji、国旗和 ZWJ 家庭 emoji,可能产生多个 编辑操作;可用 compact 合并相邻同类操作,但本库目前不执行字素簇分段。
pub(all) enum EditOp { Equal(String) Delete(String) Insert(String) } pub(all) enum ApplyError { SourceExhausted(Int, String) SourceMismatch(Int, String, String) SourceNotFullyConsumed(Int, String) } pub(all) struct DiffStats { equal_count : Int delete_count : Int insert_count : Int } pub fn diff(String, String) -> Array[EditOp] pub fn compact(Array[EditOp]) -> Array[EditOp] pub fn reconstruct_old(Array[EditOp]) -> String pub fn reconstruct_new(Array[EditOp]) -> String pub fn stats(Array[EditOp]) -> DiffStats pub fn apply(Array[EditOp], String) -> Result[String, ApplyError] pub fn invert(Array[EditOp]) -> Array[EditOp] pub fn unified_diff(String, String) -> String pub fn unified_diff_with_context( String, String, String, String, Int, ) -> String pub fn unified_diff_standard(String, String, String, String, Int) -> String
完整导出接口也可通过以下命令生成和查看:
moon info
仓库中的 examples/basic 覆盖差异计算、压缩、统计、文本重建和上下文 Diff:
examples/basic
moon run examples/basic
N+M
D
O((N+M)*D)
N*M
算法设计、路径回溯和 hunk 聚合过程详见 docs/ALGORITHM.md。
安装 MoonBit 工具链后运行:
moon fmt --check moon check --deny-warn moon build --target wasm-gc moon test --target wasm-gc moon run examples/basic
CI 还会在 wasm-gc、js 和 native 后端执行构建与测试。贡献流程参见 CONTRIBUTING.md,版本变化参见 CHANGELOG.md。
wasm-gc
js
native
pei0331/moonbit_diff
Apache-2.0,详见 LICENSE。
Myers diff algorithm library for MoonBit ecosystem
版权所有:中国计算机学会技术支持:开源发展技术委员会 京ICP备13000930号-9 京公网安备 11010802047560号
MoonBit Diff
MoonBit Diff 是一个无第三方依赖的 MoonBit 文本差异库,提供 Unicode 安全的字符级编辑脚本、脚本压缩与统计、文本往返重建,以及支持自定义文件名、 上下文行数和多 hunk 的 Unified Diff 渲染。
功能概览
diffEqual/Delete/Insert编辑脚本compactreconstruct_old/newstatsapplyinvertunified_diffunified_diff_with_contextunified_diff_standard安装
从 mooncakes.io 安装已发布版本:
在使用方的
moon.pkg中导入:快速开始
输出:
diff保留细粒度的一字符一操作语义,适合精确定位;compact只合并相邻的同类 操作,不改变编辑脚本代表的新旧文本。文本重建与统计
stats同时接受原始脚本和compact后的脚本,始终按 Unicode scalar value 计数,而不是按 UTF-16 code unit 计数。应用与反转编辑脚本
apply在生成结果的同时验证脚本中的Equal和Delete是否与指定源文本完全 一致,因此适合在脚本跨进程、持久化或延迟执行时防止误应用:apply可能返回三类错误:SourceExhausted(offset, expected)SourceMismatch(offset, expected, actual)SourceNotFullyConsumed(offset, remaining)错误偏移量和其他字符级 API 一样,按 Unicode scalar value 计数。
apply同时支持diff产生的逐字符脚本与compact产生的合并脚本。Unified Diff
简单全量输出
unified_diff适合短文本和教学示例。它使用old、new作为文件标签,并在 一个 hunk 中展示全部行:自定义上下文与多 hunk
较长文件建议使用
unified_diff_with_context:最后一个参数是每处修改前后的上下文行数。相距较远的修改会生成独立 hunk,重叠 的上下文窗口会自动合并。负数上下文按
0处理。文件标签中的 CR/LF 会替换成 空格,避免破坏补丁头部结构。两种 Unified Diff API 都会先将 CRLF、LF 和 CR 归一化为 LF;归一化后内容相同 时返回空字符串。
标准 EOF 换行语义
需要让输出与常见
diff -u工具互操作时,使用unified_diff_standard。它会区分"a"与"a\n",并为未以换行符终止的行输出约定 marker:unified_diff和unified_diff_with_context保留早期版本的简化行为;新代码如需 准确表达文件末尾换行状态,建议优先使用unified_diff_standard。Unicode 语义
MoonBit 的
String.length()返回 UTF-16 code unit 数量,直接按索引切片可能把 非 BMP 字符拆成无效代理片段。本库先通过String::to_array()转换为Array[Char], 因此😀、🌕、𐐷等字符始终作为完整操作输出。这里的“字符”指 Unicode scalar value,不是扩展字素簇。由多个 scalar value 组成 的用户感知字符,例如带肤色修饰符的 emoji、国旗和 ZWJ 家庭 emoji,可能产生多个 编辑操作;可用
compact合并相邻同类操作,但本库目前不执行字素簇分段。API 参考
完整导出接口也可通过以下命令生成和查看:
可运行示例
仓库中的
examples/basic覆盖差异计算、压缩、统计、文本重建和上下文 Diff:算法与复杂度
N+M,最短编辑距离为D,时间复杂度为O((N+M)*D),路径回溯空间复杂度为O((N+M)*D)。D较小,不再分配完整的N*MLCS 矩阵。D接近N+M,时间和 trace 空间仍可能达到二次规模。算法设计、路径回溯和 hunk 聚合过程详见 docs/ALGORITHM.md。
开发与验证
安装 MoonBit 工具链后运行:
CI 还会在
wasm-gc、js和native后端执行构建与测试。贡献流程参见 CONTRIBUTING.md,版本变化参见 CHANGELOG.md。仓库与发布
pei0331/moonbit_diffLicense
Apache-2.0,详见 LICENSE。