From 9c27090a3baa7ca38b0a272552a7a7708a677f95 Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Mon, 1 Jul 2024 20:24:48 -0400 Subject: [PATCH 01/60] Add build.zig.zon, update build.zig, .gitignore This adds a test running step and a zig.zon manifest. Also adds to the .gitignore the new location of caches, .zig-out. --- .gitignore | 3 ++- build.zig | 26 ++++++++++++++++++++++++++ build.zig.zon | 11 +++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 build.zig.zon diff --git a/.gitignore b/.gitignore index 2040c29..68557b5 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -zig-cache +zig-* +.zig-* diff --git a/build.zig b/build.zig index 30802da..d144529 100644 --- a/build.zig +++ b/build.zig @@ -1,7 +1,33 @@ const std = @import("std"); pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + _ = b.addModule("diffz", .{ .root_source_file = b.path("DiffMatchPatch.zig"), }); + + const lib = b.addStaticLibrary(.{ + .name = "diffz", + .root_source_file = b.path("DiffMatchPatch.zig"), + .target = target, + .optimize = optimize, + }); + + // This declares intent for the library to be installed into the standard + // location when the user invokes the "install" step (the default step when + // running `zig build`). + b.installArtifact(lib); + + // Run tests + const tests = b.addTest(.{ + .name = "tests", + .root_source_file = b.path("DiffMatchPatch.zig"), + .target = target, + .optimize = optimize, + }); + const step_tests = b.addRunArtifact(tests); + + b.step("test", "Run diffz tests").dependOn(&step_tests.step); } diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 0000000..747b11a --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,11 @@ +.{ + .name = "DiffMatchPatch", + .version = "0.0.1", + .paths = .{ + "DiffMatchPatch.zig", + "LICENSE", + "README.md", + "build.zig.zon", + "build.zig", + }, +} From 4792861d6d423f6a8fead9db6c7f0b0024b22136 Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Mon, 1 Jul 2024 20:30:34 -0400 Subject: [PATCH 02/60] Module name is diffz, not DiffMatchPatch --- build.zig.zon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.zig.zon b/build.zig.zon index 747b11a..48ea526 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -1,5 +1,5 @@ .{ - .name = "DiffMatchPatch", + .name = "diffz", .version = "0.0.1", .paths = .{ "DiffMatchPatch.zig", From 10bcba04b69861e6badffed87bc09462369c1ee5 Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Mon, 1 Jul 2024 20:49:26 -0400 Subject: [PATCH 03/60] More paths, .target, .optimize --- build.zig | 2 ++ build.zig.zon | 2 ++ 2 files changed, 4 insertions(+) diff --git a/build.zig b/build.zig index d144529..dd40eb6 100644 --- a/build.zig +++ b/build.zig @@ -6,6 +6,8 @@ pub fn build(b: *std.Build) void { _ = b.addModule("diffz", .{ .root_source_file = b.path("DiffMatchPatch.zig"), + .target = target, + .optimize = optimize, }); const lib = b.addStaticLibrary(.{ diff --git a/build.zig.zon b/build.zig.zon index 48ea526..b4e0b4d 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -3,6 +3,8 @@ .version = "0.0.1", .paths = .{ "DiffMatchPatch.zig", + ".gitattributes", + ".gitignore", "LICENSE", "README.md", "build.zig.zon", From 64bc1bf730a8be35b53d30ce593042b59432cb98 Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Tue, 2 Jul 2024 11:39:35 -0400 Subject: [PATCH 04/60] Memory-managed halfMatch function halfMatch now correctly manages its own memory, like it's supposed to. --- DiffMatchPatch.zig | 134 +++++++++++++++++++++++++++------------------ 1 file changed, 82 insertions(+), 52 deletions(-) diff --git a/DiffMatchPatch.zig b/DiffMatchPatch.zig index 3540518..c18b2e0 100644 --- a/DiffMatchPatch.zig +++ b/DiffMatchPatch.zig @@ -2,6 +2,7 @@ const DiffMatchPatch = @This(); const std = @import("std"); const testing = std.testing; +const Allocator = std.mem.Allocator; const ArrayListUnmanaged = std.ArrayListUnmanaged; const DiffList = ArrayListUnmanaged(Diff); @@ -255,6 +256,16 @@ const HalfMatchResult = struct { prefix_after: []const u8, suffix_after: []const u8, common_middle: []const u8, + + // TODO maybe check for empty slice here for fewer copies, + // as in, maybe we can transfer ownership and replace with "". + pub fn deinit(hmr: HalfMatchResult, alloc: Allocator) void { + alloc.free(hmr.prefix_before); + alloc.free(hmr.suffix_before); + alloc.free(hmr.prefix_after); + alloc.free(hmr.suffix_after); + alloc.free(hmr.common_middle); + } }; /// Do the two texts share a Substring which is at least half the length of @@ -296,16 +307,22 @@ fn diffHalfMatch( half_match = half_match_2.?; } else { // Both matched. Select the longest. - half_match = if (half_match_1.?.common_middle.len > half_match_2.?.common_middle.len) - half_match_1 - else - half_match_2; + half_match = half: { + if (half_match_1.?.common_middle.len > half_match_2.?.common_middle.len) { + half_match_2.?.deinit(allocator); + break :half half_match_1; + } else { + half_match_1.?.deinit(allocator); + break :half half_match_2; + } + }; } // A half-match was found, sort out the return data. if (before.len > after.len) { - return half_match; + return half_match.?; } else { + // Transfers ownership of all memory to new, permuted, half_match. const half_match_yes = half_match.?; return .{ .prefix_before = half_match_yes.prefix_after, @@ -337,6 +354,7 @@ fn diffHalfMatchInternal( var j: isize = -1; var best_common = std.ArrayListUnmanaged(u8){}; + defer best_common.deinit(allocator); var best_long_text_a: []const u8 = ""; var best_long_text_b: []const u8 = ""; var best_short_text_a: []const u8 = ""; @@ -350,8 +368,10 @@ fn diffHalfMatchInternal( const suffix_length = diffCommonSuffix(long_text[0..i], short_text[0..@as(usize, @intCast(j))]); if (best_common.items.len < suffix_length + prefix_length) { best_common.items.len = 0; - try best_common.appendSlice(allocator, short_text[@as(usize, @intCast(j - @as(isize, @intCast(suffix_length)))) .. @as(usize, @intCast(j - @as(isize, @intCast(suffix_length)))) + suffix_length]); - try best_common.appendSlice(allocator, short_text[@as(usize, @intCast(j)) .. @as(usize, @intCast(j)) + prefix_length]); + const a = short_text[@as(usize, @intCast(j - @as(isize, @intCast(suffix_length)))) .. @as(usize, @intCast(j - @as(isize, @intCast(suffix_length)))) + suffix_length]; + try best_common.appendSlice(allocator, a); + const b = short_text[@as(usize, @intCast(j)) .. @as(usize, @intCast(j)) + prefix_length]; + try best_common.appendSlice(allocator, b); best_long_text_a = long_text[0 .. i - suffix_length]; best_long_text_b = long_text[i + prefix_length ..]; @@ -361,11 +381,11 @@ fn diffHalfMatchInternal( } if (best_common.items.len * 2 >= long_text.len) { return .{ - .prefix_before = best_long_text_a, - .suffix_before = best_long_text_b, - .prefix_after = best_short_text_a, - .suffix_after = best_short_text_b, - .common_middle = best_common.items, + .prefix_before = try allocator.dupe(u8, best_long_text_a), + .suffix_before = try allocator.dupe(u8, best_long_text_b), + .prefix_after = try allocator.dupe(u8, best_short_text_a), + .suffix_after = try allocator.dupe(u8, best_short_text_b), + .common_middle = try best_common.toOwnedSlice(allocator), }; } else { return null; @@ -1411,97 +1431,107 @@ test diffCommonOverlap { } test diffHalfMatch { - var arena = std.heap.ArenaAllocator.init(testing.allocator); - defer arena.deinit(); + const allocator = testing.allocator; var one_timeout = DiffMatchPatch{}; one_timeout.diff_timeout = 1; - + const dh1 = try one_timeout.diffHalfMatch(allocator, "1234567890", "abcdef"); try testing.expectEqual( @as(?HalfMatchResult, null), - try one_timeout.diffHalfMatch(arena.allocator(), "1234567890", "abcdef"), + dh1, ); // No match #1 + const dh2 = try one_timeout.diffHalfMatch(allocator, "12345", "23"); try testing.expectEqual( @as(?HalfMatchResult, null), - try one_timeout.diffHalfMatch(arena.allocator(), "12345", "23"), + dh2, ); // No match #2 // Single matches - try testing.expectEqualDeep(@as(?HalfMatchResult, HalfMatchResult{ + var dh3 = (try one_timeout.diffHalfMatch(allocator, "1234567890", "a345678z")).?; + defer dh3.deinit(allocator); + try testing.expectEqualDeep(HalfMatchResult{ .prefix_before = "12", .suffix_before = "90", .prefix_after = "a", .suffix_after = "z", .common_middle = "345678", - }), try one_timeout.diffHalfMatch(arena.allocator(), "1234567890", "a345678z")); // Single Match #1 + }, dh3); // Single Match #1 - try testing.expectEqualDeep(@as(?HalfMatchResult, HalfMatchResult{ + var dh4 = (try one_timeout.diffHalfMatch(allocator, "a345678z", "1234567890")).?; + defer dh4.deinit(allocator); + try testing.expectEqualDeep(HalfMatchResult{ .prefix_before = "a", .suffix_before = "z", .prefix_after = "12", .suffix_after = "90", .common_middle = "345678", - }), try one_timeout.diffHalfMatch(arena.allocator(), "a345678z", "1234567890")); // Single Match #2 + }, dh4); // Single Match #2 - try testing.expectEqualDeep(@as(?HalfMatchResult, HalfMatchResult{ + var dh5 = (try one_timeout.diffHalfMatch(allocator, "abc56789z", "1234567890")).?; + defer dh5.deinit(allocator); + try testing.expectEqualDeep(HalfMatchResult{ .prefix_before = "abc", .suffix_before = "z", .prefix_after = "1234", .suffix_after = "0", .common_middle = "56789", - }), try one_timeout.diffHalfMatch(arena.allocator(), "abc56789z", "1234567890")); // Single Match #3 + }, dh5); // Single Match #3 - try testing.expectEqualDeep(@as(?HalfMatchResult, HalfMatchResult{ + var dh6 = (try one_timeout.diffHalfMatch(allocator, "a23456xyz", "1234567890")).?; + defer dh6.deinit(allocator); + try testing.expectEqualDeep(HalfMatchResult{ .prefix_before = "a", .suffix_before = "xyz", .prefix_after = "1", .suffix_after = "7890", .common_middle = "23456", - }), try one_timeout.diffHalfMatch(arena.allocator(), "a23456xyz", "1234567890")); // Single Match #4 + }, dh6); // Single Match #4 // Multiple matches - try testing.expectEqualDeep( - @as(?HalfMatchResult, HalfMatchResult{ - .prefix_before = "12123", - .suffix_before = "123121", - .prefix_after = "a", - .suffix_after = "z", - .common_middle = "1234123451234", - }), - try one_timeout.diffHalfMatch(arena.allocator(), "121231234123451234123121", "a1234123451234z"), - ); // Multiple Matches #1 - - try testing.expectEqualDeep( - @as(?HalfMatchResult, HalfMatchResult{ - .prefix_before = "", - .suffix_before = "-=-=-=-=-=", - .prefix_after = "x", - .suffix_after = "", - .common_middle = "x-=-=-=-=-=-=-=", - }), - try one_timeout.diffHalfMatch(arena.allocator(), "x-=-=-=-=-=-=-=-=-=-=-=-=", "xx-=-=-=-=-=-=-="), - ); // Multiple Matches #2 - - try testing.expectEqualDeep(@as(?HalfMatchResult, HalfMatchResult{ + var dh7 = (try one_timeout.diffHalfMatch(allocator, "121231234123451234123121", "a1234123451234z")).?; + defer dh7.deinit(allocator); + try testing.expectEqualDeep(HalfMatchResult{ + .prefix_before = "12123", + .suffix_before = "123121", + .prefix_after = "a", + .suffix_after = "z", + .common_middle = "1234123451234", + }, dh7); // Multiple Matches #1 + + var dh8 = (try one_timeout.diffHalfMatch(allocator, "x-=-=-=-=-=-=-=-=-=-=-=-=", "xx-=-=-=-=-=-=-=")).?; + defer dh8.deinit(allocator); + try testing.expectEqualDeep(HalfMatchResult{ + .prefix_before = "", + .suffix_before = "-=-=-=-=-=", + .prefix_after = "x", + .suffix_after = "", + .common_middle = "x-=-=-=-=-=-=-=", + }, dh8); // Multiple Matches #2 + + var dh9 = (try one_timeout.diffHalfMatch(allocator, "-=-=-=-=-=-=-=-=-=-=-=-=y", "-=-=-=-=-=-=-=yy")).?; + defer dh9.deinit(allocator); + try testing.expectEqualDeep(HalfMatchResult{ .prefix_before = "-=-=-=-=-=", .suffix_before = "", .prefix_after = "", .suffix_after = "y", .common_middle = "-=-=-=-=-=-=-=y", - }), try one_timeout.diffHalfMatch(arena.allocator(), "-=-=-=-=-=-=-=-=-=-=-=-=y", "-=-=-=-=-=-=-=yy")); // Multiple Matches #3 + }, dh9); // Multiple Matches #3 // Other cases // Optimal diff would be -q+x=H-i+e=lloHe+Hu=llo-Hew+y not -qHillo+x=HelloHe-w+Hulloy - try testing.expectEqualDeep(@as(?HalfMatchResult, HalfMatchResult{ + var dh10 = (try one_timeout.diffHalfMatch(allocator, "qHilloHelloHew", "xHelloHeHulloy")).?; + defer dh10.deinit(allocator); + try testing.expectEqualDeep(HalfMatchResult{ .prefix_before = "qHillo", .suffix_before = "w", .prefix_after = "x", .suffix_after = "Hulloy", .common_middle = "HelloHe", - }), try one_timeout.diffHalfMatch(arena.allocator(), "qHilloHelloHew", "xHelloHeHulloy")); // Non-optimal halfmatch + }, dh10); // Non-optimal halfmatch one_timeout.diff_timeout = 0; - try testing.expectEqualDeep(@as(?HalfMatchResult, null), try one_timeout.diffHalfMatch(arena.allocator(), "qHilloHelloHew", "xHelloHeHulloy")); // Non-optimal halfmatch + try testing.expectEqualDeep(@as(?HalfMatchResult, null), try one_timeout.diffHalfMatch(allocator, "qHilloHelloHew", "xHelloHeHulloy")); // Non-optimal halfmatch } test diffLinesToChars { From 0c7c4e29dd840f810a311384442a5602c06ca7a2 Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Tue, 2 Jul 2024 12:14:18 -0400 Subject: [PATCH 05/60] Managed memory in diffLinesToChars --- DiffMatchPatch.zig | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/DiffMatchPatch.zig b/DiffMatchPatch.zig index c18b2e0..4131a4e 100644 --- a/DiffMatchPatch.zig +++ b/DiffMatchPatch.zig @@ -413,8 +413,10 @@ fn diffBisect( const v_length = 2 * max_d; var v1 = try ArrayListUnmanaged(isize).initCapacity(allocator, @as(usize, @intCast(v_length))); + defer v1.deinit(allocator); v1.items.len = @intCast(v_length); var v2 = try ArrayListUnmanaged(isize).initCapacity(allocator, @as(usize, @intCast(v_length))); + defer v2.deinit(allocator); v2.items.len = @intCast(v_length); var x: usize = 0; @@ -650,6 +652,12 @@ const LinesToCharsResult = struct { chars_1: []const u8, chars_2: []const u8, line_array: ArrayListUnmanaged([]const u8), + + pub fn deinit(self: *LinesToCharsResult, allocator: Allocator) void { + allocator.free(self.chars_1); + allocator.free(self.chars_2); + self.line_array.deinit(allocator); + } }; /// Split two texts into a list of strings. Reduce the texts to a string of @@ -665,12 +673,15 @@ fn diffLinesToChars( text2: []const u8, ) DiffError!LinesToCharsResult { var line_array = ArrayListUnmanaged([]const u8){}; + errdefer line_array.deinit(allocator); var line_hash = std.StringHashMapUnmanaged(usize){}; + defer line_hash.deinit(allocator); // e.g. line_array[4] == "Hello\n" // e.g. line_hash.get("Hello\n") == 4 // "\x00" is a valid character, but various debuggers don't like it. // So we'll insert a junk entry to avoid generating a null character. + // XXX why is this necessary? -Sam try line_array.append(allocator, ""); // Allocate 2/3rds of the space for text1, the rest for text2. @@ -697,9 +708,9 @@ fn diffLinesToCharsMunge( var line_end: isize = -1; var line: []const u8 = ""; var chars = ArrayListUnmanaged(u8){}; + defer chars.deinit(allocator); // Walk the text, pulling out a Substring for each line. - // text.split('\n') would would temporarily double our memory footprint. - // Modifying text would create many large strings to garbage collect. + // TODO this can be handled with a Reader, avoiding all the manual splitting while (line_end < @as(isize, @intCast(text.len)) - 1) { line_end = b: { break :b @as(isize, @intCast(std.mem.indexOf(u8, text[@intCast(line_start)..], "\n") orelse @@ -1535,16 +1546,15 @@ test diffHalfMatch { } test diffLinesToChars { - var arena = std.heap.ArenaAllocator.init(testing.allocator); - defer arena.deinit(); - + const allocator = std.testing.allocator; // Convert lines down to characters. - var tmp_array_list = std.ArrayList([]const u8).init(arena.allocator()); + var tmp_array_list = std.ArrayList([]const u8).init(allocator); + defer tmp_array_list.deinit(); try tmp_array_list.append(""); try tmp_array_list.append("alpha\n"); try tmp_array_list.append("beta\n"); - var result = try diffLinesToChars(arena.allocator(), "alpha\nbeta\nalpha\n", "beta\nalpha\nbeta\n"); + var result = try diffLinesToChars(allocator, "alpha\nbeta\nalpha\n", "beta\nalpha\nbeta\n"); try testing.expectEqualStrings("\u{0001}\u{0002}\u{0001}", result.chars_1); // Shared lines #1 try testing.expectEqualStrings("\u{0002}\u{0001}\u{0002}", result.chars_2); // Shared lines #2 try testing.expectEqualDeep(tmp_array_list.items, result.line_array.items); // Shared lines #3 @@ -1554,8 +1564,9 @@ test diffLinesToChars { try tmp_array_list.append("alpha\r\n"); try tmp_array_list.append("beta\r\n"); try tmp_array_list.append("\r\n"); + result.deinit(allocator); - result = try diffLinesToChars(arena.allocator(), "", "alpha\r\nbeta\r\n\r\n\r\n"); + result = try diffLinesToChars(allocator, "", "alpha\r\nbeta\r\n\r\n\r\n"); try testing.expectEqualStrings("", result.chars_1); // Empty string and blank lines #1 try testing.expectEqualStrings("\u{0001}\u{0002}\u{0003}\u{0003}", result.chars_2); // Empty string and blank lines #2 try testing.expectEqualDeep(tmp_array_list.items, result.line_array.items); // Empty string and blank lines #3 @@ -1565,11 +1576,13 @@ test diffLinesToChars { try tmp_array_list.append("a"); try tmp_array_list.append("b"); - result = try diffLinesToChars(arena.allocator(), "a", "b"); + result.deinit(allocator); + result = try diffLinesToChars(allocator, "a", "b"); try testing.expectEqualStrings("\u{0001}", result.chars_1); // No linebreaks #1. try testing.expectEqualStrings("\u{0002}", result.chars_2); // No linebreaks #2. try testing.expectEqualDeep(tmp_array_list.items, result.line_array.items); // No linebreaks #3. + result.deinit(allocator); // TODO: More than 256 to reveal any 8-bit limitations but this requires // some unicode logic that I don't want to deal with @@ -1578,8 +1591,8 @@ test diffLinesToChars { // const n: u8 = 255; // tmp_array_list.items.len = 0; - // var line_list = std.ArrayList(u8).init(arena.allocator()); - // var char_list = std.ArrayList(u8).init(arena.allocator()); + // var line_list = std.ArrayList(u8).init(alloc); + // var char_list = std.ArrayList(u8).init(alloc); // var i: u8 = 0; // while (i < n) : (i += 1) { @@ -1590,7 +1603,7 @@ test diffLinesToChars { // try testing.expectEqual(@as(usize, n), tmp_array_list.items.len); // Test initialization fail #1 // try testing.expectEqual(@as(usize, n), char_list.items.len); // Test initialization fail #2 // try tmp_array_list.insert(0, ""); - // result = try diffLinesToChars(arena.allocator(), line_list.items, ""); + // result = try diffLinesToChars(alloc, line_list.items, ""); // try testing.expectEqualStrings(char_list.items, result.chars_1); // try testing.expectEqualStrings("", result.chars_2); // try testing.expectEqualDeep(tmp_array_list.items, result.line_array.items); From 9e638e8e90885e20597baa4c8c1fa383c60b41a7 Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Tue, 2 Jul 2024 15:14:34 -0400 Subject: [PATCH 06/60] Clean up easy leaks + prep for allocating Diffs The tests currently create Diffs with constant/static text, which causes a panic on any attempt to free said text. This pass fixes a couple leaks, and lays a foundation for consistently freeing any diff's .text field when no longer in use: this requires that, as it will be in real programs, the diff text is allocated to begin with. --- DiffMatchPatch.zig | 98 +++++++++++++++++++++++----------------------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/DiffMatchPatch.zig b/DiffMatchPatch.zig index 4131a4e..6458df4 100644 --- a/DiffMatchPatch.zig +++ b/DiffMatchPatch.zig @@ -6,6 +6,28 @@ const Allocator = std.mem.Allocator; const ArrayListUnmanaged = std.ArrayListUnmanaged; const DiffList = ArrayListUnmanaged(Diff); +fn deinitDiffList(allocator: Allocator, diffs: *DiffList) void { + defer diffs.deinit(allocator); + for (diffs.items) |d| { + if (d.text.len > 0) { + allocator.free(d.text); + } + } +} + +fn freeRangeDiffList( + allocator: Allocator, + diffs: *DiffList, + start: usize, + len: usize, +) void { + const after_range = start + len; + const range = diffs.items[start..after_range]; + for (range) |d| { + allocator.free(d.text); + } +} + /// DMP with default configuration options pub const default = DiffMatchPatch{}; @@ -207,15 +229,15 @@ fn diffCompute( if (short_text.len == 1) { // Single character string. // After the previous speedup, the character can't be an equality. - try diffs.append(allocator, Diff.init(.delete, before)); - try diffs.append(allocator, Diff.init(.insert, after)); + try diffs.append(allocator, Diff.init(.delete, try allocator.dupe(u8, before))); + try diffs.append(allocator, Diff.init(.insert, try allocator.dupe(u8, after))); return diffs; } // Check to see if the problem can be split in two. if (try dmp.diffHalfMatch(allocator, before, after)) |half_match| { // A half-match was found, sort out the return data. - + defer half_match.deinit(allocator); // Send both pairs off for separate processing. const diffs_a = try dmp.diffInternal( allocator, @@ -238,7 +260,7 @@ fn diffCompute( // Merge the results. diffs = diffs_a; - try diffs.append(allocator, Diff.init(.equal, half_match.common_middle)); + try diffs.append(allocator, Diff.init(.equal, try allocator.dupe(u8, half_match.common_middle))); try diffs.appendSlice(allocator, diffs_b.items); return diffs; } @@ -579,7 +601,8 @@ fn diffLineMode( deadline: u64, ) DiffError!DiffList { // Scan the text on a line-by-line basis first. - const a = try diffLinesToChars(allocator, text1_in, text2_in); + var a = try diffLinesToChars(allocator, text1_in, text2_in); + defer a.deinit(allocator); const text1 = a.chars_1; const text2 = a.chars_2; const line_array = a.line_array; @@ -796,22 +819,14 @@ fn diffCleanupMerge(allocator: std.mem.Allocator, diffs: *DiffList) DiffError!vo if ((pointer - count_delete - count_insert) > 0 and diffs.items[pointer - count_delete - count_insert - 1].operation == .equal) { - // diffs.items[pointer - count_delete - count_insert - 1].text - // += text_insert.Substring(0, common_length); - const ii = pointer - count_delete - count_insert - 1; var nt = try allocator.alloc(u8, diffs.items[ii].text.len + common_length); - // try diffs.items[pointer - count_delete - count_insert - 1].text.append(allocator, text_insert.items[0..common_length]); const ot = diffs.items[ii].text; @memcpy(nt[0..ot.len], ot); @memcpy(nt[ot.len..], text_insert.items[0..common_length]); - - // allocator.free(diffs.items[ii].text); diffs.items[ii].text = nt; } else { - // diffs.Insert(0, Diff.init(.equal, - // text_insert.Substring(0, common_length))); const text = std.ArrayListUnmanaged(u8){ .items = try allocator.dupe(u8, text_insert.items[0..common_length]), }; @@ -853,19 +868,11 @@ fn diffCleanupMerge(allocator: std.mem.Allocator, diffs: *DiffList) DiffError!vo } else if (pointer != 0 and diffs.items[pointer - 1].operation == .equal) { // Merge this equality with the previous one. // TODO: Fix using realloc or smth - var nt = try allocator.alloc(u8, diffs.items[pointer - 1].text.len + diffs.items[pointer].text.len); - - // try diffs.items[pointer - count_delete - count_insert - 1].text.append(allocator, text_insert.items[0..common_length]); const ot = diffs.items[pointer - 1].text; @memcpy(nt[0..ot.len], ot); @memcpy(nt[ot.len..], diffs.items[pointer].text); - - // allocator.free(diffs.items[pointer - 1].text); diffs.items[pointer - 1].text = nt; - // allocator.free(diffs.items[pointer].text); - - // try diffs.items[pointer - 1].text.append(allocator, diffs.items[pointer].text.items); _ = diffs.orderedRemove(pointer); } else { pointer += 1; @@ -893,12 +900,6 @@ fn diffCleanupMerge(allocator: std.mem.Allocator, diffs: *DiffList) DiffError!vo { // This is a single edit surrounded by equalities. if (std.mem.endsWith(u8, diffs.items[pointer].text, diffs.items[pointer - 1].text)) { - // Shift the edit over the previous equality. - // diffs.items[pointer].text = diffs.items[pointer - 1].text + - // diffs.items[pointer].text[0 .. diffs.items[pointer].text.len - - // diffs.items[pointer - 1].text.len]; - // diffs.items[pointer + 1].text = diffs.items[pointer - 1].text + diffs.items[pointer + 1].text; - const pt = try std.mem.concat(allocator, u8, &.{ diffs.items[pointer - 1].text, diffs.items[pointer].text[0 .. diffs.items[pointer].text.len - @@ -908,21 +909,12 @@ fn diffCleanupMerge(allocator: std.mem.Allocator, diffs: *DiffList) DiffError!vo diffs.items[pointer - 1].text, diffs.items[pointer + 1].text, }); - - // allocator.free(diffs.items[pointer].text); - // allocator.free(diffs.items[pointer + 1].text); - diffs.items[pointer].text = pt; diffs.items[pointer + 1].text = p1t; - + // XXX reactivate freeRangeDiffList(allocator, diffs, pointer - 1, 1); try diffs.replaceRange(allocator, pointer - 1, 1, &.{}); changes = true; } else if (std.mem.startsWith(u8, diffs.items[pointer].text, diffs.items[pointer + 1].text)) { - // Shift the edit over the next equality. - // diffs.items[pointer - 1].text += diffs.items[pointer + 1].text; - // diffs.items[pointer].text = - // diffs.items[pointer].text[diffs.items[pointer + 1].text.len..] + diffs.items[pointer + 1].text; - const pm1t = try std.mem.concat(allocator, u8, &.{ diffs.items[pointer - 1].text, diffs.items[pointer + 1].text, @@ -931,13 +923,9 @@ fn diffCleanupMerge(allocator: std.mem.Allocator, diffs: *DiffList) DiffError!vo diffs.items[pointer].text[diffs.items[pointer + 1].text.len..], diffs.items[pointer + 1].text, }); - - // allocator.free(diffs.items[pointer - 1].text); - // allocator.free(diffs.items[pointer].text); - diffs.items[pointer - 1].text = pm1t; diffs.items[pointer].text = pt; - + // XXX reactivate freeRangeDiffList(allocator, diffs, pointer - 1, 1); try diffs.replaceRange(allocator, pointer + 1, 1, &.{}); changes = true; } @@ -1043,8 +1031,10 @@ fn diffCleanupSemantic(allocator: std.mem.Allocator, diffs: *DiffList) DiffError @intCast(pointer), Diff.init(.equal, try allocator.dupe(u8, insertion[0..overlap_length1])), ); + // XXX activate: allocator.free(diffs.items[@inteCast(pointer-1)].text); diffs.items[@intCast(pointer - 1)].text = try allocator.dupe(u8, deletion[0 .. deletion.len - overlap_length1]); + // XXX activate: allocator.free(diffs.items[@inteCast(pointer+1)].text); diffs.items[@intCast(pointer + 1)].text = try allocator.dupe(u8, insertion[overlap_length1..]); pointer += 1; @@ -1061,11 +1051,13 @@ fn diffCleanupSemantic(allocator: std.mem.Allocator, diffs: *DiffList) DiffError Diff.init(.equal, try allocator.dupe(u8, deletion[0..overlap_length2])), ); diffs.items[@intCast(pointer - 1)].operation = .insert; - diffs.items[@intCast(pointer - 1)].text = - try allocator.dupe(u8, insertion[0 .. insertion.len - overlap_length2]); + const new_minus = try allocator.dupe(u8, insertion[0 .. insertion.len - overlap_length2]); + // XXX activate: allocator.free(diffs.items[@inteCast(pointer-1)].text); + diffs.items[@intCast(pointer - 1)].text = new_minus; diffs.items[@intCast(pointer + 1)].operation = .delete; - diffs.items[@intCast(pointer + 1)].text = - try allocator.dupe(u8, deletion[overlap_length2..]); + const new_plus = try allocator.dupe(u8, deletion[overlap_length2..]); + // XXX activate: allocator.free(diffs.items[@inteCast(pointer+1)].text); + diffs.items[@intCast(pointer + 1)].text = new_plus; pointer += 1; } } @@ -1114,7 +1106,7 @@ pub fn diffCleanupSemanticLossless( const not_common = try allocator.dupe(u8, edit.items[0 .. edit.items.len - common_offset]); defer allocator.free(not_common); - edit.items.len = 0; + edit.clearRetainingCapacity(); try edit.appendSlice(allocator, common_string); try edit.appendSlice(allocator, not_common); @@ -1167,16 +1159,23 @@ pub fn diffCleanupSemanticLossless( if (!std.mem.eql(u8, diffs.items[pointer - 1].text, best_equality_1.items)) { // We have an improvement, save it back to the diff. if (best_equality_1.items.len != 0) { + // allocator.free(diffs.items[pointer - 1].text); diffs.items[pointer - 1].text = try allocator.dupe(u8, best_equality_1.items); } else { - _ = diffs.orderedRemove(pointer - 1); + const old_diff = diffs.orderedRemove(pointer - 1); + // allocator.free(old_diff.text); + _ = old_diff; pointer -= 1; } + // allocator.free(diffs.items[pointer].text); diffs.items[pointer].text = try allocator.dupe(u8, best_edit.items); if (best_equality_2.items.len != 0) { + // allocator.free(diffs.items[pointer - 1].text); diffs.items[pointer + 1].text = try allocator.dupe(u8, best_equality_2.items); } else { - _ = diffs.orderedRemove(pointer + 1); + const old_diff = diffs.orderedRemove(pointer + 1); + // allocator.free(old_diff.text); + _ = old_diff; pointer -= 1; } } @@ -1259,6 +1258,7 @@ pub fn diffCleanupEfficiency( var changes = false; // Stack of indices where equalities are found. var equalities = DiffList{}; + defer deinitDiffList(allocator, equalities); // Always equal to equalities[equalitiesLength-1][1] var last_equality = ""; var pointer: isize = 0; // Index of current position. From a5c993f07ac792c51f1744eaae3b478a68754f18 Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Tue, 2 Jul 2024 16:08:36 -0400 Subject: [PATCH 07/60] Managed memory in diffCharsToLines --- DiffMatchPatch.zig | 37 ++++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/DiffMatchPatch.zig b/DiffMatchPatch.zig index 6458df4..c34ea65 100644 --- a/DiffMatchPatch.zig +++ b/DiffMatchPatch.zig @@ -776,6 +776,7 @@ fn diffCharsToLines( while (j < d.text.len) : (j += 1) { try text.appendSlice(allocator, line_array[d.text[j]]); } + allocator.free(d.text); d.text = try allocator.dupe(u8, text.items); } } @@ -1575,14 +1576,14 @@ test diffLinesToChars { try tmp_array_list.append(""); try tmp_array_list.append("a"); try tmp_array_list.append("b"); - result.deinit(allocator); + result = try diffLinesToChars(allocator, "a", "b"); try testing.expectEqualStrings("\u{0001}", result.chars_1); // No linebreaks #1. try testing.expectEqualStrings("\u{0002}", result.chars_2); // No linebreaks #2. try testing.expectEqualDeep(tmp_array_list.items, result.line_array.items); // No linebreaks #3. - result.deinit(allocator); + // TODO: More than 256 to reveal any 8-bit limitations but this requires // some unicode logic that I don't want to deal with @@ -1612,23 +1613,33 @@ test diffLinesToChars { test diffCharsToLines { var arena = std.heap.ArenaAllocator.init(testing.allocator); defer arena.deinit(); - - try testing.expect((Diff.init(.equal, "a")).eql(Diff.init(.equal, "a"))); - try testing.expect(!(Diff.init(.insert, "a")).eql(Diff.init(.equal, "a"))); - try testing.expect(!(Diff.init(.equal, "a")).eql(Diff.init(.equal, "b"))); - try testing.expect(!(Diff.init(.equal, "a")).eql(Diff.init(.delete, "b"))); + const alloc = std.testing.allocator; + const equal_a = Diff.init(.equal, try alloc.dupe(u8, "a")); + defer alloc.free(equal_a.text); + const insert_a = Diff.init(.insert, try alloc.dupe(u8, "a")); + defer alloc.free(insert_a.text); + const equal_b = Diff.init(.equal, try alloc.dupe(u8, "b")); + defer alloc.free(equal_b.text); + const delete_b = Diff.init(.delete, try alloc.dupe(u8, "b")); + defer alloc.free(delete_b.text); + try testing.expect(equal_a.eql(equal_a)); + try testing.expect(!insert_a.eql(equal_a)); + try testing.expect(!equal_a.eql(equal_b)); + try testing.expect(!equal_a.eql(delete_b)); // Convert chars up to lines. - var diffs = std.ArrayList(Diff).init(arena.allocator()); - try diffs.appendSlice(&.{ - Diff{ .operation = .equal, .text = try arena.allocator().dupe(u8, "\u{0001}\u{0002}\u{0001}") }, - Diff{ .operation = .insert, .text = try arena.allocator().dupe(u8, "\u{0002}\u{0001}\u{0002}") }, + var diffs = DiffList{}; + defer deinitDiffList(alloc, &diffs); + try diffs.appendSlice(alloc, &.{ + Diff{ .operation = .equal, .text = try alloc.dupe(u8, "\u{0001}\u{0002}\u{0001}") }, + Diff{ .operation = .insert, .text = try alloc.dupe(u8, "\u{0002}\u{0001}\u{0002}") }, }); - var tmp_vector = std.ArrayList([]const u8).init(arena.allocator()); + var tmp_vector = std.ArrayList([]const u8).init(alloc); + defer tmp_vector.deinit(); try tmp_vector.append(""); try tmp_vector.append("alpha\n"); try tmp_vector.append("beta\n"); - try diffCharsToLines(arena.allocator(), diffs.items, tmp_vector.items); + try diffCharsToLines(alloc, diffs.items, tmp_vector.items); try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ Diff.init(.equal, "alpha\nbeta\nalpha\n"), From a2beb6f4fb61bcbd51a4a9aab33c5779a7f9c46d Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Tue, 2 Jul 2024 16:31:28 -0400 Subject: [PATCH 08/60] Fix for diffCleanupMerge memory I'll need to knock out all the tests in diffMergeSemantic so that I can flip the rest of the diffCleanupMerge tests on, without trying to free .rodata memory. The real annoyance of the decision to punt on memory management is in the tests, which all have to be rewritten. Whoever you are, you could at least have written the tests responsibly, and spared the person who cleans up your sloppy code from a bunch of drudgery. --- DiffMatchPatch.zig | 428 +++++++++++++++++++++++---------------------- 1 file changed, 220 insertions(+), 208 deletions(-) diff --git a/DiffMatchPatch.zig b/DiffMatchPatch.zig index c34ea65..d36cfbf 100644 --- a/DiffMatchPatch.zig +++ b/DiffMatchPatch.zig @@ -824,14 +824,13 @@ fn diffCleanupMerge(allocator: std.mem.Allocator, diffs: *DiffList) DiffError!vo var nt = try allocator.alloc(u8, diffs.items[ii].text.len + common_length); const ot = diffs.items[ii].text; + defer allocator.free(ot); @memcpy(nt[0..ot.len], ot); @memcpy(nt[ot.len..], text_insert.items[0..common_length]); diffs.items[ii].text = nt; } else { - const text = std.ArrayListUnmanaged(u8){ - .items = try allocator.dupe(u8, text_insert.items[0..common_length]), - }; - try diffs.insert(allocator, 0, Diff.init(.equal, try allocator.dupe(u8, text.items))); + const text = try allocator.dupe(u8, text_insert.items[0..common_length]); + try diffs.insert(allocator, 0, Diff.init(.equal, text)); pointer += 1; } try text_insert.replaceRange(allocator, 0, common_length, &.{}); @@ -841,9 +840,11 @@ fn diffCleanupMerge(allocator: std.mem.Allocator, diffs: *DiffList) DiffError!vo // @ZigPort this seems very wrong common_length = diffCommonSuffix(text_insert.items, text_delete.items); if (common_length != 0) { + const old_text = diffs.items[pointer].text; + defer allocator.free(old_text); diffs.items[pointer].text = try std.mem.concat(allocator, u8, &.{ text_insert.items[text_insert.items.len - common_length ..], - diffs.items[pointer].text, + old_text, }); text_insert.items.len -= common_length; text_delete.items.len -= common_length; @@ -851,18 +852,21 @@ fn diffCleanupMerge(allocator: std.mem.Allocator, diffs: *DiffList) DiffError!vo } // Delete the offending records and add the merged ones. pointer -= count_delete + count_insert; + freeRangeDiffList(allocator, diffs, pointer, count_delete + count_insert); try diffs.replaceRange(allocator, pointer, count_delete + count_insert, &.{}); if (text_delete.items.len != 0) { - try diffs.replaceRange(allocator, pointer, 0, &.{ - Diff.init(.delete, try allocator.dupe(u8, text_delete.items)), - }); + try diffs.insert(allocator, pointer, Diff.init( + .delete, + try allocator.dupe(u8, text_delete.items), + )); pointer += 1; } if (text_insert.items.len != 0) { - try diffs.replaceRange(allocator, pointer, 0, &.{ - Diff.init(.insert, try allocator.dupe(u8, text_insert.items)), - }); + try diffs.insert(allocator, pointer, Diff.init( + .insert, + try allocator.dupe(u8, text_insert.items), + )); pointer += 1; } pointer += 1; @@ -871,10 +875,12 @@ fn diffCleanupMerge(allocator: std.mem.Allocator, diffs: *DiffList) DiffError!vo // TODO: Fix using realloc or smth var nt = try allocator.alloc(u8, diffs.items[pointer - 1].text.len + diffs.items[pointer].text.len); const ot = diffs.items[pointer - 1].text; + defer (allocator.free(ot)); @memcpy(nt[0..ot.len], ot); @memcpy(nt[ot.len..], diffs.items[pointer].text); diffs.items[pointer - 1].text = nt; - _ = diffs.orderedRemove(pointer); + const dead_diff = diffs.orderedRemove(pointer); + allocator.free(dead_diff.text); } else { pointer += 1; } @@ -912,7 +918,7 @@ fn diffCleanupMerge(allocator: std.mem.Allocator, diffs: *DiffList) DiffError!vo }); diffs.items[pointer].text = pt; diffs.items[pointer + 1].text = p1t; - // XXX reactivate freeRangeDiffList(allocator, diffs, pointer - 1, 1); + freeRangeDiffList(allocator, diffs, pointer - 1, 1); try diffs.replaceRange(allocator, pointer - 1, 1, &.{}); changes = true; } else if (std.mem.startsWith(u8, diffs.items[pointer].text, diffs.items[pointer + 1].text)) { @@ -926,7 +932,7 @@ fn diffCleanupMerge(allocator: std.mem.Allocator, diffs: *DiffList) DiffError!vo }); diffs.items[pointer - 1].text = pm1t; diffs.items[pointer].text = pt; - // XXX reactivate freeRangeDiffList(allocator, diffs, pointer - 1, 1); + freeRangeDiffList(allocator, diffs, pointer - 1, 1); try diffs.replaceRange(allocator, pointer + 1, 1, &.{}); changes = true; } @@ -1611,17 +1617,15 @@ test diffLinesToChars { } test diffCharsToLines { - var arena = std.heap.ArenaAllocator.init(testing.allocator); - defer arena.deinit(); - const alloc = std.testing.allocator; - const equal_a = Diff.init(.equal, try alloc.dupe(u8, "a")); - defer alloc.free(equal_a.text); - const insert_a = Diff.init(.insert, try alloc.dupe(u8, "a")); - defer alloc.free(insert_a.text); - const equal_b = Diff.init(.equal, try alloc.dupe(u8, "b")); - defer alloc.free(equal_b.text); - const delete_b = Diff.init(.delete, try alloc.dupe(u8, "b")); - defer alloc.free(delete_b.text); + const allocator = std.testing.allocator; + const equal_a = Diff.init(.equal, try allocator.dupe(u8, "a")); + defer allocator.free(equal_a.text); + const insert_a = Diff.init(.insert, try allocator.dupe(u8, "a")); + defer allocator.free(insert_a.text); + const equal_b = Diff.init(.equal, try allocator.dupe(u8, "b")); + defer allocator.free(equal_b.text); + const delete_b = Diff.init(.delete, try allocator.dupe(u8, "b")); + defer allocator.free(delete_b.text); try testing.expect(equal_a.eql(equal_a)); try testing.expect(!insert_a.eql(equal_a)); try testing.expect(!equal_a.eql(equal_b)); @@ -1629,17 +1633,17 @@ test diffCharsToLines { // Convert chars up to lines. var diffs = DiffList{}; - defer deinitDiffList(alloc, &diffs); - try diffs.appendSlice(alloc, &.{ - Diff{ .operation = .equal, .text = try alloc.dupe(u8, "\u{0001}\u{0002}\u{0001}") }, - Diff{ .operation = .insert, .text = try alloc.dupe(u8, "\u{0002}\u{0001}\u{0002}") }, + defer deinitDiffList(allocator, &diffs); + try diffs.appendSlice(allocator, &.{ + Diff{ .operation = .equal, .text = try allocator.dupe(u8, "\u{0001}\u{0002}\u{0001}") }, + Diff{ .operation = .insert, .text = try allocator.dupe(u8, "\u{0002}\u{0001}\u{0002}") }, }); - var tmp_vector = std.ArrayList([]const u8).init(alloc); + var tmp_vector = std.ArrayList([]const u8).init(allocator); defer tmp_vector.deinit(); try tmp_vector.append(""); try tmp_vector.append("alpha\n"); try tmp_vector.append("beta\n"); - try diffCharsToLines(alloc, diffs.items, tmp_vector.items); + try diffCharsToLines(allocator, diffs.items, tmp_vector.items); try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ Diff.init(.equal, "alpha\nbeta\nalpha\n"), @@ -1653,188 +1657,196 @@ test diffCleanupMerge { var arena = std.heap.ArenaAllocator.init(testing.allocator); defer arena.deinit(); + const alloc = std.testing.allocator; // Cleanup a messy diff. var diffs = DiffList{}; - try testing.expectEqualDeep(@as([]const Diff, &[0]Diff{}), diffs.items); // Null case - - try diffs.appendSlice(arena.allocator(), &[_]Diff{ - .{ .operation = .equal, .text = "a" }, - .{ .operation = .delete, .text = "b" }, - .{ .operation = .insert, .text = "c" }, - }); - try diffCleanupMerge(arena.allocator(), &diffs); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ - .{ .operation = .equal, .text = "a" }, - .{ .operation = .delete, .text = "b" }, - .{ .operation = .insert, .text = "c" }, - }), diffs.items); // No change case - - diffs.items.len = 0; - - try diffs.appendSlice(arena.allocator(), &[_]Diff{ - .{ .operation = .equal, .text = "a" }, - .{ .operation = .equal, .text = "b" }, - .{ .operation = .equal, .text = "c" }, - }); - try diffCleanupMerge(arena.allocator(), &diffs); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ - .{ .operation = .equal, .text = "abc" }, - }), diffs.items); // Merge equalities - - diffs.items.len = 0; - - try diffs.appendSlice(arena.allocator(), &[_]Diff{ - .{ .operation = .delete, .text = "a" }, - .{ .operation = .delete, .text = "b" }, - .{ .operation = .delete, .text = "c" }, - }); - try diffCleanupMerge(arena.allocator(), &diffs); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ - .{ .operation = .delete, .text = "abc" }, - }), diffs.items); // Merge deletions - - diffs.items.len = 0; - - try diffs.appendSlice(arena.allocator(), &[_]Diff{ - .{ .operation = .insert, .text = "a" }, - .{ .operation = .insert, .text = "b" }, - .{ .operation = .insert, .text = "c" }, - }); - try diffCleanupMerge(arena.allocator(), &diffs); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ - .{ .operation = .insert, .text = "abc" }, - }), diffs.items); // Merge insertions - - diffs.items.len = 0; - - try diffs.appendSlice(arena.allocator(), &[_]Diff{ - .{ .operation = .delete, .text = "a" }, - .{ .operation = .insert, .text = "b" }, - .{ .operation = .delete, .text = "c" }, - .{ .operation = .insert, .text = "d" }, - .{ .operation = .equal, .text = "e" }, - .{ .operation = .equal, .text = "f" }, - }); - try diffCleanupMerge(arena.allocator(), &diffs); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ - .{ .operation = .delete, .text = "ac" }, - .{ .operation = .insert, .text = "bd" }, - .{ .operation = .equal, .text = "ef" }, - }), diffs.items); // Merge interweave - - diffs.items.len = 0; - - try diffs.appendSlice(arena.allocator(), &[_]Diff{ - .{ .operation = .delete, .text = "a" }, - .{ .operation = .insert, .text = "abc" }, - .{ .operation = .delete, .text = "dc" }, - }); - try diffCleanupMerge(arena.allocator(), &diffs); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ - .{ .operation = .equal, .text = "a" }, - .{ .operation = .delete, .text = "d" }, - .{ .operation = .insert, .text = "b" }, - .{ .operation = .equal, .text = "c" }, - }), diffs.items); // Prefix and suffix detection - - diffs.items.len = 0; - - try diffs.appendSlice(arena.allocator(), &[_]Diff{ - .{ .operation = .equal, .text = "x" }, - .{ .operation = .delete, .text = "a" }, - .{ .operation = .insert, .text = "abc" }, - .{ .operation = .delete, .text = "dc" }, - .{ .operation = .equal, .text = "y" }, - }); - try diffCleanupMerge(arena.allocator(), &diffs); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ - .{ .operation = .equal, .text = "xa" }, - .{ .operation = .delete, .text = "d" }, - .{ .operation = .insert, .text = "b" }, - .{ .operation = .equal, .text = "cy" }, - }), diffs.items); // Prefix and suffix detection with equalities - - diffs.items.len = 0; - - try diffs.appendSlice(arena.allocator(), &[_]Diff{ - .{ .operation = .equal, .text = "a" }, - .{ .operation = .insert, .text = "ba" }, - .{ .operation = .equal, .text = "c" }, - }); - try diffCleanupMerge(arena.allocator(), &diffs); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ - .{ .operation = .insert, .text = "ab" }, - .{ .operation = .equal, .text = "ac" }, - }), diffs.items); // Slide edit left - - diffs.items.len = 0; - - try diffs.appendSlice(arena.allocator(), &[_]Diff{ - .{ .operation = .equal, .text = "c" }, - .{ .operation = .insert, .text = "ab" }, - .{ .operation = .equal, .text = "a" }, - }); - try diffCleanupMerge(arena.allocator(), &diffs); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ - .{ .operation = .equal, .text = "ca" }, - .{ .operation = .insert, .text = "ba" }, - }), diffs.items); // Slide edit right - - diffs.items.len = 0; - - try diffs.appendSlice(arena.allocator(), &[_]Diff{ - Diff.init(.equal, "a"), - Diff.init(.delete, "b"), - Diff.init(.equal, "c"), - Diff.init(.delete, "ac"), - Diff.init(.equal, "x"), - }); - try diffCleanupMerge(arena.allocator(), &diffs); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ - Diff.init(.delete, "abc"), - Diff.init(.equal, "acx"), - }), diffs.items); // Slide edit left recursive - - diffs.items.len = 0; - - try diffs.appendSlice(arena.allocator(), &[_]Diff{ - Diff.init(.equal, "x"), - Diff.init(.delete, "ca"), - Diff.init(.equal, "c"), - Diff.init(.delete, "b"), - Diff.init(.equal, "a"), - }); - try diffCleanupMerge(arena.allocator(), &diffs); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ - Diff.init(.equal, "xca"), - Diff.init(.delete, "cba"), - }), diffs.items); // Slide edit right recursive - - diffs.items.len = 0; - - try diffs.appendSlice(arena.allocator(), &[_]Diff{ - Diff.init(.delete, "b"), - Diff.init(.insert, "ab"), - Diff.init(.equal, "c"), - }); - try diffCleanupMerge(arena.allocator(), &diffs); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ - Diff.init(.insert, "a"), - Diff.init(.equal, "bc"), - }), diffs.items); // Empty merge + defer deinitDiffList(alloc, &diffs); - diffs.items.len = 0; + try testing.expectEqualDeep(@as([]const Diff, &[0]Diff{}), diffs.items); // Null case - try diffs.appendSlice(arena.allocator(), &[_]Diff{ - Diff.init(.equal, ""), - Diff.init(.insert, "a"), - Diff.init(.equal, "b"), + try diffs.appendSlice(alloc, &[_]Diff{ + .{ + .operation = .equal, + .text = try alloc.dupe(u8, "a"), + }, + .{ + .operation = .delete, + .text = try alloc.dupe(u8, "b"), + }, + .{ + .operation = .insert, + .text = try alloc.dupe(u8, "c"), + }, }); - try diffCleanupMerge(arena.allocator(), &diffs); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ - Diff.init(.insert, "a"), - Diff.init(.equal, "b"), - }), diffs.items); // Empty equality + try diffCleanupMerge(alloc, &diffs); + try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ .{ .operation = .equal, .text = "a" }, .{ .operation = .delete, .text = "b" }, .{ .operation = .insert, .text = "c" } }), diffs.items); // No change case + // + // var diffs2 = DiffList{}; + // + // try diffs2.appendSlice(arena.allocator(), &[_]Diff{ + // .{ .operation = .equal, .text = "a" }, + // .{ .operation = .equal, .text = "b" }, + // .{ .operation = .equal, .text = "c" }, + // }); + // try diffCleanupMerge(arena.allocator(), &diffs); + // try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ + // .{ .operation = .equal, .text = "abc" }, + // }), diffs2.items); // Merge equalities + // + // diffs2.items.len = 0; + // + // try diffs2.appendSlice(arena.allocator(), &[_]Diff{ + // .{ .operation = .delete, .text = "a" }, + // .{ .operation = .delete, .text = "b" }, + // .{ .operation = .delete, .text = "c" }, + // }); + // try diffCleanupMerge(arena.allocator(), &diffs); + // try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ + // .{ .operation = .delete, .text = "abc" }, + // }), diffs2.items); // Merge deletions + // + // diffs2.items.len = 0; + // + // try diffs2.appendSlice(arena.allocator(), &[_]Diff{ + // .{ .operation = .insert, .text = "a" }, + // .{ .operation = .insert, .text = "b" }, + // .{ .operation = .insert, .text = "c" }, + // }); + // try diffCleanupMerge(arena.allocator(), &diffs); + // try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ + // .{ .operation = .insert, .text = "abc" }, + // }), diffs2.items); // Merge insertions + // + // diffs2.items.len = 0; + // + // try diffs2.appendSlice(arena.allocator(), &[_]Diff{ + // .{ .operation = .delete, .text = "a" }, + // .{ .operation = .insert, .text = "b" }, + // .{ .operation = .delete, .text = "c" }, + // .{ .operation = .insert, .text = "d" }, + // .{ .operation = .equal, .text = "e" }, + // .{ .operation = .equal, .text = "f" }, + // }); + // try diffCleanupMerge(arena.allocator(), &diffs); + // try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ + // .{ .operation = .delete, .text = "ac" }, + // .{ .operation = .insert, .text = "bd" }, + // .{ .operation = .equal, .text = "ef" }, + // }), diffs2.items); // Merge interweave + // + // diffs2.items.len = 0; + // + // try diffs2.appendSlice(arena.allocator(), &[_]Diff{ + // .{ .operation = .delete, .text = "a" }, + // .{ .operation = .insert, .text = "abc" }, + // .{ .operation = .delete, .text = "dc" }, + // }); + // try diffCleanupMerge(arena.allocator(), &diffs); + // try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ + // .{ .operation = .equal, .text = "a" }, + // .{ .operation = .delete, .text = "d" }, + // .{ .operation = .insert, .text = "b" }, + // .{ .operation = .equal, .text = "c" }, + // }), diffs2.items); // Prefix and suffix detection + // + // diffs2.items.len = 0; + // + // try diffs2.appendSlice(arena.allocator(), &[_]Diff{ + // .{ .operation = .equal, .text = "x" }, + // .{ .operation = .delete, .text = "a" }, + // .{ .operation = .insert, .text = "abc" }, + // .{ .operation = .delete, .text = "dc" }, + // .{ .operation = .equal, .text = "y" }, + // }); + // try diffCleanupMerge(arena.allocator(), &diffs); + // try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ + // .{ .operation = .equal, .text = "xa" }, + // .{ .operation = .delete, .text = "d" }, + // .{ .operation = .insert, .text = "b" }, + // .{ .operation = .equal, .text = "cy" }, + // }), diffs2.items); // Prefix and suffix detection with equalities + // + // diffs2.items.len = 0; + // + // try diffs2.appendSlice(arena.allocator(), &[_]Diff{ + // .{ .operation = .equal, .text = "a" }, + // .{ .operation = .insert, .text = "ba" }, + // .{ .operation = .equal, .text = "c" }, + // }); + // try diffCleanupMerge(arena.allocator(), &diffs); + // try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ + // .{ .operation = .insert, .text = "ab" }, + // .{ .operation = .equal, .text = "ac" }, + // }), diffs2.items); // Slide edit left + // + // diffs2.items.len = 0; + // + // try diffs2.appendSlice(arena.allocator(), &[_]Diff{ + // .{ .operation = .equal, .text = "c" }, + // .{ .operation = .insert, .text = "ab" }, + // .{ .operation = .equal, .text = "a" }, + // }); + // try diffCleanupMerge(arena.allocator(), &diffs); + // try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ + // .{ .operation = .equal, .text = "ca" }, + // .{ .operation = .insert, .text = "ba" }, + // }), diffs2.items); // Slide edit right + // + // diffs2.items.len = 0; + // + // try diffs2.appendSlice(arena.allocator(), &[_]Diff{ + // Diff.init(.equal, "a"), + // Diff.init(.delete, "b"), + // Diff.init(.equal, "c"), + // Diff.init(.delete, "ac"), + // Diff.init(.equal, "x"), + // }); + // try diffCleanupMerge(arena.allocator(), &diffs); + // try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ + // Diff.init(.delete, "abc"), + // Diff.init(.equal, "acx"), + // }), diffs2.items); // Slide edit left recursive + // + // diffs2.items.len = 0; + // + // try diffs2.appendSlice(arena.allocator(), &[_]Diff{ + // Diff.init(.equal, "x"), + // Diff.init(.delete, "ca"), + // Diff.init(.equal, "c"), + // Diff.init(.delete, "b"), + // Diff.init(.equal, "a"), + // }); + // try diffCleanupMerge(arena.allocator(), &diffs); + // try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ + // Diff.init(.equal, "xca"), + // Diff.init(.delete, "cba"), + // }), diffs2.items); // Slide edit right recursive + // + // diffs2.items.len = 0; + // + // try diffs2.appendSlice(arena.allocator(), &[_]Diff{ + // Diff.init(.delete, "b"), + // Diff.init(.insert, "ab"), + // Diff.init(.equal, "c"), + // }); + // try diffCleanupMerge(arena.allocator(), &diffs); + // try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ + // Diff.init(.insert, "a"), + // Diff.init(.equal, "bc"), + // }), diffs2.items); // Empty merge + // + // diffs2.items.len = 0; + // + // try diffs2.appendSlice(arena.allocator(), &[_]Diff{ + // Diff.init(.equal, ""), + // Diff.init(.insert, "a"), + // Diff.init(.equal, "b"), + // }); + // try diffCleanupMerge(arena.allocator(), &diffs); + // try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ + // Diff.init(.insert, "a"), + // Diff.init(.equal, "b"), + // }), diffs2.items); // Empty equality } test diffCleanupSemanticLossless { From 7c5cccc615d5c1279b8285ef2e34a4d80513a445 Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Tue, 2 Jul 2024 17:12:33 -0400 Subject: [PATCH 09/60] One test at a time... --- DiffMatchPatch.zig | 558 +++++++++++++++++++++++---------------------- 1 file changed, 286 insertions(+), 272 deletions(-) diff --git a/DiffMatchPatch.zig b/DiffMatchPatch.zig index d36cfbf..0f0ead8 100644 --- a/DiffMatchPatch.zig +++ b/DiffMatchPatch.zig @@ -852,21 +852,23 @@ fn diffCleanupMerge(allocator: std.mem.Allocator, diffs: *DiffList) DiffError!vo } // Delete the offending records and add the merged ones. pointer -= count_delete + count_insert; - freeRangeDiffList(allocator, diffs, pointer, count_delete + count_insert); - try diffs.replaceRange(allocator, pointer, count_delete + count_insert, &.{}); + if (count_delete + count_insert > 0) { + freeRangeDiffList(allocator, diffs, pointer, count_delete + count_insert); + try diffs.replaceRange(allocator, pointer, count_delete + count_insert, &.{}); + } if (text_delete.items.len != 0) { - try diffs.insert(allocator, pointer, Diff.init( - .delete, - try allocator.dupe(u8, text_delete.items), - )); + allocator.free(diffs.items[pointer].text); + try diffs.replaceRange(allocator, pointer, 0, &.{ + Diff.init(.delete, try allocator.dupe(u8, text_delete.items)), + }); pointer += 1; } if (text_insert.items.len != 0) { - try diffs.insert(allocator, pointer, Diff.init( - .insert, - try allocator.dupe(u8, text_insert.items), - )); + allocator.free(diffs.items[pointer].text); + try diffs.replaceRange(allocator, pointer, 0, &.{ + Diff.init(.insert, try allocator.dupe(u8, text_insert.items)), + }); pointer += 1; } pointer += 1; @@ -1680,18 +1682,26 @@ test diffCleanupMerge { }); try diffCleanupMerge(alloc, &diffs); try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ .{ .operation = .equal, .text = "a" }, .{ .operation = .delete, .text = "b" }, .{ .operation = .insert, .text = "c" } }), diffs.items); // No change case - // - // var diffs2 = DiffList{}; - // - // try diffs2.appendSlice(arena.allocator(), &[_]Diff{ - // .{ .operation = .equal, .text = "a" }, - // .{ .operation = .equal, .text = "b" }, - // .{ .operation = .equal, .text = "c" }, - // }); - // try diffCleanupMerge(arena.allocator(), &diffs); - // try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ - // .{ .operation = .equal, .text = "abc" }, - // }), diffs2.items); // Merge equalities + var diffs2 = DiffList{}; + defer deinitDiffList(alloc, &diffs2); + try diffs2.appendSlice(alloc, &[_]Diff{ + .{ + .operation = .equal, + .text = try alloc.dupe(u8, "a"), + }, + .{ + .operation = .equal, + .text = try alloc.dupe(u8, "b"), + }, + .{ + .operation = .equal, + .text = try alloc.dupe(u8, "c"), + }, + }); + try diffCleanupMerge(alloc, &diffs2); + try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ + .{ .operation = .equal, .text = "abc" }, + }), diffs2.items); // Merge equalities // // diffs2.items.len = 0; // @@ -1850,108 +1860,110 @@ test diffCleanupMerge { } test diffCleanupSemanticLossless { - var arena = std.heap.ArenaAllocator.init(testing.allocator); - defer arena.deinit(); - - var diffs = DiffList{}; - try diffCleanupSemanticLossless(arena.allocator(), &diffs); - try testing.expectEqualDeep(@as([]const Diff, &[0]Diff{}), diffs.items); // Null case - - diffs.items.len = 0; - - try diffs.appendSlice(arena.allocator(), &.{ - Diff.init(.equal, "AAA\r\n\r\nBBB"), - Diff.init(.insert, "\r\nDDD\r\n\r\nBBB"), - Diff.init(.equal, "\r\nEEE"), - }); - try diffCleanupSemanticLossless(arena.allocator(), &diffs); - try testing.expectEqualDeep(@as([]const Diff, &.{ - Diff.init(.equal, "AAA\r\n\r\n"), - Diff.init(.insert, "BBB\r\nDDD\r\n\r\n"), - Diff.init(.equal, "BBB\r\nEEE"), - }), diffs.items); - - diffs.items.len = 0; + if (false) { + var arena = std.heap.ArenaAllocator.init(testing.allocator); + defer arena.deinit(); - try diffs.appendSlice(arena.allocator(), &.{ - Diff.init(.equal, "AAA\r\nBBB"), - Diff.init(.insert, " DDD\r\nBBB"), - Diff.init(.equal, " EEE"), - }); - try diffCleanupSemanticLossless(arena.allocator(), &diffs); - try testing.expectEqualDeep(@as([]const Diff, &.{ - Diff.init(.equal, "AAA\r\n"), - Diff.init(.insert, "BBB DDD\r\n"), - Diff.init(.equal, "BBB EEE"), - }), diffs.items); - - diffs.items.len = 0; - - try diffs.appendSlice(arena.allocator(), &.{ - Diff.init(.equal, "The c"), - Diff.init(.insert, "ow and the c"), - Diff.init(.equal, "at."), - }); - try diffCleanupSemanticLossless(arena.allocator(), &diffs); - try testing.expectEqualDeep(@as([]const Diff, &.{ - Diff.init(.equal, "The "), - Diff.init(.insert, "cow and the "), - Diff.init(.equal, "cat."), - }), diffs.items); - - diffs.items.len = 0; - - try diffs.appendSlice(arena.allocator(), &.{ - Diff.init(.equal, "The-c"), - Diff.init(.insert, "ow-and-the-c"), - Diff.init(.equal, "at."), - }); - try diffCleanupSemanticLossless(arena.allocator(), &diffs); - try testing.expectEqualDeep(@as([]const Diff, &.{ - Diff.init(.equal, "The-"), - Diff.init(.insert, "cow-and-the-"), - Diff.init(.equal, "cat."), - }), diffs.items); - - diffs.items.len = 0; - - try diffs.appendSlice(arena.allocator(), &.{ - Diff.init(.equal, "a"), - Diff.init(.delete, "a"), - Diff.init(.equal, "ax"), - }); - try diffCleanupSemanticLossless(arena.allocator(), &diffs); - try testing.expectEqualDeep(@as([]const Diff, &.{ - Diff.init(.delete, "a"), - Diff.init(.equal, "aax"), - }), diffs.items); - - diffs.items.len = 0; - - try diffs.appendSlice(arena.allocator(), &.{ - Diff.init(.equal, "xa"), - Diff.init(.delete, "a"), - Diff.init(.equal, "a"), - }); - try diffCleanupSemanticLossless(arena.allocator(), &diffs); - try testing.expectEqualDeep(@as([]const Diff, &.{ - Diff.init(.equal, "xaa"), - Diff.init(.delete, "a"), - }), diffs.items); + var diffs = DiffList{}; + try diffCleanupSemanticLossless(arena.allocator(), &diffs); + try testing.expectEqualDeep(@as([]const Diff, &[0]Diff{}), diffs.items); // Null case - diffs.items.len = 0; + diffs.items.len = 0; - try diffs.appendSlice(arena.allocator(), &.{ - Diff.init(.equal, "The xxx. The "), - Diff.init(.insert, "zzz. The "), - Diff.init(.equal, "yyy."), - }); - try diffCleanupSemanticLossless(arena.allocator(), &diffs); - try testing.expectEqualDeep(@as([]const Diff, &.{ - Diff.init(.equal, "The xxx."), - Diff.init(.insert, " The zzz."), - Diff.init(.equal, " The yyy."), - }), diffs.items); + try diffs.appendSlice(arena.allocator(), &.{ + Diff.init(.equal, "AAA\r\n\r\nBBB"), + Diff.init(.insert, "\r\nDDD\r\n\r\nBBB"), + Diff.init(.equal, "\r\nEEE"), + }); + try diffCleanupSemanticLossless(arena.allocator(), &diffs); + try testing.expectEqualDeep(@as([]const Diff, &.{ + Diff.init(.equal, "AAA\r\n\r\n"), + Diff.init(.insert, "BBB\r\nDDD\r\n\r\n"), + Diff.init(.equal, "BBB\r\nEEE"), + }), diffs.items); + + diffs.items.len = 0; + + try diffs.appendSlice(arena.allocator(), &.{ + Diff.init(.equal, "AAA\r\nBBB"), + Diff.init(.insert, " DDD\r\nBBB"), + Diff.init(.equal, " EEE"), + }); + try diffCleanupSemanticLossless(arena.allocator(), &diffs); + try testing.expectEqualDeep(@as([]const Diff, &.{ + Diff.init(.equal, "AAA\r\n"), + Diff.init(.insert, "BBB DDD\r\n"), + Diff.init(.equal, "BBB EEE"), + }), diffs.items); + + diffs.items.len = 0; + + try diffs.appendSlice(arena.allocator(), &.{ + Diff.init(.equal, "The c"), + Diff.init(.insert, "ow and the c"), + Diff.init(.equal, "at."), + }); + try diffCleanupSemanticLossless(arena.allocator(), &diffs); + try testing.expectEqualDeep(@as([]const Diff, &.{ + Diff.init(.equal, "The "), + Diff.init(.insert, "cow and the "), + Diff.init(.equal, "cat."), + }), diffs.items); + + diffs.items.len = 0; + + try diffs.appendSlice(arena.allocator(), &.{ + Diff.init(.equal, "The-c"), + Diff.init(.insert, "ow-and-the-c"), + Diff.init(.equal, "at."), + }); + try diffCleanupSemanticLossless(arena.allocator(), &diffs); + try testing.expectEqualDeep(@as([]const Diff, &.{ + Diff.init(.equal, "The-"), + Diff.init(.insert, "cow-and-the-"), + Diff.init(.equal, "cat."), + }), diffs.items); + + diffs.items.len = 0; + + try diffs.appendSlice(arena.allocator(), &.{ + Diff.init(.equal, "a"), + Diff.init(.delete, "a"), + Diff.init(.equal, "ax"), + }); + try diffCleanupSemanticLossless(arena.allocator(), &diffs); + try testing.expectEqualDeep(@as([]const Diff, &.{ + Diff.init(.delete, "a"), + Diff.init(.equal, "aax"), + }), diffs.items); + + diffs.items.len = 0; + + try diffs.appendSlice(arena.allocator(), &.{ + Diff.init(.equal, "xa"), + Diff.init(.delete, "a"), + Diff.init(.equal, "a"), + }); + try diffCleanupSemanticLossless(arena.allocator(), &diffs); + try testing.expectEqualDeep(@as([]const Diff, &.{ + Diff.init(.equal, "xaa"), + Diff.init(.delete, "a"), + }), diffs.items); + + diffs.items.len = 0; + + try diffs.appendSlice(arena.allocator(), &.{ + Diff.init(.equal, "The xxx. The "), + Diff.init(.insert, "zzz. The "), + Diff.init(.equal, "yyy."), + }); + try diffCleanupSemanticLossless(arena.allocator(), &diffs); + try testing.expectEqualDeep(@as([]const Diff, &.{ + Diff.init(.equal, "The xxx."), + Diff.init(.insert, " The zzz."), + Diff.init(.equal, " The yyy."), + }), diffs.items); + } } fn rebuildtexts(allocator: std.mem.Allocator, diffs: DiffList) ![2][]const u8 { @@ -2116,155 +2128,157 @@ test diff { } test diffCleanupSemantic { - var arena = std.heap.ArenaAllocator.init(talloc); - defer arena.deinit(); - - // Cleanup semantically trivial equalities. - // Null case. - var diffs = DiffList{}; - defer diffs.deinit(arena.allocator()); - // var this = default; - try diffCleanupSemantic(arena.allocator(), &diffs); - try testing.expectEqual(@as(usize, 0), diffs.items.len); // Null case - - diffs.items.len = 0; - try diffs.appendSlice(arena.allocator(), &.{ - Diff.init(.delete, "ab"), - Diff.init(.insert, "cd"), - Diff.init(.equal, "12"), - Diff.init(.delete, "e"), - }); - try diffCleanupSemantic(arena.allocator(), &diffs); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // No elimination #1 - Diff.init(.delete, "ab"), - Diff.init(.insert, "cd"), - Diff.init(.equal, "12"), - Diff.init(.delete, "e"), - }), diffs.items); - - diffs.items.len = 0; - try diffs.appendSlice(arena.allocator(), &.{ - Diff.init(.delete, "abc"), - Diff.init(.insert, "ABC"), - Diff.init(.equal, "1234"), - Diff.init(.delete, "wxyz"), - }); - try diffCleanupSemantic(arena.allocator(), &diffs); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // No elimination #2 - Diff.init(.delete, "abc"), - Diff.init(.insert, "ABC"), - Diff.init(.equal, "1234"), - Diff.init(.delete, "wxyz"), - }), diffs.items); - - diffs.items.len = 0; - try diffs.appendSlice(arena.allocator(), &.{ - Diff.init(.delete, "a"), - Diff.init(.equal, "b"), - Diff.init(.delete, "c"), - }); - try diffCleanupSemantic(arena.allocator(), &diffs); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // Simple elimination - Diff.init(.delete, "abc"), - Diff.init(.insert, "b"), - }), diffs.items); - - diffs.items.len = 0; - try diffs.appendSlice(arena.allocator(), &.{ - Diff.init(.delete, "ab"), - Diff.init(.equal, "cd"), - Diff.init(.delete, "e"), - Diff.init(.equal, "f"), - Diff.init(.insert, "g"), - }); - try diffCleanupSemantic(arena.allocator(), &diffs); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // Backpass elimination - Diff.init(.delete, "abcdef"), - Diff.init(.insert, "cdfg"), - }), diffs.items); - - diffs.items.len = 0; - try diffs.appendSlice(arena.allocator(), &.{ - Diff.init(.insert, "1"), - Diff.init(.equal, "A"), - Diff.init(.delete, "B"), - Diff.init(.insert, "2"), - Diff.init(.equal, "_"), - Diff.init(.insert, "1"), - Diff.init(.equal, "A"), - Diff.init(.delete, "B"), - Diff.init(.insert, "2"), - }); - try diffCleanupSemantic(arena.allocator(), &diffs); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // Multiple elimination - Diff.init(.delete, "AB_AB"), - Diff.init(.insert, "1A2_1A2"), - }), diffs.items); - - diffs.items.len = 0; - try diffs.appendSlice(arena.allocator(), &.{ - Diff.init(.equal, "The c"), - Diff.init(.delete, "ow and the c"), - Diff.init(.equal, "at."), - }); - try diffCleanupSemantic(arena.allocator(), &diffs); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // Word boundaries - Diff.init(.equal, "The "), - Diff.init(.delete, "cow and the "), - Diff.init(.equal, "cat."), - }), diffs.items); - - diffs.items.len = 0; - try diffs.appendSlice(arena.allocator(), &.{ - Diff.init(.delete, "abcxx"), - Diff.init(.insert, "xxdef"), - }); - try diffCleanupSemantic(arena.allocator(), &diffs); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // No overlap elimination - Diff.init(.delete, "abcxx"), - Diff.init(.insert, "xxdef"), - }), diffs.items); - - diffs.items.len = 0; - try diffs.appendSlice(arena.allocator(), &.{ - Diff.init(.delete, "abcxxx"), - Diff.init(.insert, "xxxdef"), - }); - try diffCleanupSemantic(arena.allocator(), &diffs); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // Overlap elimination - Diff.init(.delete, "abc"), - Diff.init(.equal, "xxx"), - Diff.init(.insert, "def"), - }), diffs.items); - - diffs.items.len = 0; - try diffs.appendSlice(arena.allocator(), &.{ - Diff.init(.delete, "xxxabc"), - Diff.init(.insert, "defxxx"), - }); - try diffCleanupSemantic(arena.allocator(), &diffs); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // Reverse overlap elimination - Diff.init(.insert, "def"), - Diff.init(.equal, "xxx"), - Diff.init(.delete, "abc"), - }), diffs.items); - - diffs.items.len = 0; - try diffs.appendSlice(arena.allocator(), &.{ - Diff.init(.delete, "abcd1212"), - Diff.init(.insert, "1212efghi"), - Diff.init(.equal, "----"), - Diff.init(.delete, "A3"), - Diff.init(.insert, "3BC"), - }); - try diffCleanupSemantic(arena.allocator(), &diffs); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // Two overlap eliminations - Diff.init(.delete, "abcd"), - Diff.init(.equal, "1212"), - Diff.init(.insert, "efghi"), - Diff.init(.equal, "----"), - Diff.init(.delete, "A"), - Diff.init(.equal, "3"), - Diff.init(.insert, "BC"), - }), diffs.items); + if (false) { + var arena = std.heap.ArenaAllocator.init(talloc); + defer arena.deinit(); + + // Cleanup semantically trivial equalities. + // Null case. + var diffs = DiffList{}; + defer diffs.deinit(arena.allocator()); + // var this = default; + try diffCleanupSemantic(arena.allocator(), &diffs); + try testing.expectEqual(@as(usize, 0), diffs.items.len); // Null case + + diffs.items.len = 0; + try diffs.appendSlice(arena.allocator(), &.{ + Diff.init(.delete, "ab"), + Diff.init(.insert, "cd"), + Diff.init(.equal, "12"), + Diff.init(.delete, "e"), + }); + try diffCleanupSemantic(arena.allocator(), &diffs); + try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // No elimination #1 + Diff.init(.delete, "ab"), + Diff.init(.insert, "cd"), + Diff.init(.equal, "12"), + Diff.init(.delete, "e"), + }), diffs.items); + + diffs.items.len = 0; + try diffs.appendSlice(arena.allocator(), &.{ + Diff.init(.delete, "abc"), + Diff.init(.insert, "ABC"), + Diff.init(.equal, "1234"), + Diff.init(.delete, "wxyz"), + }); + try diffCleanupSemantic(arena.allocator(), &diffs); + try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // No elimination #2 + Diff.init(.delete, "abc"), + Diff.init(.insert, "ABC"), + Diff.init(.equal, "1234"), + Diff.init(.delete, "wxyz"), + }), diffs.items); + + diffs.items.len = 0; + try diffs.appendSlice(arena.allocator(), &.{ + Diff.init(.delete, "a"), + Diff.init(.equal, "b"), + Diff.init(.delete, "c"), + }); + try diffCleanupSemantic(arena.allocator(), &diffs); + try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // Simple elimination + Diff.init(.delete, "abc"), + Diff.init(.insert, "b"), + }), diffs.items); + + diffs.items.len = 0; + try diffs.appendSlice(arena.allocator(), &.{ + Diff.init(.delete, "ab"), + Diff.init(.equal, "cd"), + Diff.init(.delete, "e"), + Diff.init(.equal, "f"), + Diff.init(.insert, "g"), + }); + try diffCleanupSemantic(arena.allocator(), &diffs); + try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // Backpass elimination + Diff.init(.delete, "abcdef"), + Diff.init(.insert, "cdfg"), + }), diffs.items); + + diffs.items.len = 0; + try diffs.appendSlice(arena.allocator(), &.{ + Diff.init(.insert, "1"), + Diff.init(.equal, "A"), + Diff.init(.delete, "B"), + Diff.init(.insert, "2"), + Diff.init(.equal, "_"), + Diff.init(.insert, "1"), + Diff.init(.equal, "A"), + Diff.init(.delete, "B"), + Diff.init(.insert, "2"), + }); + try diffCleanupSemantic(arena.allocator(), &diffs); + try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // Multiple elimination + Diff.init(.delete, "AB_AB"), + Diff.init(.insert, "1A2_1A2"), + }), diffs.items); + + diffs.items.len = 0; + try diffs.appendSlice(arena.allocator(), &.{ + Diff.init(.equal, "The c"), + Diff.init(.delete, "ow and the c"), + Diff.init(.equal, "at."), + }); + try diffCleanupSemantic(arena.allocator(), &diffs); + try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // Word boundaries + Diff.init(.equal, "The "), + Diff.init(.delete, "cow and the "), + Diff.init(.equal, "cat."), + }), diffs.items); + + diffs.items.len = 0; + try diffs.appendSlice(arena.allocator(), &.{ + Diff.init(.delete, "abcxx"), + Diff.init(.insert, "xxdef"), + }); + try diffCleanupSemantic(arena.allocator(), &diffs); + try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // No overlap elimination + Diff.init(.delete, "abcxx"), + Diff.init(.insert, "xxdef"), + }), diffs.items); + + diffs.items.len = 0; + try diffs.appendSlice(arena.allocator(), &.{ + Diff.init(.delete, "abcxxx"), + Diff.init(.insert, "xxxdef"), + }); + try diffCleanupSemantic(arena.allocator(), &diffs); + try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // Overlap elimination + Diff.init(.delete, "abc"), + Diff.init(.equal, "xxx"), + Diff.init(.insert, "def"), + }), diffs.items); + + diffs.items.len = 0; + try diffs.appendSlice(arena.allocator(), &.{ + Diff.init(.delete, "xxxabc"), + Diff.init(.insert, "defxxx"), + }); + try diffCleanupSemantic(arena.allocator(), &diffs); + try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // Reverse overlap elimination + Diff.init(.insert, "def"), + Diff.init(.equal, "xxx"), + Diff.init(.delete, "abc"), + }), diffs.items); + + diffs.items.len = 0; + try diffs.appendSlice(arena.allocator(), &.{ + Diff.init(.delete, "abcd1212"), + Diff.init(.insert, "1212efghi"), + Diff.init(.equal, "----"), + Diff.init(.delete, "A3"), + Diff.init(.insert, "3BC"), + }); + try diffCleanupSemantic(arena.allocator(), &diffs); + try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // Two overlap eliminations + Diff.init(.delete, "abcd"), + Diff.init(.equal, "1212"), + Diff.init(.insert, "efghi"), + Diff.init(.equal, "----"), + Diff.init(.delete, "A"), + Diff.init(.equal, "3"), + Diff.init(.insert, "BC"), + }), diffs.items); + } } From dbc3a84e6935b6d43be0396a06194e18e87fa5fe Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Tue, 2 Jul 2024 17:28:19 -0400 Subject: [PATCH 10/60] More tests Found a double free in the falsed-out one, so that's next up... --- DiffMatchPatch.zig | 155 ++++++++++++++++++++++++++++----------------- 1 file changed, 96 insertions(+), 59 deletions(-) diff --git a/DiffMatchPatch.zig b/DiffMatchPatch.zig index 0f0ead8..e9ab5d1 100644 --- a/DiffMatchPatch.zig +++ b/DiffMatchPatch.zig @@ -1702,56 +1702,93 @@ test diffCleanupMerge { try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ .{ .operation = .equal, .text = "abc" }, }), diffs2.items); // Merge equalities + + var diffs3 = DiffList{}; + defer deinitDiffList(alloc, &diffs3); + + try diffs3.appendSlice(alloc, &[_]Diff{ + .{ + .operation = .delete, + .text = try alloc.dupe(u8, "a"), + }, + .{ + .operation = .delete, + .text = try alloc.dupe(u8, "b"), + }, + .{ + .operation = .delete, + .text = try alloc.dupe(u8, "c"), + }, + }); + try diffCleanupMerge(alloc, &diffs3); + try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ + .{ .operation = .delete, .text = "abc" }, + }), diffs3.items); // Merge deletions + + var diffs4 = DiffList{}; + defer deinitDiffList(alloc, &diffs4); + try diffs4.appendSlice(alloc, &[_]Diff{ + .{ + .operation = .insert, + .text = try alloc.dupe(u8, "a"), + }, + .{ + .operation = .insert, + .text = try alloc.dupe(u8, "b"), + }, + .{ + .operation = .insert, + .text = try alloc.dupe(u8, "c"), + }, + }); + try diffCleanupMerge(alloc, &diffs4); + try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ + .{ .operation = .insert, .text = "abc" }, + }), diffs4.items); // Merge insertions + + if (false) { + var diffs5 = DiffList{}; + defer deinitDiffList(alloc, &diffs5); + try diffs5.appendSlice(alloc, &[_]Diff{ + .{ + .operation = .delete, + .text = try alloc.dupe(u8, "a"), + }, + .{ + .operation = .insert, + .text = try alloc.dupe(u8, "b"), + }, + .{ + .operation = .delete, + .text = try alloc.dupe(u8, "c"), + }, + .{ + .operation = .insert, + .text = try alloc.dupe(u8, "d"), + }, + .{ + .operation = .equal, + .text = try alloc.dupe(u8, "e"), + }, + .{ + .operation = .equal, + .text = try alloc.dupe(u8, "f"), + }, + }); + try diffCleanupMerge(alloc, &diffs5); + try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ + .{ .operation = .delete, .text = "ac" }, + .{ .operation = .insert, .text = "bd" }, + .{ .operation = .equal, .text = "ef" }, + }), diffs5.items); // Merge interweave + } // - // diffs2.items.len = 0; - // - // try diffs2.appendSlice(arena.allocator(), &[_]Diff{ - // .{ .operation = .delete, .text = "a" }, - // .{ .operation = .delete, .text = "b" }, - // .{ .operation = .delete, .text = "c" }, - // }); - // try diffCleanupMerge(arena.allocator(), &diffs); - // try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ - // .{ .operation = .delete, .text = "abc" }, - // }), diffs2.items); // Merge deletions - // - // diffs2.items.len = 0; - // - // try diffs2.appendSlice(arena.allocator(), &[_]Diff{ - // .{ .operation = .insert, .text = "a" }, - // .{ .operation = .insert, .text = "b" }, - // .{ .operation = .insert, .text = "c" }, - // }); - // try diffCleanupMerge(arena.allocator(), &diffs); - // try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ - // .{ .operation = .insert, .text = "abc" }, - // }), diffs2.items); // Merge insertions - // - // diffs2.items.len = 0; - // - // try diffs2.appendSlice(arena.allocator(), &[_]Diff{ - // .{ .operation = .delete, .text = "a" }, - // .{ .operation = .insert, .text = "b" }, - // .{ .operation = .delete, .text = "c" }, - // .{ .operation = .insert, .text = "d" }, - // .{ .operation = .equal, .text = "e" }, - // .{ .operation = .equal, .text = "f" }, - // }); - // try diffCleanupMerge(arena.allocator(), &diffs); - // try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ - // .{ .operation = .delete, .text = "ac" }, - // .{ .operation = .insert, .text = "bd" }, - // .{ .operation = .equal, .text = "ef" }, - // }), diffs2.items); // Merge interweave - // - // diffs2.items.len = 0; - // - // try diffs2.appendSlice(arena.allocator(), &[_]Diff{ + // try diffs2.appendSlice(alloc, &[_]Diff{ // .{ .operation = .delete, .text = "a" }, // .{ .operation = .insert, .text = "abc" }, // .{ .operation = .delete, .text = "dc" }, // }); - // try diffCleanupMerge(arena.allocator(), &diffs); + // try diffCleanupMerge(alloc, &diffs2); // try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // .{ .operation = .equal, .text = "a" }, // .{ .operation = .delete, .text = "d" }, @@ -1761,14 +1798,14 @@ test diffCleanupMerge { // // diffs2.items.len = 0; // - // try diffs2.appendSlice(arena.allocator(), &[_]Diff{ + // try diffs2.appendSlice(alloc, &[_]Diff{ // .{ .operation = .equal, .text = "x" }, // .{ .operation = .delete, .text = "a" }, // .{ .operation = .insert, .text = "abc" }, // .{ .operation = .delete, .text = "dc" }, // .{ .operation = .equal, .text = "y" }, // }); - // try diffCleanupMerge(arena.allocator(), &diffs); + // try diffCleanupMerge(alloc, &diffs2); // try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // .{ .operation = .equal, .text = "xa" }, // .{ .operation = .delete, .text = "d" }, @@ -1778,12 +1815,12 @@ test diffCleanupMerge { // // diffs2.items.len = 0; // - // try diffs2.appendSlice(arena.allocator(), &[_]Diff{ + // try diffs2.appendSlice(alloc, &[_]Diff{ // .{ .operation = .equal, .text = "a" }, // .{ .operation = .insert, .text = "ba" }, // .{ .operation = .equal, .text = "c" }, // }); - // try diffCleanupMerge(arena.allocator(), &diffs); + // try diffCleanupMerge(alloc, &diffs2); // try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // .{ .operation = .insert, .text = "ab" }, // .{ .operation = .equal, .text = "ac" }, @@ -1791,12 +1828,12 @@ test diffCleanupMerge { // // diffs2.items.len = 0; // - // try diffs2.appendSlice(arena.allocator(), &[_]Diff{ + // try diffs2.appendSlice(alloc, &[_]Diff{ // .{ .operation = .equal, .text = "c" }, // .{ .operation = .insert, .text = "ab" }, // .{ .operation = .equal, .text = "a" }, // }); - // try diffCleanupMerge(arena.allocator(), &diffs); + // try diffCleanupMerge(alloc, &diffs2); // try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // .{ .operation = .equal, .text = "ca" }, // .{ .operation = .insert, .text = "ba" }, @@ -1804,14 +1841,14 @@ test diffCleanupMerge { // // diffs2.items.len = 0; // - // try diffs2.appendSlice(arena.allocator(), &[_]Diff{ + // try diffs2.appendSlice(alloc, &[_]Diff{ // Diff.init(.equal, "a"), // Diff.init(.delete, "b"), // Diff.init(.equal, "c"), // Diff.init(.delete, "ac"), // Diff.init(.equal, "x"), // }); - // try diffCleanupMerge(arena.allocator(), &diffs); + // try diffCleanupMerge(alloc, &diffs2); // try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // Diff.init(.delete, "abc"), // Diff.init(.equal, "acx"), @@ -1819,14 +1856,14 @@ test diffCleanupMerge { // // diffs2.items.len = 0; // - // try diffs2.appendSlice(arena.allocator(), &[_]Diff{ + // try diffs2.appendSlice(alloc, &[_]Diff{ // Diff.init(.equal, "x"), // Diff.init(.delete, "ca"), // Diff.init(.equal, "c"), // Diff.init(.delete, "b"), // Diff.init(.equal, "a"), // }); - // try diffCleanupMerge(arena.allocator(), &diffs); + // try diffCleanupMerge(alloc, &diffs2); // try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // Diff.init(.equal, "xca"), // Diff.init(.delete, "cba"), @@ -1834,12 +1871,12 @@ test diffCleanupMerge { // // diffs2.items.len = 0; // - // try diffs2.appendSlice(arena.allocator(), &[_]Diff{ + // try diffs2.appendSlice(alloc, &[_]Diff{ // Diff.init(.delete, "b"), // Diff.init(.insert, "ab"), // Diff.init(.equal, "c"), // }); - // try diffCleanupMerge(arena.allocator(), &diffs); + // try diffCleanupMerge(alloc, &diffs2); // try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // Diff.init(.insert, "a"), // Diff.init(.equal, "bc"), @@ -1847,12 +1884,12 @@ test diffCleanupMerge { // // diffs2.items.len = 0; // - // try diffs2.appendSlice(arena.allocator(), &[_]Diff{ + // try diffs2.appendSlice(alloc, &[_]Diff{ // Diff.init(.equal, ""), // Diff.init(.insert, "a"), // Diff.init(.equal, "b"), // }); - // try diffCleanupMerge(arena.allocator(), &diffs); + // try diffCleanupMerge(alloc, &diffs2); // try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // Diff.init(.insert, "a"), // Diff.init(.equal, "b"), From 0b2f274e99418fbcd4587f0d8c45074d5db4cfc2 Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Tue, 2 Jul 2024 18:05:58 -0400 Subject: [PATCH 11/60] Fix two double-frees Also changing the use of .replaceRange, which lead me to think that the item at the pointer location was being, y'know, replaced, with a use of .insert, which is,, correct. --- DiffMatchPatch.zig | 88 ++++++++++++++++++++++------------------------ 1 file changed, 43 insertions(+), 45 deletions(-) diff --git a/DiffMatchPatch.zig b/DiffMatchPatch.zig index e9ab5d1..5d764e7 100644 --- a/DiffMatchPatch.zig +++ b/DiffMatchPatch.zig @@ -858,17 +858,17 @@ fn diffCleanupMerge(allocator: std.mem.Allocator, diffs: *DiffList) DiffError!vo } if (text_delete.items.len != 0) { - allocator.free(diffs.items[pointer].text); - try diffs.replaceRange(allocator, pointer, 0, &.{ - Diff.init(.delete, try allocator.dupe(u8, text_delete.items)), - }); + try diffs.insert(allocator, pointer, Diff.init( + .delete, + try allocator.dupe(u8, text_delete.items), + )); pointer += 1; } if (text_insert.items.len != 0) { - allocator.free(diffs.items[pointer].text); - try diffs.replaceRange(allocator, pointer, 0, &.{ - Diff.init(.insert, try allocator.dupe(u8, text_insert.items)), - }); + try diffs.insert(allocator, pointer, Diff.init( + .insert, + try allocator.dupe(u8, text_insert.items), + )); pointer += 1; } pointer += 1; @@ -934,7 +934,7 @@ fn diffCleanupMerge(allocator: std.mem.Allocator, diffs: *DiffList) DiffError!vo }); diffs.items[pointer - 1].text = pm1t; diffs.items[pointer].text = pt; - freeRangeDiffList(allocator, diffs, pointer - 1, 1); + freeRangeDiffList(allocator, diffs, pointer + 1, 1); try diffs.replaceRange(allocator, pointer + 1, 1, &.{}); changes = true; } @@ -1746,42 +1746,40 @@ test diffCleanupMerge { .{ .operation = .insert, .text = "abc" }, }), diffs4.items); // Merge insertions - if (false) { - var diffs5 = DiffList{}; - defer deinitDiffList(alloc, &diffs5); - try diffs5.appendSlice(alloc, &[_]Diff{ - .{ - .operation = .delete, - .text = try alloc.dupe(u8, "a"), - }, - .{ - .operation = .insert, - .text = try alloc.dupe(u8, "b"), - }, - .{ - .operation = .delete, - .text = try alloc.dupe(u8, "c"), - }, - .{ - .operation = .insert, - .text = try alloc.dupe(u8, "d"), - }, - .{ - .operation = .equal, - .text = try alloc.dupe(u8, "e"), - }, - .{ - .operation = .equal, - .text = try alloc.dupe(u8, "f"), - }, - }); - try diffCleanupMerge(alloc, &diffs5); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ - .{ .operation = .delete, .text = "ac" }, - .{ .operation = .insert, .text = "bd" }, - .{ .operation = .equal, .text = "ef" }, - }), diffs5.items); // Merge interweave - } + var diffs5 = DiffList{}; + defer deinitDiffList(alloc, &diffs5); + try diffs5.appendSlice(alloc, &[_]Diff{ + .{ + .operation = .delete, + .text = try alloc.dupe(u8, "a"), + }, + .{ + .operation = .insert, + .text = try alloc.dupe(u8, "b"), + }, + .{ + .operation = .delete, + .text = try alloc.dupe(u8, "c"), + }, + .{ + .operation = .insert, + .text = try alloc.dupe(u8, "d"), + }, + .{ + .operation = .equal, + .text = try alloc.dupe(u8, "e"), + }, + .{ + .operation = .equal, + .text = try alloc.dupe(u8, "f"), + }, + }); + try diffCleanupMerge(alloc, &diffs5); + try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ + .{ .operation = .delete, .text = "ac" }, + .{ .operation = .insert, .text = "bd" }, + .{ .operation = .equal, .text = "ef" }, + }), diffs5.items); // Merge interweave // // try diffs2.appendSlice(alloc, &[_]Diff{ // .{ .operation = .delete, .text = "a" }, From 18b75311d281146a1becf905c9bea8a2890a8bbf Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Tue, 2 Jul 2024 18:21:30 -0400 Subject: [PATCH 12/60] Free two more clobbered diff texts --- DiffMatchPatch.zig | 130 ++++++++++++++++++++++++++++++--------------- 1 file changed, 87 insertions(+), 43 deletions(-) diff --git a/DiffMatchPatch.zig b/DiffMatchPatch.zig index 5d764e7..73f02d5 100644 --- a/DiffMatchPatch.zig +++ b/DiffMatchPatch.zig @@ -918,6 +918,10 @@ fn diffCleanupMerge(allocator: std.mem.Allocator, diffs: *DiffList) DiffError!vo diffs.items[pointer - 1].text, diffs.items[pointer + 1].text, }); + const old_pt = diffs.items[pointer].text; + defer allocator.free(old_pt); + const old_pt1t = diffs.items[pointer + 1].text; + defer allocator.free(old_pt1t); diffs.items[pointer].text = pt; diffs.items[pointer + 1].text = p1t; freeRangeDiffList(allocator, diffs, pointer - 1, 1); @@ -932,6 +936,10 @@ fn diffCleanupMerge(allocator: std.mem.Allocator, diffs: *DiffList) DiffError!vo diffs.items[pointer].text[diffs.items[pointer + 1].text.len..], diffs.items[pointer + 1].text, }); + const old_ptm1 = diffs.items[pointer - 1].text; + defer allocator.free(old_ptm1); + const old_pt = diffs.items[pointer].text; + defer allocator.free(old_pt); diffs.items[pointer - 1].text = pm1t; diffs.items[pointer].text = pt; freeRangeDiffList(allocator, diffs, pointer + 1, 1); @@ -1780,49 +1788,85 @@ test diffCleanupMerge { .{ .operation = .insert, .text = "bd" }, .{ .operation = .equal, .text = "ef" }, }), diffs5.items); // Merge interweave - // - // try diffs2.appendSlice(alloc, &[_]Diff{ - // .{ .operation = .delete, .text = "a" }, - // .{ .operation = .insert, .text = "abc" }, - // .{ .operation = .delete, .text = "dc" }, - // }); - // try diffCleanupMerge(alloc, &diffs2); - // try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ - // .{ .operation = .equal, .text = "a" }, - // .{ .operation = .delete, .text = "d" }, - // .{ .operation = .insert, .text = "b" }, - // .{ .operation = .equal, .text = "c" }, - // }), diffs2.items); // Prefix and suffix detection - // - // diffs2.items.len = 0; - // - // try diffs2.appendSlice(alloc, &[_]Diff{ - // .{ .operation = .equal, .text = "x" }, - // .{ .operation = .delete, .text = "a" }, - // .{ .operation = .insert, .text = "abc" }, - // .{ .operation = .delete, .text = "dc" }, - // .{ .operation = .equal, .text = "y" }, - // }); - // try diffCleanupMerge(alloc, &diffs2); - // try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ - // .{ .operation = .equal, .text = "xa" }, - // .{ .operation = .delete, .text = "d" }, - // .{ .operation = .insert, .text = "b" }, - // .{ .operation = .equal, .text = "cy" }, - // }), diffs2.items); // Prefix and suffix detection with equalities - // - // diffs2.items.len = 0; - // - // try diffs2.appendSlice(alloc, &[_]Diff{ - // .{ .operation = .equal, .text = "a" }, - // .{ .operation = .insert, .text = "ba" }, - // .{ .operation = .equal, .text = "c" }, - // }); - // try diffCleanupMerge(alloc, &diffs2); - // try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ - // .{ .operation = .insert, .text = "ab" }, - // .{ .operation = .equal, .text = "ac" }, - // }), diffs2.items); // Slide edit left + + var diffs6 = DiffList{}; + defer deinitDiffList(alloc, &diffs6); + try diffs6.appendSlice(alloc, &[_]Diff{ + .{ + .operation = .delete, + .text = try alloc.dupe(u8, "a"), + }, + .{ + .operation = .insert, + .text = try alloc.dupe(u8, "abc"), + }, + .{ + .operation = .delete, + .text = try alloc.dupe(u8, "dc"), + }, + }); + try diffCleanupMerge(alloc, &diffs6); + try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ + .{ .operation = .equal, .text = "a" }, + .{ .operation = .delete, .text = "d" }, + .{ .operation = .insert, .text = "b" }, + .{ .operation = .equal, .text = "c" }, + }), diffs6.items); // Prefix and suffix detection + + var diffs7 = DiffList{}; + defer deinitDiffList(alloc, &diffs7); + + try diffs7.appendSlice(alloc, &[_]Diff{ + .{ + .operation = .equal, + .text = try alloc.dupe(u8, "x"), + }, + .{ + .operation = .delete, + .text = try alloc.dupe(u8, "a"), + }, + .{ + .operation = .insert, + .text = try alloc.dupe(u8, "abc"), + }, + .{ + .operation = .delete, + .text = try alloc.dupe(u8, "dc"), + }, + .{ + .operation = .equal, + .text = try alloc.dupe(u8, "y"), + }, + }); + try diffCleanupMerge(alloc, &diffs7); + try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ + .{ .operation = .equal, .text = "xa" }, + .{ .operation = .delete, .text = "d" }, + .{ .operation = .insert, .text = "b" }, + .{ .operation = .equal, .text = "cy" }, + }), diffs7.items); // Prefix and suffix detection with equalities + + var diffs8 = DiffList{}; + defer deinitDiffList(alloc, &diffs8); + try diffs8.appendSlice(alloc, &[_]Diff{ + .{ + .operation = .equal, + .text = try alloc.dupe(u8, "a"), + }, + .{ + .operation = .insert, + .text = try alloc.dupe(u8, "ba"), + }, + .{ + .operation = .equal, + .text = try alloc.dupe(u8, "c"), + }, + }); + try diffCleanupMerge(alloc, &diffs8); + try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ + .{ .operation = .insert, .text = "ab" }, + .{ .operation = .equal, .text = "ac" }, + }), diffs8.items); // Slide edit left // // diffs2.items.len = 0; // From 1ebee826e957823d5b91e222fec0d198339702e8 Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Tue, 2 Jul 2024 18:40:41 -0400 Subject: [PATCH 13/60] Restore all diffCleanMerge tests --- DiffMatchPatch.zig | 186 ++++++++++++++++++++++++++++----------------- 1 file changed, 117 insertions(+), 69 deletions(-) diff --git a/DiffMatchPatch.zig b/DiffMatchPatch.zig index 73f02d5..6c93bfb 100644 --- a/DiffMatchPatch.zig +++ b/DiffMatchPatch.zig @@ -1867,75 +1867,123 @@ test diffCleanupMerge { .{ .operation = .insert, .text = "ab" }, .{ .operation = .equal, .text = "ac" }, }), diffs8.items); // Slide edit left - // - // diffs2.items.len = 0; - // - // try diffs2.appendSlice(alloc, &[_]Diff{ - // .{ .operation = .equal, .text = "c" }, - // .{ .operation = .insert, .text = "ab" }, - // .{ .operation = .equal, .text = "a" }, - // }); - // try diffCleanupMerge(alloc, &diffs2); - // try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ - // .{ .operation = .equal, .text = "ca" }, - // .{ .operation = .insert, .text = "ba" }, - // }), diffs2.items); // Slide edit right - // - // diffs2.items.len = 0; - // - // try diffs2.appendSlice(alloc, &[_]Diff{ - // Diff.init(.equal, "a"), - // Diff.init(.delete, "b"), - // Diff.init(.equal, "c"), - // Diff.init(.delete, "ac"), - // Diff.init(.equal, "x"), - // }); - // try diffCleanupMerge(alloc, &diffs2); - // try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ - // Diff.init(.delete, "abc"), - // Diff.init(.equal, "acx"), - // }), diffs2.items); // Slide edit left recursive - // - // diffs2.items.len = 0; - // - // try diffs2.appendSlice(alloc, &[_]Diff{ - // Diff.init(.equal, "x"), - // Diff.init(.delete, "ca"), - // Diff.init(.equal, "c"), - // Diff.init(.delete, "b"), - // Diff.init(.equal, "a"), - // }); - // try diffCleanupMerge(alloc, &diffs2); - // try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ - // Diff.init(.equal, "xca"), - // Diff.init(.delete, "cba"), - // }), diffs2.items); // Slide edit right recursive - // - // diffs2.items.len = 0; - // - // try diffs2.appendSlice(alloc, &[_]Diff{ - // Diff.init(.delete, "b"), - // Diff.init(.insert, "ab"), - // Diff.init(.equal, "c"), - // }); - // try diffCleanupMerge(alloc, &diffs2); - // try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ - // Diff.init(.insert, "a"), - // Diff.init(.equal, "bc"), - // }), diffs2.items); // Empty merge - // - // diffs2.items.len = 0; - // - // try diffs2.appendSlice(alloc, &[_]Diff{ - // Diff.init(.equal, ""), - // Diff.init(.insert, "a"), - // Diff.init(.equal, "b"), - // }); - // try diffCleanupMerge(alloc, &diffs2); - // try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ - // Diff.init(.insert, "a"), - // Diff.init(.equal, "b"), - // }), diffs2.items); // Empty equality + + var diffs9 = DiffList{}; + defer deinitDiffList(alloc, &diffs9); + try diffs9.appendSlice(alloc, &[_]Diff{ + .{ + .operation = .equal, + .text = try alloc.dupe(u8, "c"), + }, + .{ + .operation = .insert, + .text = try alloc.dupe(u8, "ab"), + }, + .{ + .operation = .equal, + .text = try alloc.dupe(u8, "a"), + }, + }); + try diffCleanupMerge(alloc, &diffs9); + try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ + .{ .operation = .equal, .text = "ca" }, + .{ .operation = .insert, .text = "ba" }, + }), diffs9.items); // Slide edit right + + var diffs10 = DiffList{}; + defer deinitDiffList(alloc, &diffs10); + try diffs10.appendSlice(alloc, &[_]Diff{ + Diff.init( + .equal, + try alloc.dupe(u8, "a"), + ), + Diff.init( + .delete, + try alloc.dupe(u8, "b"), + ), + Diff.init( + .equal, + try alloc.dupe(u8, "c"), + ), + Diff.init( + .delete, + try alloc.dupe(u8, "ac"), + ), + Diff.init( + .equal, + try alloc.dupe(u8, "x"), + ), + }); + try diffCleanupMerge(alloc, &diffs10); + try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ + Diff.init(.delete, "abc"), + Diff.init(.equal, "acx"), + }), diffs10.items); // Slide edit left recursive + + var diffs11 = DiffList{}; + defer deinitDiffList(alloc, &diffs11); + try diffs11.appendSlice(alloc, &[_]Diff{ + Diff.init( + .equal, + try alloc.dupe(u8, "x"), + ), + Diff.init( + .delete, + try alloc.dupe(u8, "ca"), + ), + Diff.init( + .equal, + try alloc.dupe(u8, "c"), + ), + Diff.init( + .delete, + try alloc.dupe(u8, "b"), + ), + Diff.init( + .equal, + try alloc.dupe(u8, "a"), + ), + }); + try diffCleanupMerge(alloc, &diffs11); + try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ + Diff.init(.equal, "xca"), + Diff.init(.delete, "cba"), + }), diffs11.items); // Slide edit right recursive + + var diffs12 = DiffList{}; + defer deinitDiffList(alloc, &diffs12); + try diffs12.appendSlice(alloc, &[_]Diff{ + Diff.init( + .delete, + try alloc.dupe(u8, "b"), + ), + Diff.init( + .insert, + try alloc.dupe(u8, "ab"), + ), + Diff.init( + .equal, + try alloc.dupe(u8, "c"), + ), + }); + try diffCleanupMerge(alloc, &diffs12); + try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ + Diff.init(.insert, "a"), + Diff.init(.equal, "bc"), + }), diffs12.items); // Empty merge + + var diffs13 = DiffList{}; + defer deinitDiffList(alloc, &diffs13); + try diffs13.appendSlice(alloc, &[_]Diff{ + Diff.init(.equal, ""), + Diff.init(.insert, try alloc.dupe(u8, "a")), + Diff.init(.equal, try alloc.dupe(u8, "b")), + }); + try diffCleanupMerge(alloc, &diffs13); + try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ + Diff.init(.insert, "a"), + Diff.init(.equal, "b"), + }), diffs13.items); // Empty equality } test diffCleanupSemanticLossless { From 350a85e4817624d219eb4536f75bdc88bd7e9cd7 Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Tue, 2 Jul 2024 18:50:36 -0400 Subject: [PATCH 14/60] Restore frees to diffCleanupSemanticLossless --- DiffMatchPatch.zig | 77 ++++++++++++++++++++++------------------------ 1 file changed, 37 insertions(+), 40 deletions(-) diff --git a/DiffMatchPatch.zig b/DiffMatchPatch.zig index 6c93bfb..a3ea385 100644 --- a/DiffMatchPatch.zig +++ b/DiffMatchPatch.zig @@ -1176,23 +1176,21 @@ pub fn diffCleanupSemanticLossless( if (!std.mem.eql(u8, diffs.items[pointer - 1].text, best_equality_1.items)) { // We have an improvement, save it back to the diff. if (best_equality_1.items.len != 0) { - // allocator.free(diffs.items[pointer - 1].text); + allocator.free(diffs.items[pointer - 1].text); diffs.items[pointer - 1].text = try allocator.dupe(u8, best_equality_1.items); } else { const old_diff = diffs.orderedRemove(pointer - 1); - // allocator.free(old_diff.text); - _ = old_diff; + allocator.free(old_diff.text); pointer -= 1; } - // allocator.free(diffs.items[pointer].text); + allocator.free(diffs.items[pointer].text); diffs.items[pointer].text = try allocator.dupe(u8, best_edit.items); if (best_equality_2.items.len != 0) { - // allocator.free(diffs.items[pointer - 1].text); + allocator.free(diffs.items[pointer + 1].text); diffs.items[pointer + 1].text = try allocator.dupe(u8, best_equality_2.items); } else { const old_diff = diffs.orderedRemove(pointer + 1); - // allocator.free(old_diff.text); - _ = old_diff; + allocator.free(old_diff.text); pointer -= 1; } } @@ -1987,36 +1985,35 @@ test diffCleanupMerge { } test diffCleanupSemanticLossless { - if (false) { - var arena = std.heap.ArenaAllocator.init(testing.allocator); - defer arena.deinit(); - - var diffs = DiffList{}; - try diffCleanupSemanticLossless(arena.allocator(), &diffs); - try testing.expectEqualDeep(@as([]const Diff, &[0]Diff{}), diffs.items); // Null case - - diffs.items.len = 0; + var arena = std.heap.ArenaAllocator.init(testing.allocator); + defer arena.deinit(); - try diffs.appendSlice(arena.allocator(), &.{ - Diff.init(.equal, "AAA\r\n\r\nBBB"), - Diff.init(.insert, "\r\nDDD\r\n\r\nBBB"), - Diff.init(.equal, "\r\nEEE"), - }); - try diffCleanupSemanticLossless(arena.allocator(), &diffs); - try testing.expectEqualDeep(@as([]const Diff, &.{ - Diff.init(.equal, "AAA\r\n\r\n"), - Diff.init(.insert, "BBB\r\nDDD\r\n\r\n"), - Diff.init(.equal, "BBB\r\nEEE"), - }), diffs.items); + const alloc = std.testing.allocator; + var diffs = DiffList{}; + try diffCleanupSemanticLossless(alloc, &diffs); + try testing.expectEqualDeep(@as([]const Diff, &[0]Diff{}), diffs.items); // Null case - diffs.items.len = 0; + var diffs2 = DiffList{}; + defer deinitDiffList(alloc, &diffs2); + try diffs2.appendSlice(alloc, &.{ + Diff.init(.equal, try alloc.dupe(u8, "AAA\r\n\r\nBBB")), + Diff.init(.insert, try alloc.dupe(u8, "\r\nDDD\r\n\r\nBBB")), + Diff.init(.equal, try alloc.dupe(u8, "\r\nEEE")), + }); + try diffCleanupSemanticLossless(alloc, &diffs2); + try testing.expectEqualDeep(@as([]const Diff, &.{ + Diff.init(.equal, "AAA\r\n\r\n"), + Diff.init(.insert, "BBB\r\nDDD\r\n\r\n"), + Diff.init(.equal, "BBB\r\nEEE"), + }), diffs2.items); - try diffs.appendSlice(arena.allocator(), &.{ + if (false) { + try diffs.appendSlice(alloc, &.{ Diff.init(.equal, "AAA\r\nBBB"), Diff.init(.insert, " DDD\r\nBBB"), Diff.init(.equal, " EEE"), }); - try diffCleanupSemanticLossless(arena.allocator(), &diffs); + try diffCleanupSemanticLossless(alloc, &diffs); try testing.expectEqualDeep(@as([]const Diff, &.{ Diff.init(.equal, "AAA\r\n"), Diff.init(.insert, "BBB DDD\r\n"), @@ -2025,12 +2022,12 @@ test diffCleanupSemanticLossless { diffs.items.len = 0; - try diffs.appendSlice(arena.allocator(), &.{ + try diffs.appendSlice(alloc, &.{ Diff.init(.equal, "The c"), Diff.init(.insert, "ow and the c"), Diff.init(.equal, "at."), }); - try diffCleanupSemanticLossless(arena.allocator(), &diffs); + try diffCleanupSemanticLossless(alloc, &diffs); try testing.expectEqualDeep(@as([]const Diff, &.{ Diff.init(.equal, "The "), Diff.init(.insert, "cow and the "), @@ -2039,12 +2036,12 @@ test diffCleanupSemanticLossless { diffs.items.len = 0; - try diffs.appendSlice(arena.allocator(), &.{ + try diffs.appendSlice(alloc, &.{ Diff.init(.equal, "The-c"), Diff.init(.insert, "ow-and-the-c"), Diff.init(.equal, "at."), }); - try diffCleanupSemanticLossless(arena.allocator(), &diffs); + try diffCleanupSemanticLossless(alloc, &diffs); try testing.expectEqualDeep(@as([]const Diff, &.{ Diff.init(.equal, "The-"), Diff.init(.insert, "cow-and-the-"), @@ -2053,12 +2050,12 @@ test diffCleanupSemanticLossless { diffs.items.len = 0; - try diffs.appendSlice(arena.allocator(), &.{ + try diffs.appendSlice(alloc, &.{ Diff.init(.equal, "a"), Diff.init(.delete, "a"), Diff.init(.equal, "ax"), }); - try diffCleanupSemanticLossless(arena.allocator(), &diffs); + try diffCleanupSemanticLossless(alloc, &diffs); try testing.expectEqualDeep(@as([]const Diff, &.{ Diff.init(.delete, "a"), Diff.init(.equal, "aax"), @@ -2066,12 +2063,12 @@ test diffCleanupSemanticLossless { diffs.items.len = 0; - try diffs.appendSlice(arena.allocator(), &.{ + try diffs.appendSlice(alloc, &.{ Diff.init(.equal, "xa"), Diff.init(.delete, "a"), Diff.init(.equal, "a"), }); - try diffCleanupSemanticLossless(arena.allocator(), &diffs); + try diffCleanupSemanticLossless(alloc, &diffs); try testing.expectEqualDeep(@as([]const Diff, &.{ Diff.init(.equal, "xaa"), Diff.init(.delete, "a"), @@ -2079,12 +2076,12 @@ test diffCleanupSemanticLossless { diffs.items.len = 0; - try diffs.appendSlice(arena.allocator(), &.{ + try diffs.appendSlice(alloc, &.{ Diff.init(.equal, "The xxx. The "), Diff.init(.insert, "zzz. The "), Diff.init(.equal, "yyy."), }); - try diffCleanupSemanticLossless(arena.allocator(), &diffs); + try diffCleanupSemanticLossless(alloc, &diffs); try testing.expectEqualDeep(@as([]const Diff, &.{ Diff.init(.equal, "The xxx."), Diff.init(.insert, " The zzz."), From fe21b517ae642b47e55e8173f88fdb9e993c00fb Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Tue, 2 Jul 2024 18:59:18 -0400 Subject: [PATCH 15/60] Tests of diffCleanupSemanticLossless pass --- DiffMatchPatch.zig | 152 ++++++++++++++++++++++----------------------- 1 file changed, 76 insertions(+), 76 deletions(-) diff --git a/DiffMatchPatch.zig b/DiffMatchPatch.zig index a3ea385..5b46824 100644 --- a/DiffMatchPatch.zig +++ b/DiffMatchPatch.zig @@ -2007,87 +2007,87 @@ test diffCleanupSemanticLossless { Diff.init(.equal, "BBB\r\nEEE"), }), diffs2.items); - if (false) { - try diffs.appendSlice(alloc, &.{ - Diff.init(.equal, "AAA\r\nBBB"), - Diff.init(.insert, " DDD\r\nBBB"), - Diff.init(.equal, " EEE"), - }); - try diffCleanupSemanticLossless(alloc, &diffs); - try testing.expectEqualDeep(@as([]const Diff, &.{ - Diff.init(.equal, "AAA\r\n"), - Diff.init(.insert, "BBB DDD\r\n"), - Diff.init(.equal, "BBB EEE"), - }), diffs.items); - - diffs.items.len = 0; - - try diffs.appendSlice(alloc, &.{ - Diff.init(.equal, "The c"), - Diff.init(.insert, "ow and the c"), - Diff.init(.equal, "at."), - }); - try diffCleanupSemanticLossless(alloc, &diffs); - try testing.expectEqualDeep(@as([]const Diff, &.{ - Diff.init(.equal, "The "), - Diff.init(.insert, "cow and the "), - Diff.init(.equal, "cat."), - }), diffs.items); - - diffs.items.len = 0; - - try diffs.appendSlice(alloc, &.{ - Diff.init(.equal, "The-c"), - Diff.init(.insert, "ow-and-the-c"), - Diff.init(.equal, "at."), - }); - try diffCleanupSemanticLossless(alloc, &diffs); - try testing.expectEqualDeep(@as([]const Diff, &.{ - Diff.init(.equal, "The-"), - Diff.init(.insert, "cow-and-the-"), - Diff.init(.equal, "cat."), - }), diffs.items); - - diffs.items.len = 0; + var diffs3 = DiffList{}; + defer deinitDiffList(alloc, &diffs3); + try diffs3.appendSlice(alloc, &.{ + Diff.init(.equal, try alloc.dupe(u8, "AAA\r\nBBB")), + Diff.init(.insert, try alloc.dupe(u8, " DDD\r\nBBB")), + Diff.init(.equal, try alloc.dupe(u8, " EEE")), + }); + try diffCleanupSemanticLossless(alloc, &diffs3); + try testing.expectEqualDeep(@as([]const Diff, &.{ + Diff.init(.equal, "AAA\r\n"), + Diff.init(.insert, "BBB DDD\r\n"), + Diff.init(.equal, "BBB EEE"), + }), diffs3.items); - try diffs.appendSlice(alloc, &.{ - Diff.init(.equal, "a"), - Diff.init(.delete, "a"), - Diff.init(.equal, "ax"), - }); - try diffCleanupSemanticLossless(alloc, &diffs); - try testing.expectEqualDeep(@as([]const Diff, &.{ - Diff.init(.delete, "a"), - Diff.init(.equal, "aax"), - }), diffs.items); + var diffs4 = DiffList{}; + defer deinitDiffList(alloc, &diffs4); + try diffs4.appendSlice(alloc, &.{ + Diff.init(.equal, try alloc.dupe(u8, "The c")), + Diff.init(.insert, try alloc.dupe(u8, "ow and the c")), + Diff.init(.equal, try alloc.dupe(u8, "at.")), + }); + try diffCleanupSemanticLossless(alloc, &diffs4); + try testing.expectEqualDeep(@as([]const Diff, &.{ + Diff.init(.equal, "The "), + Diff.init(.insert, "cow and the "), + Diff.init(.equal, "cat."), + }), diffs4.items); - diffs.items.len = 0; + var diffs5 = DiffList{}; + defer deinitDiffList(alloc, &diffs5); + try diffs5.appendSlice(alloc, &.{ + Diff.init(.equal, try alloc.dupe(u8, "The-c")), + Diff.init(.insert, try alloc.dupe(u8, "ow-and-the-c")), + Diff.init(.equal, try alloc.dupe(u8, "at.")), + }); + try diffCleanupSemanticLossless(alloc, &diffs5); + try testing.expectEqualDeep(@as([]const Diff, &.{ + Diff.init(.equal, "The-"), + Diff.init(.insert, "cow-and-the-"), + Diff.init(.equal, "cat."), + }), diffs5.items); - try diffs.appendSlice(alloc, &.{ - Diff.init(.equal, "xa"), - Diff.init(.delete, "a"), - Diff.init(.equal, "a"), - }); - try diffCleanupSemanticLossless(alloc, &diffs); - try testing.expectEqualDeep(@as([]const Diff, &.{ - Diff.init(.equal, "xaa"), - Diff.init(.delete, "a"), - }), diffs.items); + var diffs6 = DiffList{}; + defer deinitDiffList(alloc, &diffs6); + try diffs6.appendSlice(alloc, &.{ + Diff.init(.equal, try alloc.dupe(u8, "a")), + Diff.init(.delete, try alloc.dupe(u8, "a")), + Diff.init(.equal, try alloc.dupe(u8, "ax")), + }); + try diffCleanupSemanticLossless(alloc, &diffs6); + try testing.expectEqualDeep(@as([]const Diff, &.{ + Diff.init(.delete, "a"), + Diff.init(.equal, "aax"), + }), diffs6.items); - diffs.items.len = 0; + var diffs7 = DiffList{}; + defer deinitDiffList(alloc, &diffs7); + try diffs7.appendSlice(alloc, &.{ + Diff.init(.equal, try alloc.dupe(u8, "xa")), + Diff.init(.delete, try alloc.dupe(u8, "a")), + Diff.init(.equal, try alloc.dupe(u8, "a")), + }); + try diffCleanupSemanticLossless(alloc, &diffs7); + try testing.expectEqualDeep(@as([]const Diff, &.{ + Diff.init(.equal, "xaa"), + Diff.init(.delete, "a"), + }), diffs7.items); - try diffs.appendSlice(alloc, &.{ - Diff.init(.equal, "The xxx. The "), - Diff.init(.insert, "zzz. The "), - Diff.init(.equal, "yyy."), - }); - try diffCleanupSemanticLossless(alloc, &diffs); - try testing.expectEqualDeep(@as([]const Diff, &.{ - Diff.init(.equal, "The xxx."), - Diff.init(.insert, " The zzz."), - Diff.init(.equal, " The yyy."), - }), diffs.items); - } + var diffs8 = DiffList{}; + defer deinitDiffList(alloc, &diffs8); + try diffs8.appendSlice(alloc, &.{ + Diff.init(.equal, try alloc.dupe(u8, "The xxx. The ")), + Diff.init(.insert, try alloc.dupe(u8, "zzz. The ")), + Diff.init(.equal, try alloc.dupe(u8, "yyy.")), + }); + try diffCleanupSemanticLossless(alloc, &diffs8); + try testing.expectEqualDeep(@as([]const Diff, &.{ + Diff.init(.equal, "The xxx."), + Diff.init(.insert, " The zzz."), + Diff.init(.equal, " The yyy."), + }), diffs8.items); } fn rebuildtexts(allocator: std.mem.Allocator, diffs: DiffList) ![2][]const u8 { From 5ff7a7648462c501fa78469a89c1998008ffede0 Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Tue, 2 Jul 2024 19:04:10 -0400 Subject: [PATCH 16/60] alloc -> allocator --- DiffMatchPatch.zig | 268 ++++++++++++++++++++++----------------------- 1 file changed, 131 insertions(+), 137 deletions(-) diff --git a/DiffMatchPatch.zig b/DiffMatchPatch.zig index 5b46824..a4ed93b 100644 --- a/DiffMatchPatch.zig +++ b/DiffMatchPatch.zig @@ -1662,125 +1662,122 @@ test diffCharsToLines { } test diffCleanupMerge { - var arena = std.heap.ArenaAllocator.init(testing.allocator); - defer arena.deinit(); - - const alloc = std.testing.allocator; + const allocator = std.testing.allocator; // Cleanup a messy diff. var diffs = DiffList{}; - defer deinitDiffList(alloc, &diffs); + defer deinitDiffList(allocator, &diffs); try testing.expectEqualDeep(@as([]const Diff, &[0]Diff{}), diffs.items); // Null case - try diffs.appendSlice(alloc, &[_]Diff{ + try diffs.appendSlice(allocator, &[_]Diff{ .{ .operation = .equal, - .text = try alloc.dupe(u8, "a"), + .text = try allocator.dupe(u8, "a"), }, .{ .operation = .delete, - .text = try alloc.dupe(u8, "b"), + .text = try allocator.dupe(u8, "b"), }, .{ .operation = .insert, - .text = try alloc.dupe(u8, "c"), + .text = try allocator.dupe(u8, "c"), }, }); - try diffCleanupMerge(alloc, &diffs); + try diffCleanupMerge(allocator, &diffs); try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ .{ .operation = .equal, .text = "a" }, .{ .operation = .delete, .text = "b" }, .{ .operation = .insert, .text = "c" } }), diffs.items); // No change case var diffs2 = DiffList{}; - defer deinitDiffList(alloc, &diffs2); - try diffs2.appendSlice(alloc, &[_]Diff{ + defer deinitDiffList(allocator, &diffs2); + try diffs2.appendSlice(allocator, &[_]Diff{ .{ .operation = .equal, - .text = try alloc.dupe(u8, "a"), + .text = try allocator.dupe(u8, "a"), }, .{ .operation = .equal, - .text = try alloc.dupe(u8, "b"), + .text = try allocator.dupe(u8, "b"), }, .{ .operation = .equal, - .text = try alloc.dupe(u8, "c"), + .text = try allocator.dupe(u8, "c"), }, }); - try diffCleanupMerge(alloc, &diffs2); + try diffCleanupMerge(allocator, &diffs2); try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ .{ .operation = .equal, .text = "abc" }, }), diffs2.items); // Merge equalities var diffs3 = DiffList{}; - defer deinitDiffList(alloc, &diffs3); + defer deinitDiffList(allocator, &diffs3); - try diffs3.appendSlice(alloc, &[_]Diff{ + try diffs3.appendSlice(allocator, &[_]Diff{ .{ .operation = .delete, - .text = try alloc.dupe(u8, "a"), + .text = try allocator.dupe(u8, "a"), }, .{ .operation = .delete, - .text = try alloc.dupe(u8, "b"), + .text = try allocator.dupe(u8, "b"), }, .{ .operation = .delete, - .text = try alloc.dupe(u8, "c"), + .text = try allocator.dupe(u8, "c"), }, }); - try diffCleanupMerge(alloc, &diffs3); + try diffCleanupMerge(allocator, &diffs3); try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ .{ .operation = .delete, .text = "abc" }, }), diffs3.items); // Merge deletions var diffs4 = DiffList{}; - defer deinitDiffList(alloc, &diffs4); - try diffs4.appendSlice(alloc, &[_]Diff{ + defer deinitDiffList(allocator, &diffs4); + try diffs4.appendSlice(allocator, &[_]Diff{ .{ .operation = .insert, - .text = try alloc.dupe(u8, "a"), + .text = try allocator.dupe(u8, "a"), }, .{ .operation = .insert, - .text = try alloc.dupe(u8, "b"), + .text = try allocator.dupe(u8, "b"), }, .{ .operation = .insert, - .text = try alloc.dupe(u8, "c"), + .text = try allocator.dupe(u8, "c"), }, }); - try diffCleanupMerge(alloc, &diffs4); + try diffCleanupMerge(allocator, &diffs4); try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ .{ .operation = .insert, .text = "abc" }, }), diffs4.items); // Merge insertions var diffs5 = DiffList{}; - defer deinitDiffList(alloc, &diffs5); - try diffs5.appendSlice(alloc, &[_]Diff{ + defer deinitDiffList(allocator, &diffs5); + try diffs5.appendSlice(allocator, &[_]Diff{ .{ .operation = .delete, - .text = try alloc.dupe(u8, "a"), + .text = try allocator.dupe(u8, "a"), }, .{ .operation = .insert, - .text = try alloc.dupe(u8, "b"), + .text = try allocator.dupe(u8, "b"), }, .{ .operation = .delete, - .text = try alloc.dupe(u8, "c"), + .text = try allocator.dupe(u8, "c"), }, .{ .operation = .insert, - .text = try alloc.dupe(u8, "d"), + .text = try allocator.dupe(u8, "d"), }, .{ .operation = .equal, - .text = try alloc.dupe(u8, "e"), + .text = try allocator.dupe(u8, "e"), }, .{ .operation = .equal, - .text = try alloc.dupe(u8, "f"), + .text = try allocator.dupe(u8, "f"), }, }); - try diffCleanupMerge(alloc, &diffs5); + try diffCleanupMerge(allocator, &diffs5); try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ .{ .operation = .delete, .text = "ac" }, .{ .operation = .insert, .text = "bd" }, @@ -1788,22 +1785,22 @@ test diffCleanupMerge { }), diffs5.items); // Merge interweave var diffs6 = DiffList{}; - defer deinitDiffList(alloc, &diffs6); - try diffs6.appendSlice(alloc, &[_]Diff{ + defer deinitDiffList(allocator, &diffs6); + try diffs6.appendSlice(allocator, &[_]Diff{ .{ .operation = .delete, - .text = try alloc.dupe(u8, "a"), + .text = try allocator.dupe(u8, "a"), }, .{ .operation = .insert, - .text = try alloc.dupe(u8, "abc"), + .text = try allocator.dupe(u8, "abc"), }, .{ .operation = .delete, - .text = try alloc.dupe(u8, "dc"), + .text = try allocator.dupe(u8, "dc"), }, }); - try diffCleanupMerge(alloc, &diffs6); + try diffCleanupMerge(allocator, &diffs6); try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ .{ .operation = .equal, .text = "a" }, .{ .operation = .delete, .text = "d" }, @@ -1812,31 +1809,31 @@ test diffCleanupMerge { }), diffs6.items); // Prefix and suffix detection var diffs7 = DiffList{}; - defer deinitDiffList(alloc, &diffs7); + defer deinitDiffList(allocator, &diffs7); - try diffs7.appendSlice(alloc, &[_]Diff{ + try diffs7.appendSlice(allocator, &[_]Diff{ .{ .operation = .equal, - .text = try alloc.dupe(u8, "x"), + .text = try allocator.dupe(u8, "x"), }, .{ .operation = .delete, - .text = try alloc.dupe(u8, "a"), + .text = try allocator.dupe(u8, "a"), }, .{ .operation = .insert, - .text = try alloc.dupe(u8, "abc"), + .text = try allocator.dupe(u8, "abc"), }, .{ .operation = .delete, - .text = try alloc.dupe(u8, "dc"), + .text = try allocator.dupe(u8, "dc"), }, .{ .operation = .equal, - .text = try alloc.dupe(u8, "y"), + .text = try allocator.dupe(u8, "y"), }, }); - try diffCleanupMerge(alloc, &diffs7); + try diffCleanupMerge(allocator, &diffs7); try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ .{ .operation = .equal, .text = "xa" }, .{ .operation = .delete, .text = "d" }, @@ -1845,139 +1842,139 @@ test diffCleanupMerge { }), diffs7.items); // Prefix and suffix detection with equalities var diffs8 = DiffList{}; - defer deinitDiffList(alloc, &diffs8); - try diffs8.appendSlice(alloc, &[_]Diff{ + defer deinitDiffList(allocator, &diffs8); + try diffs8.appendSlice(allocator, &[_]Diff{ .{ .operation = .equal, - .text = try alloc.dupe(u8, "a"), + .text = try allocator.dupe(u8, "a"), }, .{ .operation = .insert, - .text = try alloc.dupe(u8, "ba"), + .text = try allocator.dupe(u8, "ba"), }, .{ .operation = .equal, - .text = try alloc.dupe(u8, "c"), + .text = try allocator.dupe(u8, "c"), }, }); - try diffCleanupMerge(alloc, &diffs8); + try diffCleanupMerge(allocator, &diffs8); try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ .{ .operation = .insert, .text = "ab" }, .{ .operation = .equal, .text = "ac" }, }), diffs8.items); // Slide edit left var diffs9 = DiffList{}; - defer deinitDiffList(alloc, &diffs9); - try diffs9.appendSlice(alloc, &[_]Diff{ + defer deinitDiffList(allocator, &diffs9); + try diffs9.appendSlice(allocator, &[_]Diff{ .{ .operation = .equal, - .text = try alloc.dupe(u8, "c"), + .text = try allocator.dupe(u8, "c"), }, .{ .operation = .insert, - .text = try alloc.dupe(u8, "ab"), + .text = try allocator.dupe(u8, "ab"), }, .{ .operation = .equal, - .text = try alloc.dupe(u8, "a"), + .text = try allocator.dupe(u8, "a"), }, }); - try diffCleanupMerge(alloc, &diffs9); + try diffCleanupMerge(allocator, &diffs9); try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ .{ .operation = .equal, .text = "ca" }, .{ .operation = .insert, .text = "ba" }, }), diffs9.items); // Slide edit right var diffs10 = DiffList{}; - defer deinitDiffList(alloc, &diffs10); - try diffs10.appendSlice(alloc, &[_]Diff{ + defer deinitDiffList(allocator, &diffs10); + try diffs10.appendSlice(allocator, &[_]Diff{ Diff.init( .equal, - try alloc.dupe(u8, "a"), + try allocator.dupe(u8, "a"), ), Diff.init( .delete, - try alloc.dupe(u8, "b"), + try allocator.dupe(u8, "b"), ), Diff.init( .equal, - try alloc.dupe(u8, "c"), + try allocator.dupe(u8, "c"), ), Diff.init( .delete, - try alloc.dupe(u8, "ac"), + try allocator.dupe(u8, "ac"), ), Diff.init( .equal, - try alloc.dupe(u8, "x"), + try allocator.dupe(u8, "x"), ), }); - try diffCleanupMerge(alloc, &diffs10); + try diffCleanupMerge(allocator, &diffs10); try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ Diff.init(.delete, "abc"), Diff.init(.equal, "acx"), }), diffs10.items); // Slide edit left recursive var diffs11 = DiffList{}; - defer deinitDiffList(alloc, &diffs11); - try diffs11.appendSlice(alloc, &[_]Diff{ + defer deinitDiffList(allocator, &diffs11); + try diffs11.appendSlice(allocator, &[_]Diff{ Diff.init( .equal, - try alloc.dupe(u8, "x"), + try allocator.dupe(u8, "x"), ), Diff.init( .delete, - try alloc.dupe(u8, "ca"), + try allocator.dupe(u8, "ca"), ), Diff.init( .equal, - try alloc.dupe(u8, "c"), + try allocator.dupe(u8, "c"), ), Diff.init( .delete, - try alloc.dupe(u8, "b"), + try allocator.dupe(u8, "b"), ), Diff.init( .equal, - try alloc.dupe(u8, "a"), + try allocator.dupe(u8, "a"), ), }); - try diffCleanupMerge(alloc, &diffs11); + try diffCleanupMerge(allocator, &diffs11); try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ Diff.init(.equal, "xca"), Diff.init(.delete, "cba"), }), diffs11.items); // Slide edit right recursive var diffs12 = DiffList{}; - defer deinitDiffList(alloc, &diffs12); - try diffs12.appendSlice(alloc, &[_]Diff{ + defer deinitDiffList(allocator, &diffs12); + try diffs12.appendSlice(allocator, &[_]Diff{ Diff.init( .delete, - try alloc.dupe(u8, "b"), + try allocator.dupe(u8, "b"), ), Diff.init( .insert, - try alloc.dupe(u8, "ab"), + try allocator.dupe(u8, "ab"), ), Diff.init( .equal, - try alloc.dupe(u8, "c"), + try allocator.dupe(u8, "c"), ), }); - try diffCleanupMerge(alloc, &diffs12); + try diffCleanupMerge(allocator, &diffs12); try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ Diff.init(.insert, "a"), Diff.init(.equal, "bc"), }), diffs12.items); // Empty merge var diffs13 = DiffList{}; - defer deinitDiffList(alloc, &diffs13); - try diffs13.appendSlice(alloc, &[_]Diff{ + defer deinitDiffList(allocator, &diffs13); + try diffs13.appendSlice(allocator, &[_]Diff{ Diff.init(.equal, ""), - Diff.init(.insert, try alloc.dupe(u8, "a")), - Diff.init(.equal, try alloc.dupe(u8, "b")), + Diff.init(.insert, try allocator.dupe(u8, "a")), + Diff.init(.equal, try allocator.dupe(u8, "b")), }); - try diffCleanupMerge(alloc, &diffs13); + try diffCleanupMerge(allocator, &diffs13); try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ Diff.init(.insert, "a"), Diff.init(.equal, "b"), @@ -1985,22 +1982,19 @@ test diffCleanupMerge { } test diffCleanupSemanticLossless { - var arena = std.heap.ArenaAllocator.init(testing.allocator); - defer arena.deinit(); - - const alloc = std.testing.allocator; + const allocator = std.testing.allocator; var diffs = DiffList{}; - try diffCleanupSemanticLossless(alloc, &diffs); + try diffCleanupSemanticLossless(allocator, &diffs); try testing.expectEqualDeep(@as([]const Diff, &[0]Diff{}), diffs.items); // Null case var diffs2 = DiffList{}; - defer deinitDiffList(alloc, &diffs2); - try diffs2.appendSlice(alloc, &.{ - Diff.init(.equal, try alloc.dupe(u8, "AAA\r\n\r\nBBB")), - Diff.init(.insert, try alloc.dupe(u8, "\r\nDDD\r\n\r\nBBB")), - Diff.init(.equal, try alloc.dupe(u8, "\r\nEEE")), + defer deinitDiffList(allocator, &diffs2); + try diffs2.appendSlice(allocator, &.{ + Diff.init(.equal, try allocator.dupe(u8, "AAA\r\n\r\nBBB")), + Diff.init(.insert, try allocator.dupe(u8, "\r\nDDD\r\n\r\nBBB")), + Diff.init(.equal, try allocator.dupe(u8, "\r\nEEE")), }); - try diffCleanupSemanticLossless(alloc, &diffs2); + try diffCleanupSemanticLossless(allocator, &diffs2); try testing.expectEqualDeep(@as([]const Diff, &.{ Diff.init(.equal, "AAA\r\n\r\n"), Diff.init(.insert, "BBB\r\nDDD\r\n\r\n"), @@ -2008,13 +2002,13 @@ test diffCleanupSemanticLossless { }), diffs2.items); var diffs3 = DiffList{}; - defer deinitDiffList(alloc, &diffs3); - try diffs3.appendSlice(alloc, &.{ - Diff.init(.equal, try alloc.dupe(u8, "AAA\r\nBBB")), - Diff.init(.insert, try alloc.dupe(u8, " DDD\r\nBBB")), - Diff.init(.equal, try alloc.dupe(u8, " EEE")), + defer deinitDiffList(allocator, &diffs3); + try diffs3.appendSlice(allocator, &.{ + Diff.init(.equal, try allocator.dupe(u8, "AAA\r\nBBB")), + Diff.init(.insert, try allocator.dupe(u8, " DDD\r\nBBB")), + Diff.init(.equal, try allocator.dupe(u8, " EEE")), }); - try diffCleanupSemanticLossless(alloc, &diffs3); + try diffCleanupSemanticLossless(allocator, &diffs3); try testing.expectEqualDeep(@as([]const Diff, &.{ Diff.init(.equal, "AAA\r\n"), Diff.init(.insert, "BBB DDD\r\n"), @@ -2022,13 +2016,13 @@ test diffCleanupSemanticLossless { }), diffs3.items); var diffs4 = DiffList{}; - defer deinitDiffList(alloc, &diffs4); - try diffs4.appendSlice(alloc, &.{ - Diff.init(.equal, try alloc.dupe(u8, "The c")), - Diff.init(.insert, try alloc.dupe(u8, "ow and the c")), - Diff.init(.equal, try alloc.dupe(u8, "at.")), + defer deinitDiffList(allocator, &diffs4); + try diffs4.appendSlice(allocator, &.{ + Diff.init(.equal, try allocator.dupe(u8, "The c")), + Diff.init(.insert, try allocator.dupe(u8, "ow and the c")), + Diff.init(.equal, try allocator.dupe(u8, "at.")), }); - try diffCleanupSemanticLossless(alloc, &diffs4); + try diffCleanupSemanticLossless(allocator, &diffs4); try testing.expectEqualDeep(@as([]const Diff, &.{ Diff.init(.equal, "The "), Diff.init(.insert, "cow and the "), @@ -2036,13 +2030,13 @@ test diffCleanupSemanticLossless { }), diffs4.items); var diffs5 = DiffList{}; - defer deinitDiffList(alloc, &diffs5); - try diffs5.appendSlice(alloc, &.{ - Diff.init(.equal, try alloc.dupe(u8, "The-c")), - Diff.init(.insert, try alloc.dupe(u8, "ow-and-the-c")), - Diff.init(.equal, try alloc.dupe(u8, "at.")), + defer deinitDiffList(allocator, &diffs5); + try diffs5.appendSlice(allocator, &.{ + Diff.init(.equal, try allocator.dupe(u8, "The-c")), + Diff.init(.insert, try allocator.dupe(u8, "ow-and-the-c")), + Diff.init(.equal, try allocator.dupe(u8, "at.")), }); - try diffCleanupSemanticLossless(alloc, &diffs5); + try diffCleanupSemanticLossless(allocator, &diffs5); try testing.expectEqualDeep(@as([]const Diff, &.{ Diff.init(.equal, "The-"), Diff.init(.insert, "cow-and-the-"), @@ -2050,39 +2044,39 @@ test diffCleanupSemanticLossless { }), diffs5.items); var diffs6 = DiffList{}; - defer deinitDiffList(alloc, &diffs6); - try diffs6.appendSlice(alloc, &.{ - Diff.init(.equal, try alloc.dupe(u8, "a")), - Diff.init(.delete, try alloc.dupe(u8, "a")), - Diff.init(.equal, try alloc.dupe(u8, "ax")), + defer deinitDiffList(allocator, &diffs6); + try diffs6.appendSlice(allocator, &.{ + Diff.init(.equal, try allocator.dupe(u8, "a")), + Diff.init(.delete, try allocator.dupe(u8, "a")), + Diff.init(.equal, try allocator.dupe(u8, "ax")), }); - try diffCleanupSemanticLossless(alloc, &diffs6); + try diffCleanupSemanticLossless(allocator, &diffs6); try testing.expectEqualDeep(@as([]const Diff, &.{ Diff.init(.delete, "a"), Diff.init(.equal, "aax"), }), diffs6.items); var diffs7 = DiffList{}; - defer deinitDiffList(alloc, &diffs7); - try diffs7.appendSlice(alloc, &.{ - Diff.init(.equal, try alloc.dupe(u8, "xa")), - Diff.init(.delete, try alloc.dupe(u8, "a")), - Diff.init(.equal, try alloc.dupe(u8, "a")), + defer deinitDiffList(allocator, &diffs7); + try diffs7.appendSlice(allocator, &.{ + Diff.init(.equal, try allocator.dupe(u8, "xa")), + Diff.init(.delete, try allocator.dupe(u8, "a")), + Diff.init(.equal, try allocator.dupe(u8, "a")), }); - try diffCleanupSemanticLossless(alloc, &diffs7); + try diffCleanupSemanticLossless(allocator, &diffs7); try testing.expectEqualDeep(@as([]const Diff, &.{ Diff.init(.equal, "xaa"), Diff.init(.delete, "a"), }), diffs7.items); var diffs8 = DiffList{}; - defer deinitDiffList(alloc, &diffs8); - try diffs8.appendSlice(alloc, &.{ - Diff.init(.equal, try alloc.dupe(u8, "The xxx. The ")), - Diff.init(.insert, try alloc.dupe(u8, "zzz. The ")), - Diff.init(.equal, try alloc.dupe(u8, "yyy.")), + defer deinitDiffList(allocator, &diffs8); + try diffs8.appendSlice(allocator, &.{ + Diff.init(.equal, try allocator.dupe(u8, "The xxx. The ")), + Diff.init(.insert, try allocator.dupe(u8, "zzz. The ")), + Diff.init(.equal, try allocator.dupe(u8, "yyy.")), }); - try diffCleanupSemanticLossless(alloc, &diffs8); + try diffCleanupSemanticLossless(allocator, &diffs8); try testing.expectEqualDeep(@as([]const Diff, &.{ Diff.init(.equal, "The xxx."), Diff.init(.insert, " The zzz."), From b56449f455d374caefe254a9a23a49099f620589 Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Tue, 2 Jul 2024 19:33:15 -0400 Subject: [PATCH 17/60] Tests pass for diffBisect --- DiffMatchPatch.zig | 44 ++++++++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/DiffMatchPatch.zig b/DiffMatchPatch.zig index a4ed93b..646ef65 100644 --- a/DiffMatchPatch.zig +++ b/DiffMatchPatch.zig @@ -119,8 +119,8 @@ fn diffInternal( deadline: u64, ) DiffError!DiffList { // Check for equality (speedup). - var diffs = DiffList{}; if (std.mem.eql(u8, before, after)) { + var diffs = DiffList{}; if (before.len != 0) { try diffs.append(allocator, Diff.init(.equal, try allocator.dupe(u8, before))); } @@ -140,7 +140,7 @@ fn diffInternal( trimmed_after = trimmed_after[0 .. trimmed_after.len - common_length]; // Compute the diff on the middle block. - diffs = try dmp.diffCompute(allocator, trimmed_before, trimmed_after, check_lines, deadline); + var diffs = try dmp.diffCompute(allocator, trimmed_before, trimmed_after, check_lines, deadline); // Restore the prefix and suffix. if (common_prefix.len != 0) { @@ -2105,9 +2105,7 @@ fn rebuildtexts(allocator: std.mem.Allocator, diffs: DiffList) ![2][]const u8 { } test diffBisect { - var arena = std.heap.ArenaAllocator.init(talloc); - defer arena.deinit(); - + const allocator = std.testing.allocator; // Normal. const a = "cat"; const b = "map"; @@ -2115,26 +2113,36 @@ test diffBisect { // the insertion and deletion pairs are swapped. // If the order changes, tweak this test as required. var diffs = DiffList{}; - defer diffs.deinit(arena.allocator()); + defer deinitDiffList(allocator, &diffs); var this = default; - try diffs.appendSlice(arena.allocator(), &.{ - Diff.init(.delete, "c"), - Diff.init(.insert, "m"), - Diff.init(.equal, "a"), - Diff.init(.delete, "t"), - Diff.init(.insert, "p"), + try diffs.appendSlice(allocator, &.{ + Diff.init(.delete, try allocator.dupe(u8, "c")), + Diff.init(.insert, try allocator.dupe(u8, "m")), + Diff.init(.equal, try allocator.dupe(u8, "a")), + Diff.init(.delete, try allocator.dupe(u8, "t")), + Diff.init(.insert, try allocator.dupe(u8, "p")), }); // Travis TODO not sure if maxInt(u64) is correct for DateTime.MaxValue - try testing.expectEqualDeep(diffs, try this.diffBisect(arena.allocator(), a, b, std.math.maxInt(u64))); // Normal. + var diff_bisect = try this.diffBisect( + allocator, + a, + b, + std.math.maxInt(u64), + ); + defer deinitDiffList(allocator, &diff_bisect); + try testing.expectEqualDeep(diffs, diff_bisect); // Normal. // Timeout. - diffs.items.len = 0; - try diffs.appendSlice(arena.allocator(), &.{ - Diff.init(.delete, "cat"), - Diff.init(.insert, "map"), + var diffs2 = DiffList{}; + defer deinitDiffList(allocator, &diffs2); + try diffs2.appendSlice(allocator, &.{ + Diff.init(.delete, try allocator.dupe(u8, "cat")), + Diff.init(.insert, try allocator.dupe(u8, "map")), }); // Travis TODO not sure if 0 is correct for DateTime.MinValue - try testing.expectEqualDeep(diffs, try this.diffBisect(arena.allocator(), a, b, 0)); // Timeout. + var diff_bisect2 = try this.diffBisect(allocator, a, b, 0); + defer deinitDiffList(allocator, &diff_bisect2); + try testing.expectEqualDeep(diffs2, diff_bisect2); // Timeout. } const talloc = testing.allocator; From 2388fff63442371aa9e4aa68ccf332eba7ea4e67 Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Tue, 2 Jul 2024 20:36:39 -0400 Subject: [PATCH 18/60] Catch a few leaks, lather, rinse, repeat --- DiffMatchPatch.zig | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/DiffMatchPatch.zig b/DiffMatchPatch.zig index 646ef65..1078bbb 100644 --- a/DiffMatchPatch.zig +++ b/DiffMatchPatch.zig @@ -645,6 +645,12 @@ fn diffLineMode( if (count_delete >= 1 and count_insert >= 1) { // Delete the offending records and add the merged ones. // diffs.RemoveRange(pointer - count_delete - count_insert, count_delete + count_insert); + freeRangeDiffList( + allocator, + &diffs, + pointer - count_delete - count_insert, + count_delete + count_insert, + ); try diffs.replaceRange( allocator, pointer - count_delete - count_insert, @@ -652,7 +658,8 @@ fn diffLineMode( &.{}, ); pointer = pointer - count_delete - count_insert; - const sub_diff = try dmp.diffInternal(allocator, text_delete.items, text_insert.items, false, deadline); + var sub_diff = try dmp.diffInternal(allocator, text_delete.items, text_insert.items, false, deadline); + defer sub_diff.deinit(allocator); // diffs.InsertRange(pointer, sub_diff); try diffs.insertSlice(allocator, pointer, sub_diff.items); pointer = pointer + sub_diff.items.len; @@ -1048,10 +1055,10 @@ fn diffCleanupSemantic(allocator: std.mem.Allocator, diffs: *DiffList) DiffError @intCast(pointer), Diff.init(.equal, try allocator.dupe(u8, insertion[0..overlap_length1])), ); - // XXX activate: allocator.free(diffs.items[@inteCast(pointer-1)].text); + allocator.free(diffs.items[@intCast(pointer - 1)].text); diffs.items[@intCast(pointer - 1)].text = try allocator.dupe(u8, deletion[0 .. deletion.len - overlap_length1]); - // XXX activate: allocator.free(diffs.items[@inteCast(pointer+1)].text); + allocator.free(diffs.items[@intCast(pointer + 1)].text); diffs.items[@intCast(pointer + 1)].text = try allocator.dupe(u8, insertion[overlap_length1..]); pointer += 1; @@ -1069,11 +1076,11 @@ fn diffCleanupSemantic(allocator: std.mem.Allocator, diffs: *DiffList) DiffError ); diffs.items[@intCast(pointer - 1)].operation = .insert; const new_minus = try allocator.dupe(u8, insertion[0 .. insertion.len - overlap_length2]); - // XXX activate: allocator.free(diffs.items[@inteCast(pointer-1)].text); + allocator.free(diffs.items[@intCast(pointer - 1)].text); diffs.items[@intCast(pointer - 1)].text = new_minus; diffs.items[@intCast(pointer + 1)].operation = .delete; const new_plus = try allocator.dupe(u8, deletion[overlap_length2..]); - // XXX activate: allocator.free(diffs.items[@inteCast(pointer+1)].text); + allocator.free(diffs.items[@intCast(pointer + 1)].text); diffs.items[@intCast(pointer + 1)].text = new_plus; pointer += 1; } @@ -2146,9 +2153,12 @@ test diffBisect { } const talloc = testing.allocator; -test diff { + +// XXX rename to diff +test "diff main" { var arena = std.heap.ArenaAllocator.init(talloc); defer arena.deinit(); + const alloc = std.testing.allocator; // Perform a trivial diff. var diffs = DiffList{}; @@ -2213,7 +2223,8 @@ test diff { const a = "`Twas brillig, and the slithy toves\nDid gyre and gimble in the wabe:\nAll mimsy were the borogoves,\nAnd the mome raths outgrabe.\n" ** 1024; const b = "I am the very model of a modern major general,\nI've information vegetable, animal, and mineral,\nI know the kings of England, and I quote the fights historical,\nFrom Marathon to Waterloo, in order categorical.\n" ** 1024; const start_time = std.time.milliTimestamp(); - _ = try this.diff(arena.allocator(), a, b, false); // Travis - TODO not sure what the third arg should be + var time_diff = try this.diff(alloc, a, b, false); // Travis - TODO not sure what the third arg should be + defer deinitDiffList(alloc, &time_diff); const end_time = std.time.milliTimestamp(); // Test that we took at least the timeout period. try testing.expect(this.diff_timeout <= end_time - start_time); // diff: Timeout min. @@ -2228,7 +2239,11 @@ test diff { // Must be long to pass the 100 char cutoff. const a = "1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n"; const b = "abcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\n"; - try testing.expectEqualDeep(try this.diff(arena.allocator(), a, b, true), try this.diff(arena.allocator(), a, b, false)); // diff: Simple line-mode. + var diff_checked = try this.diff(alloc, a, b, true); + defer deinitDiffList(alloc, &diff_checked); + var diff_unchecked = try this.diff(alloc, a, b, false); + defer deinitDiffList(alloc, &diff_unchecked); + try testing.expectEqualDeep(diff_checked, diff_unchecked); // diff: Simple line-mode. } { const a = "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"; From 7a47bd7d7384b0cc1132356b7e68641383af3235 Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Tue, 2 Jul 2024 21:29:45 -0400 Subject: [PATCH 19/60] Defer free until slice is taken I had missed that some slices in cleanupSemantic were being taken from the same string/slice that was already at that index, due to the prior code renaming them. Ganbatte! --- DiffMatchPatch.zig | 260 ++++++++++++++++++++++++--------------------- 1 file changed, 137 insertions(+), 123 deletions(-) diff --git a/DiffMatchPatch.zig b/DiffMatchPatch.zig index 1078bbb..3609788 100644 --- a/DiffMatchPatch.zig +++ b/DiffMatchPatch.zig @@ -969,6 +969,7 @@ fn diffCleanupSemantic(allocator: std.mem.Allocator, diffs: *DiffList) DiffError var changes = false; // Stack of indices where equalities are found. var equalities = ArrayListUnmanaged(isize){}; + defer equalities.deinit(allocator); // Always equal to equalities[equalitiesLength-1][1] var last_equality: ?[]const u8 = null; var pointer: isize = 0; // Index of current position. @@ -1050,15 +1051,15 @@ fn diffCleanupSemantic(allocator: std.mem.Allocator, diffs: *DiffList) DiffError { // Overlap found. // Insert an equality and trim the surrounding edits. + defer allocator.free(deletion); + defer allocator.free(insertion); try diffs.insert( allocator, @intCast(pointer), Diff.init(.equal, try allocator.dupe(u8, insertion[0..overlap_length1])), ); - allocator.free(diffs.items[@intCast(pointer - 1)].text); diffs.items[@intCast(pointer - 1)].text = try allocator.dupe(u8, deletion[0 .. deletion.len - overlap_length1]); - allocator.free(diffs.items[@intCast(pointer + 1)].text); diffs.items[@intCast(pointer + 1)].text = try allocator.dupe(u8, insertion[overlap_length1..]); pointer += 1; @@ -1069,6 +1070,8 @@ fn diffCleanupSemantic(allocator: std.mem.Allocator, diffs: *DiffList) DiffError { // Reverse overlap found. // Insert an equality and swap and trim the surrounding edits. + defer allocator.free(deletion); + defer allocator.free(insertion); try diffs.insert( allocator, @intCast(pointer), @@ -1076,7 +1079,6 @@ fn diffCleanupSemantic(allocator: std.mem.Allocator, diffs: *DiffList) DiffError ); diffs.items[@intCast(pointer - 1)].operation = .insert; const new_minus = try allocator.dupe(u8, insertion[0 .. insertion.len - overlap_length2]); - allocator.free(diffs.items[@intCast(pointer - 1)].text); diffs.items[@intCast(pointer - 1)].text = new_minus; diffs.items[@intCast(pointer + 1)].operation = .delete; const new_plus = try allocator.dupe(u8, deletion[overlap_length2..]); @@ -2248,7 +2250,11 @@ test "diff main" { { const a = "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"; const b = "abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghij"; - try testing.expectEqualDeep(try this.diff(arena.allocator(), a, b, true), try this.diff(arena.allocator(), a, b, false)); // diff: Single line-mode. + var diff_checked = try this.diff(alloc, a, b, true); + defer deinitDiffList(alloc, &diff_checked); + var diff_unchecked = try this.diff(alloc, a, b, false); + defer deinitDiffList(alloc, &diff_unchecked); + try testing.expectEqualDeep(diff_checked, diff_unchecked); // diff: Single line-mode. } const a = "1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n"; @@ -2264,139 +2270,147 @@ test "diff main" { arena.allocator().free(texts_textmode[1]); } try testing.expectEqualDeep(texts_textmode, texts_linemode); // diff: Overlap line-mode. - - // Test null inputs -- not needed because nulls can't be passed in C#. } test diffCleanupSemantic { - if (false) { - var arena = std.heap.ArenaAllocator.init(talloc); - defer arena.deinit(); + var arena = std.heap.ArenaAllocator.init(talloc); + defer arena.deinit(); - // Cleanup semantically trivial equalities. - // Null case. - var diffs = DiffList{}; - defer diffs.deinit(arena.allocator()); - // var this = default; - try diffCleanupSemantic(arena.allocator(), &diffs); - try testing.expectEqual(@as(usize, 0), diffs.items.len); // Null case + const alloc = std.testing.allocator; + // Cleanup semantically trivial equalities. + // Null case. + var diffs_empty = DiffList{}; + defer deinitDiffList(alloc, &diffs_empty); + // var this = default; + try diffCleanupSemantic(arena.allocator(), &diffs_empty); + try testing.expectEqual(@as(usize, 0), diffs_empty.items.len); // Null case - diffs.items.len = 0; - try diffs.appendSlice(arena.allocator(), &.{ - Diff.init(.delete, "ab"), - Diff.init(.insert, "cd"), - Diff.init(.equal, "12"), - Diff.init(.delete, "e"), - }); - try diffCleanupSemantic(arena.allocator(), &diffs); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // No elimination #1 - Diff.init(.delete, "ab"), - Diff.init(.insert, "cd"), - Diff.init(.equal, "12"), - Diff.init(.delete, "e"), - }), diffs.items); + var diffs = DiffList{}; + defer deinitDiffList(alloc, &diffs); + diffs.items.len = 0; + try diffs.appendSlice(alloc, &.{ + Diff.init(.delete, try alloc.dupe(u8, "ab")), + Diff.init(.insert, try alloc.dupe(u8, "cd")), + Diff.init(.equal, try alloc.dupe(u8, "12")), + Diff.init(.delete, try alloc.dupe(u8, "e")), + }); + try diffCleanupSemantic(alloc, &diffs); + try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // No elimination #1 + Diff.init(.delete, "ab"), + Diff.init(.insert, "cd"), + Diff.init(.equal, "12"), + Diff.init(.delete, "e"), + }), diffs.items); - diffs.items.len = 0; - try diffs.appendSlice(arena.allocator(), &.{ - Diff.init(.delete, "abc"), - Diff.init(.insert, "ABC"), - Diff.init(.equal, "1234"), - Diff.init(.delete, "wxyz"), - }); - try diffCleanupSemantic(arena.allocator(), &diffs); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // No elimination #2 - Diff.init(.delete, "abc"), - Diff.init(.insert, "ABC"), - Diff.init(.equal, "1234"), - Diff.init(.delete, "wxyz"), - }), diffs.items); + var diffs2 = DiffList{}; + defer deinitDiffList(alloc, &diffs2); + diffs2.items.len = 0; + try diffs2.appendSlice(alloc, &.{ + Diff.init(.delete, try alloc.dupe(u8, "abc")), + Diff.init(.insert, try alloc.dupe(u8, "ABC")), + Diff.init(.equal, try alloc.dupe(u8, "1234")), + Diff.init(.delete, try alloc.dupe(u8, "wxyz")), + }); + try diffCleanupSemantic(alloc, &diffs2); + try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // No elimination #2 + Diff.init(.delete, "abc"), + Diff.init(.insert, "ABC"), + Diff.init(.equal, "1234"), + Diff.init(.delete, "wxyz"), + }), diffs2.items); - diffs.items.len = 0; - try diffs.appendSlice(arena.allocator(), &.{ - Diff.init(.delete, "a"), - Diff.init(.equal, "b"), - Diff.init(.delete, "c"), - }); - try diffCleanupSemantic(arena.allocator(), &diffs); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // Simple elimination - Diff.init(.delete, "abc"), - Diff.init(.insert, "b"), - }), diffs.items); + var diffs3 = DiffList{}; + defer deinitDiffList(alloc, &diffs3); + try diffs3.appendSlice(alloc, &.{ + Diff.init(.delete, try alloc.dupe(u8, "a")), + Diff.init(.equal, try alloc.dupe(u8, "b")), + Diff.init(.delete, try alloc.dupe(u8, "c")), + }); + try diffCleanupSemantic(alloc, &diffs3); + try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // Simple elimination + Diff.init(.delete, "abc"), + Diff.init(.insert, "b"), + }), diffs3.items); - diffs.items.len = 0; - try diffs.appendSlice(arena.allocator(), &.{ - Diff.init(.delete, "ab"), - Diff.init(.equal, "cd"), - Diff.init(.delete, "e"), - Diff.init(.equal, "f"), - Diff.init(.insert, "g"), - }); - try diffCleanupSemantic(arena.allocator(), &diffs); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // Backpass elimination - Diff.init(.delete, "abcdef"), - Diff.init(.insert, "cdfg"), - }), diffs.items); + var diffs4 = DiffList{}; + defer deinitDiffList(alloc, &diffs4); + try diffs4.appendSlice(alloc, &.{ + Diff.init(.delete, try alloc.dupe(u8, "ab")), + Diff.init(.equal, try alloc.dupe(u8, "cd")), + Diff.init(.delete, try alloc.dupe(u8, "e")), + Diff.init(.equal, try alloc.dupe(u8, "f")), + Diff.init(.insert, try alloc.dupe(u8, "g")), + }); + try diffCleanupSemantic(alloc, &diffs4); + try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // Backpass elimination + Diff.init(.delete, "abcdef"), + Diff.init(.insert, "cdfg"), + }), diffs4.items); - diffs.items.len = 0; - try diffs.appendSlice(arena.allocator(), &.{ - Diff.init(.insert, "1"), - Diff.init(.equal, "A"), - Diff.init(.delete, "B"), - Diff.init(.insert, "2"), - Diff.init(.equal, "_"), - Diff.init(.insert, "1"), - Diff.init(.equal, "A"), - Diff.init(.delete, "B"), - Diff.init(.insert, "2"), - }); - try diffCleanupSemantic(arena.allocator(), &diffs); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // Multiple elimination - Diff.init(.delete, "AB_AB"), - Diff.init(.insert, "1A2_1A2"), - }), diffs.items); + var diffs5 = DiffList{}; + defer deinitDiffList(alloc, &diffs5); + try diffs5.appendSlice(alloc, &.{ + Diff.init(.insert, try alloc.dupe(u8, "1")), + Diff.init(.equal, try alloc.dupe(u8, "A")), + Diff.init(.delete, try alloc.dupe(u8, "B")), + Diff.init(.insert, try alloc.dupe(u8, "2")), + Diff.init(.equal, try alloc.dupe(u8, "_")), + Diff.init(.insert, try alloc.dupe(u8, "1")), + Diff.init(.equal, try alloc.dupe(u8, "A")), + Diff.init(.delete, try alloc.dupe(u8, "B")), + Diff.init(.insert, try alloc.dupe(u8, "2")), + }); + try diffCleanupSemantic(alloc, &diffs5); + try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // Multiple elimination + Diff.init(.delete, "AB_AB"), + Diff.init(.insert, "1A2_1A2"), + }), diffs5.items); - diffs.items.len = 0; - try diffs.appendSlice(arena.allocator(), &.{ - Diff.init(.equal, "The c"), - Diff.init(.delete, "ow and the c"), - Diff.init(.equal, "at."), - }); - try diffCleanupSemantic(arena.allocator(), &diffs); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // Word boundaries - Diff.init(.equal, "The "), - Diff.init(.delete, "cow and the "), - Diff.init(.equal, "cat."), - }), diffs.items); + var diffs6 = DiffList{}; + defer deinitDiffList(alloc, &diffs6); + try diffs6.appendSlice(alloc, &.{ + Diff.init(.equal, try alloc.dupe(u8, "The c")), + Diff.init(.delete, try alloc.dupe(u8, "ow and the c")), + Diff.init(.equal, try alloc.dupe(u8, "at.")), + }); + try diffCleanupSemantic(alloc, &diffs6); + try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // Word boundaries + Diff.init(.equal, "The "), + Diff.init(.delete, "cow and the "), + Diff.init(.equal, "cat."), + }), diffs6.items); - diffs.items.len = 0; - try diffs.appendSlice(arena.allocator(), &.{ - Diff.init(.delete, "abcxx"), - Diff.init(.insert, "xxdef"), - }); - try diffCleanupSemantic(arena.allocator(), &diffs); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // No overlap elimination - Diff.init(.delete, "abcxx"), - Diff.init(.insert, "xxdef"), - }), diffs.items); + var diffs7 = DiffList{}; + defer deinitDiffList(alloc, &diffs7); + try diffs7.appendSlice(alloc, &.{ + Diff.init(.delete, try alloc.dupe(u8, "abcxx")), + Diff.init(.insert, try alloc.dupe(u8, "xxdef")), + }); + try diffCleanupSemantic(alloc, &diffs7); + try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // No overlap elimination + Diff.init(.delete, "abcxx"), + Diff.init(.insert, "xxdef"), + }), diffs7.items); - diffs.items.len = 0; - try diffs.appendSlice(arena.allocator(), &.{ - Diff.init(.delete, "abcxxx"), - Diff.init(.insert, "xxxdef"), - }); - try diffCleanupSemantic(arena.allocator(), &diffs); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // Overlap elimination - Diff.init(.delete, "abc"), - Diff.init(.equal, "xxx"), - Diff.init(.insert, "def"), - }), diffs.items); + var diffs8 = DiffList{}; + defer deinitDiffList(alloc, &diffs8); + try diffs8.appendSlice(alloc, &.{ + Diff.init(.delete, try alloc.dupe(u8, "abcxxx")), + Diff.init(.insert, try alloc.dupe(u8, "xxxdef")), + }); + try diffCleanupSemantic(alloc, &diffs8); + try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // Overlap elimination + Diff.init(.delete, "abc"), + Diff.init(.equal, "xxx"), + Diff.init(.insert, "def"), + }), diffs8.items); - diffs.items.len = 0; - try diffs.appendSlice(arena.allocator(), &.{ + if (false) { + try diffs.appendSlice(alloc, &.{ Diff.init(.delete, "xxxabc"), Diff.init(.insert, "defxxx"), }); - try diffCleanupSemantic(arena.allocator(), &diffs); + try diffCleanupSemantic(alloc, &diffs); try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // Reverse overlap elimination Diff.init(.insert, "def"), Diff.init(.equal, "xxx"), @@ -2404,14 +2418,14 @@ test diffCleanupSemantic { }), diffs.items); diffs.items.len = 0; - try diffs.appendSlice(arena.allocator(), &.{ + try diffs.appendSlice(alloc, &.{ Diff.init(.delete, "abcd1212"), Diff.init(.insert, "1212efghi"), Diff.init(.equal, "----"), Diff.init(.delete, "A3"), Diff.init(.insert, "3BC"), }); - try diffCleanupSemantic(arena.allocator(), &diffs); + try diffCleanupSemantic(alloc, &diffs); try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // Two overlap eliminations Diff.init(.delete, "abcd"), Diff.init(.equal, "1212"), From eecdb470e552ad9152c5231c548e4fd492877918 Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Tue, 2 Jul 2024 23:05:05 -0400 Subject: [PATCH 20/60] Fait accompli The library now manages its own memory, as it should. --- DiffMatchPatch.zig | 202 +++++++++++++++++++-------------------------- 1 file changed, 86 insertions(+), 116 deletions(-) diff --git a/DiffMatchPatch.zig b/DiffMatchPatch.zig index 3609788..6945b71 100644 --- a/DiffMatchPatch.zig +++ b/DiffMatchPatch.zig @@ -6,7 +6,9 @@ const Allocator = std.mem.Allocator; const ArrayListUnmanaged = std.ArrayListUnmanaged; const DiffList = ArrayListUnmanaged(Diff); -fn deinitDiffList(allocator: Allocator, diffs: *DiffList) void { +/// Deinit an `ArrayListUnmanaged(Diff)` and the allocated slices of +/// text in each `Diff`. +pub fn deinitDiffList(allocator: Allocator, diffs: *DiffList) void { defer diffs.deinit(allocator); for (diffs.items) |d| { if (d.text.len > 0) { @@ -84,8 +86,6 @@ patch_margin: u16 = 4, pub const DiffError = error{OutOfMemory}; -/// It is recommended that you use an Arena for this operation. -/// /// Find the differences between two texts. /// @param before Old string to be diffed. /// @param after New string to be diffed. @@ -711,7 +711,6 @@ fn diffLinesToChars( // "\x00" is a valid character, but various debuggers don't like it. // So we'll insert a junk entry to avoid generating a null character. - // XXX why is this necessary? -Sam try line_array.append(allocator, ""); // Allocate 2/3rds of the space for text1, the rest for text2. @@ -736,7 +735,7 @@ fn diffLinesToCharsMunge( ) DiffError![]const u8 { var line_start: isize = 0; var line_end: isize = -1; - var line: []const u8 = ""; + var line: []const u8 = undefined; var chars = ArrayListUnmanaged(u8){}; defer chars.deinit(allocator); // Walk the text, pulling out a Substring for each line. @@ -1082,7 +1081,6 @@ fn diffCleanupSemantic(allocator: std.mem.Allocator, diffs: *DiffList) DiffError diffs.items[@intCast(pointer - 1)].text = new_minus; diffs.items[@intCast(pointer + 1)].operation = .delete; const new_plus = try allocator.dupe(u8, deletion[overlap_length2..]); - allocator.free(diffs.items[@intCast(pointer + 1)].text); diffs.items[@intCast(pointer + 1)].text = new_plus; pointer += 1; } @@ -1262,16 +1260,6 @@ fn diffCleanupSemanticScore(one: []const u8, two: []const u8) usize { return 0; } -// Define some regex patterns for matching boundaries. -// private Regex BLANKLINEEND = new Regex("\\n\\r?\\n\\Z"); -// \n\n -// \n\r\n -// private Regex BLANKLINESTART = new Regex("\\A\\r?\\n\\r?\\n"); -// \n\n -// \r\n\n -// \n\r\n -// \r\n\r\n - /// Reduce the number of edits by eliminating operationally trivial /// equalities. pub fn diffCleanupEfficiency( @@ -1408,36 +1396,10 @@ fn diffCommonOverlap(text1_in: []const u8, text2_in: []const u8) usize { } } -// pub fn main() void { -// var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); -// defer arena.deinit(); - -// var bruh = default.diff(arena.allocator(), "Hello World.", "Goodbye World.", true); -// std.log.err("{any}", .{bruh}); -// } - -// test { -// var arena = std.heap.ArenaAllocator.init(testing.allocator); -// defer arena.deinit(); - -// var bruh = try default.diff(arena.allocator(), "Hello World.", "Goodbye World.", true); -// try diffCleanupSemantic(arena.allocator(), &bruh); -// for (bruh.items) |b| { -// std.log.err("{any}", .{b}); -// } - -// // for (bruh.items) |b| { -// // std.log.err("{s} {s}", .{ switch (b.operation) { -// // .equal => "", -// // .insert => "+", -// // .delete => "-", -// // }, b.text }); -// // } -// } - -// TODO: Allocate all text in diffs to +// DONE [✅]: Allocate all text in diffs to // not cause segfault while freeing; not a problem -// at the moment because we don't free anything :P +// at the moment because we don't free anything :( +// (or was it??) test diffCommonPrefix { // Detect any common suffix. @@ -1609,27 +1571,36 @@ test diffLinesToChars { // TODO: More than 256 to reveal any 8-bit limitations but this requires // some unicode logic that I don't want to deal with + // + // Casting to Unicode is straightforward and should sort correctly, I'm + // more concerned about the weird behavior when the 'char' is equal to a + // newline. Uncomment the EqualSlices below to see what I mean. + // I think there's some cleanup logic in the actual linediff that should + // take care of the problem, but I don't like it. + + const n: u8 = 255; + tmp_array_list.items.len = 0; - // TODO: Fix this - - // const n: u8 = 255; - // tmp_array_list.items.len = 0; - - // var line_list = std.ArrayList(u8).init(alloc); - // var char_list = std.ArrayList(u8).init(alloc); - - // var i: u8 = 0; - // while (i < n) : (i += 1) { - // try tmp_array_list.append(&.{ i, '\n' }); - // try line_list.appendSlice(&.{ i, '\n' }); - // try char_list.append(i); - // } - // try testing.expectEqual(@as(usize, n), tmp_array_list.items.len); // Test initialization fail #1 - // try testing.expectEqual(@as(usize, n), char_list.items.len); // Test initialization fail #2 - // try tmp_array_list.insert(0, ""); - // result = try diffLinesToChars(alloc, line_list.items, ""); - // try testing.expectEqualStrings(char_list.items, result.chars_1); - // try testing.expectEqualStrings("", result.chars_2); + var line_list = std.ArrayList(u8).init(allocator); + defer line_list.deinit(); + var char_list = std.ArrayList(u8).init(allocator); + defer char_list.deinit(); + + var i: u8 = 1; + while (i < n) : (i += 1) { + try tmp_array_list.append(&.{ i, '\n' }); + try line_list.appendSlice(&.{ i, '\n' }); + try char_list.append(i); + } + try testing.expectEqual(@as(usize, n - 1), tmp_array_list.items.len); // Test initialization fail #1 + try testing.expectEqual(@as(usize, n - 1), char_list.items.len); // Test initialization fail #2 + try tmp_array_list.insert(0, ""); + result = try diffLinesToChars(allocator, line_list.items, ""); + defer result.deinit(allocator); + // TODO: This isn't equal, should it be? + // try testing.expectEqualSlices(u8, char_list.items, result.chars_1); + try testing.expectEqualStrings("", result.chars_2); + // TODO this is wrong because of the max_value I think? // try testing.expectEqualDeep(tmp_array_list.items, result.line_array.items); } @@ -1676,7 +1647,10 @@ test diffCleanupMerge { var diffs = DiffList{}; defer deinitDiffList(allocator, &diffs); - try testing.expectEqualDeep(@as([]const Diff, &[0]Diff{}), diffs.items); // Null case + try testing.expectEqualDeep( + @as([]const Diff, &[0]Diff{}), + diffs.items, + ); // Null case try diffs.appendSlice(allocator, &[_]Diff{ .{ @@ -1694,6 +1668,7 @@ test diffCleanupMerge { }); try diffCleanupMerge(allocator, &diffs); try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ .{ .operation = .equal, .text = "a" }, .{ .operation = .delete, .text = "b" }, .{ .operation = .insert, .text = "c" } }), diffs.items); // No change case + var diffs2 = DiffList{}; defer deinitDiffList(allocator, &diffs2); try diffs2.appendSlice(allocator, &[_]Diff{ @@ -1717,7 +1692,6 @@ test diffCleanupMerge { var diffs3 = DiffList{}; defer deinitDiffList(allocator, &diffs3); - try diffs3.appendSlice(allocator, &[_]Diff{ .{ .operation = .delete, @@ -1819,7 +1793,6 @@ test diffCleanupMerge { var diffs7 = DiffList{}; defer deinitDiffList(allocator, &diffs7); - try diffs7.appendSlice(allocator, &[_]Diff{ .{ .operation = .equal, @@ -2156,11 +2129,10 @@ test diffBisect { const talloc = testing.allocator; -// XXX rename to diff -test "diff main" { +test diff { var arena = std.heap.ArenaAllocator.init(talloc); defer arena.deinit(); - const alloc = std.testing.allocator; + const allocator = std.testing.allocator; // Perform a trivial diff. var diffs = DiffList{}; @@ -2225,8 +2197,8 @@ test "diff main" { const a = "`Twas brillig, and the slithy toves\nDid gyre and gimble in the wabe:\nAll mimsy were the borogoves,\nAnd the mome raths outgrabe.\n" ** 1024; const b = "I am the very model of a modern major general,\nI've information vegetable, animal, and mineral,\nI know the kings of England, and I quote the fights historical,\nFrom Marathon to Waterloo, in order categorical.\n" ** 1024; const start_time = std.time.milliTimestamp(); - var time_diff = try this.diff(alloc, a, b, false); // Travis - TODO not sure what the third arg should be - defer deinitDiffList(alloc, &time_diff); + var time_diff = try this.diff(allocator, a, b, false); // Travis - TODO not sure what the third arg should be + defer deinitDiffList(allocator, &time_diff); const end_time = std.time.milliTimestamp(); // Test that we took at least the timeout period. try testing.expect(this.diff_timeout <= end_time - start_time); // diff: Timeout min. @@ -2241,19 +2213,19 @@ test "diff main" { // Must be long to pass the 100 char cutoff. const a = "1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n"; const b = "abcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\n"; - var diff_checked = try this.diff(alloc, a, b, true); - defer deinitDiffList(alloc, &diff_checked); - var diff_unchecked = try this.diff(alloc, a, b, false); - defer deinitDiffList(alloc, &diff_unchecked); + var diff_checked = try this.diff(allocator, a, b, true); + defer deinitDiffList(allocator, &diff_checked); + var diff_unchecked = try this.diff(allocator, a, b, false); + defer deinitDiffList(allocator, &diff_unchecked); try testing.expectEqualDeep(diff_checked, diff_unchecked); // diff: Simple line-mode. } { const a = "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"; const b = "abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghij"; - var diff_checked = try this.diff(alloc, a, b, true); - defer deinitDiffList(alloc, &diff_checked); - var diff_unchecked = try this.diff(alloc, a, b, false); - defer deinitDiffList(alloc, &diff_unchecked); + var diff_checked = try this.diff(allocator, a, b, true); + defer deinitDiffList(allocator, &diff_checked); + var diff_unchecked = try this.diff(allocator, a, b, false); + defer deinitDiffList(allocator, &diff_unchecked); try testing.expectEqualDeep(diff_checked, diff_unchecked); // diff: Single line-mode. } @@ -2273,16 +2245,13 @@ test "diff main" { } test diffCleanupSemantic { - var arena = std.heap.ArenaAllocator.init(talloc); - defer arena.deinit(); - const alloc = std.testing.allocator; // Cleanup semantically trivial equalities. // Null case. var diffs_empty = DiffList{}; defer deinitDiffList(alloc, &diffs_empty); // var this = default; - try diffCleanupSemantic(arena.allocator(), &diffs_empty); + try diffCleanupSemantic(alloc, &diffs_empty); try testing.expectEqual(@as(usize, 0), diffs_empty.items.len); // Null case var diffs = DiffList{}; @@ -2405,35 +2374,36 @@ test diffCleanupSemantic { Diff.init(.insert, "def"), }), diffs8.items); - if (false) { - try diffs.appendSlice(alloc, &.{ - Diff.init(.delete, "xxxabc"), - Diff.init(.insert, "defxxx"), - }); - try diffCleanupSemantic(alloc, &diffs); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // Reverse overlap elimination - Diff.init(.insert, "def"), - Diff.init(.equal, "xxx"), - Diff.init(.delete, "abc"), - }), diffs.items); - - diffs.items.len = 0; - try diffs.appendSlice(alloc, &.{ - Diff.init(.delete, "abcd1212"), - Diff.init(.insert, "1212efghi"), - Diff.init(.equal, "----"), - Diff.init(.delete, "A3"), - Diff.init(.insert, "3BC"), - }); - try diffCleanupSemantic(alloc, &diffs); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // Two overlap eliminations - Diff.init(.delete, "abcd"), - Diff.init(.equal, "1212"), - Diff.init(.insert, "efghi"), - Diff.init(.equal, "----"), - Diff.init(.delete, "A"), - Diff.init(.equal, "3"), - Diff.init(.insert, "BC"), - }), diffs.items); - } + var diffs9 = DiffList{}; + defer deinitDiffList(alloc, &diffs9); + try diffs9.appendSlice(alloc, &.{ + Diff.init(.delete, try alloc.dupe(u8, "xxxabc")), + Diff.init(.insert, try alloc.dupe(u8, "defxxx")), + }); + try diffCleanupSemantic(alloc, &diffs9); + try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // Reverse overlap elimination + Diff.init(.insert, "def"), + Diff.init(.equal, "xxx"), + Diff.init(.delete, "abc"), + }), diffs9.items); + + var diffs10 = DiffList{}; + defer deinitDiffList(alloc, &diffs10); + try diffs10.appendSlice(alloc, &.{ + Diff.init(.delete, try alloc.dupe(u8, "abcd1212")), + Diff.init(.insert, try alloc.dupe(u8, "1212efghi")), + Diff.init(.equal, try alloc.dupe(u8, "----")), + Diff.init(.delete, try alloc.dupe(u8, "A3")), + Diff.init(.insert, try alloc.dupe(u8, "3BC")), + }); + try diffCleanupSemantic(alloc, &diffs10); + try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // Two overlap eliminations + Diff.init(.delete, "abcd"), + Diff.init(.equal, "1212"), + Diff.init(.insert, "efghi"), + Diff.init(.equal, "----"), + Diff.init(.delete, "A"), + Diff.init(.equal, "3"), + Diff.init(.insert, "BC"), + }), diffs10.items); } From 7032b4b8289945a187f53ab6b6025dc551bc0ff4 Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Wed, 3 Jul 2024 09:29:19 -0400 Subject: [PATCH 21/60] Polish things up --- DiffMatchPatch.zig | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/DiffMatchPatch.zig b/DiffMatchPatch.zig index 6945b71..d5a7d9f 100644 --- a/DiffMatchPatch.zig +++ b/DiffMatchPatch.zig @@ -632,19 +632,16 @@ fn diffLineMode( switch (diffs.items[pointer].operation) { .insert => { count_insert += 1; - // text_insert += diffs.items[pointer].text; try text_insert.appendSlice(allocator, diffs.items[pointer].text); }, .delete => { count_delete += 1; - // text_delete += diffs.items[pointer].text; try text_delete.appendSlice(allocator, diffs.items[pointer].text); }, .equal => { // Upon reaching an equality, check for prior redundancies. if (count_delete >= 1 and count_insert >= 1) { // Delete the offending records and add the merged ones. - // diffs.RemoveRange(pointer - count_delete - count_insert, count_delete + count_insert); freeRangeDiffList( allocator, &diffs, @@ -660,7 +657,6 @@ fn diffLineMode( pointer = pointer - count_delete - count_insert; var sub_diff = try dmp.diffInternal(allocator, text_delete.items, text_insert.items, false, deadline); defer sub_diff.deinit(allocator); - // diffs.InsertRange(pointer, sub_diff); try diffs.insertSlice(allocator, pointer, sub_diff.items); pointer = pointer + sub_diff.items.len; } @@ -672,8 +668,7 @@ fn diffLineMode( } pointer += 1; } - // diffs.RemoveAt(diffs.Count - 1); // Remove the dummy entry at the end. - diffs.items.len -= 1; + diffs.items.len -= 1; // Remove the dummy entry at the end. return diffs; } @@ -2140,6 +2135,8 @@ test diff { var this = DiffMatchPatch{}; try testing.expectEqualDeep(diffs.items, (try this.diff(arena.allocator(), "", "", false)).items); // diff: Null case. + // TODO This is the last set of tests using the arena. Someone should + // rewrite them not to do so. -Sam diffs.items.len = 0; try diffs.appendSlice(arena.allocator(), &.{Diff.init(.equal, "abc")}); try testing.expectEqualDeep(diffs.items, (try this.diff(arena.allocator(), "abc", "abc", false)).items); // diff: Equality. @@ -2191,13 +2188,15 @@ test diff { try diffs.appendSlice(arena.allocator(), &.{ Diff.init(.insert, " "), Diff.init(.equal, "a"), Diff.init(.insert, "nd"), Diff.init(.equal, " [[Pennsylvania]]"), Diff.init(.delete, " and [[New") }); try testing.expectEqualDeep(diffs.items, (try this.diff(arena.allocator(), "a [[Pennsylvania]] and [[New", " and [[Pennsylvania]]", false)).items); // diff: Large equality. + // end of Arena Zone + this.diff_timeout = 100; // 100ms // Increase the text lengths by 1024 times to ensure a timeout. { const a = "`Twas brillig, and the slithy toves\nDid gyre and gimble in the wabe:\nAll mimsy were the borogoves,\nAnd the mome raths outgrabe.\n" ** 1024; const b = "I am the very model of a modern major general,\nI've information vegetable, animal, and mineral,\nI know the kings of England, and I quote the fights historical,\nFrom Marathon to Waterloo, in order categorical.\n" ** 1024; const start_time = std.time.milliTimestamp(); - var time_diff = try this.diff(allocator, a, b, false); // Travis - TODO not sure what the third arg should be + var time_diff = try this.diff(allocator, a, b, false); defer deinitDiffList(allocator, &time_diff); const end_time = std.time.milliTimestamp(); // Test that we took at least the timeout period. @@ -2231,15 +2230,19 @@ test diff { const a = "1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n"; const b = "abcdefghij\n1234567890\n1234567890\n1234567890\nabcdefghij\n1234567890\n1234567890\n1234567890\nabcdefghij\n1234567890\n1234567890\n1234567890\nabcdefghij\n"; - const texts_linemode = try rebuildtexts(arena.allocator(), try this.diff(arena.allocator(), a, b, true)); + var diffs_linemode = try this.diff(allocator, a, b, true); + defer deinitDiffList(allocator, &diffs_linemode); + const texts_linemode = try rebuildtexts(allocator, diffs_linemode); defer { - arena.allocator().free(texts_linemode[0]); - arena.allocator().free(texts_linemode[1]); + allocator.free(texts_linemode[0]); + allocator.free(texts_linemode[1]); } - const texts_textmode = try rebuildtexts(arena.allocator(), try this.diff(arena.allocator(), a, b, false)); + var diffs_textmode = try this.diff(allocator, a, b, false); + defer deinitDiffList(allocator, &diffs_textmode); + const texts_textmode = try rebuildtexts(allocator, diffs_textmode); defer { - arena.allocator().free(texts_textmode[0]); - arena.allocator().free(texts_textmode[1]); + allocator.free(texts_textmode[0]); + allocator.free(texts_textmode[1]); } try testing.expectEqualDeep(texts_textmode, texts_linemode); // diff: Overlap line-mode. } From 87b1b323026cab13b1b3ce3b9b65507ec363ea95 Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Wed, 3 Jul 2024 10:53:47 -0400 Subject: [PATCH 22/60] Remove unused temp diff --- DiffMatchPatch.zig | 3 --- 1 file changed, 3 deletions(-) diff --git a/DiffMatchPatch.zig b/DiffMatchPatch.zig index d5a7d9f..6350a5f 100644 --- a/DiffMatchPatch.zig +++ b/DiffMatchPatch.zig @@ -255,9 +255,6 @@ fn diffCompute( ); defer diffs_b.deinit(allocator); - var tmp_diffs = diffs; - defer tmp_diffs.deinit(allocator); - // Merge the results. diffs = diffs_a; try diffs.append(allocator, Diff.init(.equal, try allocator.dupe(u8, half_match.common_middle))); From 0655ece7cfa59ab280c71dbb3a081ff6f8ea8af3 Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Sun, 7 Jul 2024 03:13:18 -1000 Subject: [PATCH 23/60] Remove conditional free for zero-length text Co-authored-by: Techatrix --- DiffMatchPatch.zig | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/DiffMatchPatch.zig b/DiffMatchPatch.zig index 6350a5f..aeb86be 100644 --- a/DiffMatchPatch.zig +++ b/DiffMatchPatch.zig @@ -11,9 +11,7 @@ const DiffList = ArrayListUnmanaged(Diff); pub fn deinitDiffList(allocator: Allocator, diffs: *DiffList) void { defer diffs.deinit(allocator); for (diffs.items) |d| { - if (d.text.len > 0) { - allocator.free(d.text); - } + allocator.free(d.text); } } From f044a8ba6df47ac028d6a702ee598c28adc89dca Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Sun, 7 Jul 2024 03:16:40 -1000 Subject: [PATCH 24/60] Use explicit directory names in .gitignore Co-authored-by: Techatrix --- .gitignore | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 68557b5..8c9d17e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ -zig-* -.zig-* +.zig-cache +zig-cache +zig-out From 9bb28191ae739268262c24e61ea934eae270ec0c Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Sun, 7 Jul 2024 03:22:43 -1000 Subject: [PATCH 25/60] Update build.zig to remove empty library Library steps are only for extern symbols, since DiffMatchPatch (currently) provides no C API, this stage produces an empty static library. Co-authored-by: Techatrix --- build.zig | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/build.zig b/build.zig index dd40eb6..dedeff9 100644 --- a/build.zig +++ b/build.zig @@ -10,18 +10,6 @@ pub fn build(b: *std.Build) void { .optimize = optimize, }); - const lib = b.addStaticLibrary(.{ - .name = "diffz", - .root_source_file = b.path("DiffMatchPatch.zig"), - .target = target, - .optimize = optimize, - }); - - // This declares intent for the library to be installed into the standard - // location when the user invokes the "install" step (the default step when - // running `zig build`). - b.installArtifact(lib); - // Run tests const tests = b.addTest(.{ .name = "tests", From cc640a35442ece5c7b5630573c130eae45788473 Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Sun, 7 Jul 2024 10:18:33 -0400 Subject: [PATCH 26/60] Free resources when allocation fails This is an edit pass to try and catch every case (outside of tests) where an allocation failure would produce a memory leak. --- DiffMatchPatch.zig | 100 ++++++++++++++++++++++++++++----------------- 1 file changed, 63 insertions(+), 37 deletions(-) diff --git a/DiffMatchPatch.zig b/DiffMatchPatch.zig index 6350a5f..6d41786 100644 --- a/DiffMatchPatch.zig +++ b/DiffMatchPatch.zig @@ -122,7 +122,7 @@ fn diffInternal( if (std.mem.eql(u8, before, after)) { var diffs = DiffList{}; if (before.len != 0) { - try diffs.append(allocator, Diff.init(.equal, try allocator.dupe(u8, before))); + try diffsAppend(allocator, &diffs, .equal, before); } return diffs; } @@ -141,13 +141,14 @@ fn diffInternal( // Compute the diff on the middle block. var diffs = try dmp.diffCompute(allocator, trimmed_before, trimmed_after, check_lines, deadline); + errdefer deinitDiffList(allocator, &diffs); // Restore the prefix and suffix. if (common_prefix.len != 0) { - try diffs.insert(allocator, 0, Diff.init(.equal, try allocator.dupe(u8, common_prefix))); + try diffsInsert(allocator, &diffs, 0, .equal, common_prefix); } if (common_suffix.len != 0) { - try diffs.append(allocator, Diff.init(.equal, try allocator.dupe(u8, common_suffix))); + try diffsAppend(allocator, &diffs, .equal, common_suffix); } try diffCleanupMerge(allocator, &diffs); @@ -198,16 +199,17 @@ fn diffCompute( deadline: u64, ) DiffError!DiffList { var diffs = DiffList{}; + errdefer deinitDiffList(allocator, &diffs); if (before.len == 0) { // Just add some text (speedup). - try diffs.append(allocator, Diff.init(.insert, try allocator.dupe(u8, after))); + try diffsAppend(allocator, &diffs, .insert, after); return diffs; } if (after.len == 0) { // Just delete some text (speedup). - try diffs.append(allocator, Diff.init(.delete, try allocator.dupe(u8, before))); + try diffsAppend(allocator, &diffs, .delete, before); return diffs; } @@ -220,17 +222,17 @@ fn diffCompute( .delete else .insert; - try diffs.append(allocator, Diff.init(op, try allocator.dupe(u8, long_text[0..index]))); - try diffs.append(allocator, Diff.init(.equal, try allocator.dupe(u8, short_text))); - try diffs.append(allocator, Diff.init(op, try allocator.dupe(u8, long_text[index + short_text.len ..]))); + try diffsAppend(allocator, &diffs, op, long_text[0..index]); + try diffsAppend(allocator, &diffs, .equal, short_text); + try diffsAppend(allocator, &diffs, op, long_text[index + short_text.len ..]); return diffs; } if (short_text.len == 1) { // Single character string. // After the previous speedup, the character can't be an equality. - try diffs.append(allocator, Diff.init(.delete, try allocator.dupe(u8, before))); - try diffs.append(allocator, Diff.init(.insert, try allocator.dupe(u8, after))); + try diffsAppend(allocator, &diffs, .delete, before); + try diffsAppend(allocator, &diffs, .insert, after); return diffs; } @@ -257,7 +259,7 @@ fn diffCompute( // Merge the results. diffs = diffs_a; - try diffs.append(allocator, Diff.init(.equal, try allocator.dupe(u8, half_match.common_middle))); + try diffsAppend(allocator, &diffs, .equal, half_match.common_middle); try diffs.appendSlice(allocator, diffs_b.items); return diffs; } @@ -399,11 +401,19 @@ fn diffHalfMatchInternal( } } if (best_common.items.len * 2 >= long_text.len) { + const prefix_before = try allocator.dupe(u8, best_long_text_a); + errdefer allocator.free(prefix_before); + const suffix_before = try allocator.dupe(u8, best_long_text_b); + errdefer allocator.free(suffix_before); + const prefix_after = try allocator.dupe(u8, best_short_text_a); + errdefer allocator.free(prefix_after); + const suffix_after = try allocator.dupe(u8, best_short_text_b); + errdefer allocator.free(suffix_after); return .{ - .prefix_before = try allocator.dupe(u8, best_long_text_a), - .suffix_before = try allocator.dupe(u8, best_long_text_b), - .prefix_after = try allocator.dupe(u8, best_short_text_a), - .suffix_after = try allocator.dupe(u8, best_short_text_b), + .prefix_before = prefix_before, + .suffix_before = suffix_before, + .prefix_after = prefix_after, + .suffix_after = suffix_after, .common_middle = try best_common.toOwnedSlice(allocator), }; } else { @@ -547,8 +557,8 @@ fn diffBisect( // Diff took too long and hit the deadline or // number of diffs equals number of characters, no commonality at all. var diffs = DiffList{}; - try diffs.append(allocator, Diff.init(.delete, try allocator.dupe(u8, before))); - try diffs.append(allocator, Diff.init(.insert, try allocator.dupe(u8, after))); + try diffsAppend(allocator, &diffs, .delete, before); + try diffsAppend(allocator, &diffs, .insert, after); return diffs; } @@ -576,7 +586,9 @@ fn diffBisectSplit( // Compute both diffs serially. var diffs = try dmp.diffInternal(allocator, text1a, text2a, false, deadline); + errdefer deinitDiffList(allocator, &diffs); var diffsb = try dmp.diffInternal(allocator, text1b, text2b, false, deadline); + // Free the list, but not the contents: defer diffsb.deinit(allocator); try diffs.appendSlice(allocator, diffsb.items); @@ -605,7 +617,7 @@ fn diffLineMode( const line_array = a.line_array; var diffs: DiffList = try dmp.diffInternal(allocator, text1, text2, false, deadline); - + errdefer diffs.deinit(allocator); // Convert the diff back to original text. try diffCharsToLines(allocator, diffs.items, line_array.items); // Eliminate freak matches (e.g. blank lines) @@ -828,6 +840,7 @@ fn diffCleanupMerge(allocator: std.mem.Allocator, diffs: *DiffList) DiffError!vo diffs.items[ii].text = nt; } else { const text = try allocator.dupe(u8, text_insert.items[0..common_length]); + errdefer allocator.free(text); try diffs.insert(allocator, 0, Diff.init(.equal, text)); pointer += 1; } @@ -856,23 +869,18 @@ fn diffCleanupMerge(allocator: std.mem.Allocator, diffs: *DiffList) DiffError!vo } if (text_delete.items.len != 0) { - try diffs.insert(allocator, pointer, Diff.init( - .delete, - try allocator.dupe(u8, text_delete.items), - )); + try diffsInsert(allocator, diffs, pointer, .delete, text_delete.items); pointer += 1; } if (text_insert.items.len != 0) { - try diffs.insert(allocator, pointer, Diff.init( - .insert, - try allocator.dupe(u8, text_insert.items), - )); + try diffsInsert(allocator, diffs, pointer, .insert, text_insert.items); pointer += 1; } pointer += 1; } else if (pointer != 0 and diffs.items[pointer - 1].operation == .equal) { // Merge this equality with the previous one. // TODO: Fix using realloc or smth + // Note: can't use realloc because the text is const var nt = try allocator.alloc(u8, diffs.items[pointer - 1].text.len + diffs.items[pointer].text.len); const ot = diffs.items[pointer - 1].text; defer (allocator.free(ot)); @@ -991,10 +999,12 @@ fn diffCleanupSemantic(allocator: std.mem.Allocator, diffs: *DiffList) DiffError (last_equality.?.len <= @max(length_insertions2, length_deletions2))) { // Duplicate record. - try diffs.insert( + try diffsInsert( allocator, + diffs, @intCast(equalities.items[equalities.items.len - 1]), - Diff.init(.delete, try allocator.dupe(u8, last_equality.?)), + .delete, + last_equality.?, ); // Change second copy to insert. diffs.items[@intCast(equalities.items[equalities.items.len - 1] + 1)].operation = .insert; @@ -1044,10 +1054,12 @@ fn diffCleanupSemantic(allocator: std.mem.Allocator, diffs: *DiffList) DiffError // Insert an equality and trim the surrounding edits. defer allocator.free(deletion); defer allocator.free(insertion); - try diffs.insert( + try diffsInsert( allocator, + diffs, @intCast(pointer), - Diff.init(.equal, try allocator.dupe(u8, insertion[0..overlap_length1])), + .equal, + insertion[0..overlap_length1], ); diffs.items[@intCast(pointer - 1)].text = try allocator.dupe(u8, deletion[0 .. deletion.len - overlap_length1]); @@ -1063,10 +1075,12 @@ fn diffCleanupSemantic(allocator: std.mem.Allocator, diffs: *DiffList) DiffError // Insert an equality and swap and trim the surrounding edits. defer allocator.free(deletion); defer allocator.free(insertion); - try diffs.insert( + try diffsInsert( allocator, + diffs, @intCast(pointer), - Diff.init(.equal, try allocator.dupe(u8, deletion[0..overlap_length2])), + .equal, + deletion[0..overlap_length2], ); diffs.items[@intCast(pointer - 1)].operation = .insert; const new_minus = try allocator.dupe(u8, insertion[0 .. insertion.len - overlap_length2]); @@ -1307,10 +1321,12 @@ pub fn diffCleanupEfficiency( ((if (pre_ins) 1 else 0) + (if (pre_del) 1 else 0) + (if (post_ins) 1 else 0) + (if (post_del) 1 else 0)) == 3))) { // Duplicate record. - try diffs.insert( + try diffsInsert( allocator, + &diffs, equalities.items[equalities.items.len - 1], - Diff.init(.delete, try allocator.dupe(u8, last_equality)), + .delete, + last_equality, ); // Change second copy to insert. diffs.items[equalities.items[equalities.items.len - 1] + 1].operation = .insert; @@ -1388,10 +1404,20 @@ fn diffCommonOverlap(text1_in: []const u8, text2_in: []const u8) usize { } } +fn diffsAppend(allocator: Allocator, diffs: *DiffList, op: Diff.Operation, text: []const u8) !void { + const new_text = try allocator.dupe(u8, text); + errdefer allocator.free(new_text); + try diffs.append(allocator, Diff{ .operation = op, .text = new_text }); +} + +fn diffsInsert(allocator: Allocator, diffs: *DiffList, index: usize, op: Diff.Operation, text: []const u8) !void { + const new_text = try allocator.dupe(u8, text); + errdefer allocator.free(new_text); + try diffs.insert(allocator, index, Diff{ .operation = op, .text = new_text }); +} + // DONE [✅]: Allocate all text in diffs to -// not cause segfault while freeing; not a problem -// at the moment because we don't free anything :( -// (or was it??) +// not cause segfault while freeing test diffCommonPrefix { // Detect any common suffix. From 4b63907c18bf360d320a2a97efd8f6aadc70380d Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Sun, 7 Jul 2024 10:34:45 -0400 Subject: [PATCH 27/60] errdefer freeing slice appends --- DiffMatchPatch.zig | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/DiffMatchPatch.zig b/DiffMatchPatch.zig index 38c2aff..150628c 100644 --- a/DiffMatchPatch.zig +++ b/DiffMatchPatch.zig @@ -254,6 +254,14 @@ fn diffCompute( deadline, ); defer diffs_b.deinit(allocator); + // we have to deinit regardless, so deinitDiffList would be + // a double free: + errdefer { + for (diffs_b.items) |d| { + allocator.free(d.text); + } + } + (deinitDiffList(allocator, diffs_b)); // Merge the results. diffs = diffs_a; @@ -585,11 +593,15 @@ fn diffBisectSplit( // Compute both diffs serially. var diffs = try dmp.diffInternal(allocator, text1a, text2a, false, deadline); errdefer deinitDiffList(allocator, &diffs); - var diffsb = try dmp.diffInternal(allocator, text1b, text2b, false, deadline); + var diffs_b = try dmp.diffInternal(allocator, text1b, text2b, false, deadline); // Free the list, but not the contents: - defer diffsb.deinit(allocator); - - try diffs.appendSlice(allocator, diffsb.items); + defer diffs_b.deinit(allocator); + errdefer { + for (diffs_b.items) |d| { + allocator.free(d.text); + } + } + try diffs.appendSlice(allocator, diffs_b.items); return diffs; } From 83ac54fd0a8d29df61d251748688fc16a8c93509 Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Sun, 7 Jul 2024 10:37:09 -0400 Subject: [PATCH 28/60] Remove spurious line --- DiffMatchPatch.zig | 1 - 1 file changed, 1 deletion(-) diff --git a/DiffMatchPatch.zig b/DiffMatchPatch.zig index 150628c..ab556de 100644 --- a/DiffMatchPatch.zig +++ b/DiffMatchPatch.zig @@ -261,7 +261,6 @@ fn diffCompute( allocator.free(d.text); } } - (deinitDiffList(allocator, diffs_b)); // Merge the results. diffs = diffs_a; From a7ab35c0c0cbb418eb7dc9504c066d0672944cf7 Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Sun, 7 Jul 2024 11:49:48 -0400 Subject: [PATCH 29/60] Patch tests --- DiffMatchPatch.zig | 1548 ++++++++++++++++++++++++-------------------- 1 file changed, 837 insertions(+), 711 deletions(-) diff --git a/DiffMatchPatch.zig b/DiffMatchPatch.zig index ab556de..b273a63 100644 --- a/DiffMatchPatch.zig +++ b/DiffMatchPatch.zig @@ -58,6 +58,18 @@ pub const Diff = struct { pub fn eql(a: Diff, b: Diff) bool { return a.operation == b.operation and std.mem.eql(u8, a.text, b.text); } + + test eql { + const equal_a: Diff = .{ .operation = .equal, .text = "a" }; + const insert_a: Diff = .{ .operation = .insert, .text = "a" }; + const equal_b: Diff = .{ .operation = .equal, .text = "b" }; + const delete_b: Diff = .{ .operation = .delete, .text = "b" }; + + try testing.expect(equal_a.eql(equal_a)); + try testing.expect(!insert_a.eql(equal_a)); + try testing.expect(!equal_a.eql(equal_b)); + try testing.expect(!equal_a.eql(delete_b)); + } }; /// Number of milliseconds to map a diff before giving up (0 for infinity). @@ -1559,7 +1571,7 @@ test diffHalfMatch { } test diffLinesToChars { - const allocator = std.testing.allocator; + const allocator = testing.allocator; // Convert lines down to characters. var tmp_array_list = std.ArrayList([]const u8).init(allocator); defer tmp_array_list.deinit(); @@ -1633,26 +1645,14 @@ test diffLinesToChars { test diffCharsToLines { const allocator = std.testing.allocator; - const equal_a = Diff.init(.equal, try allocator.dupe(u8, "a")); - defer allocator.free(equal_a.text); - const insert_a = Diff.init(.insert, try allocator.dupe(u8, "a")); - defer allocator.free(insert_a.text); - const equal_b = Diff.init(.equal, try allocator.dupe(u8, "b")); - defer allocator.free(equal_b.text); - const delete_b = Diff.init(.delete, try allocator.dupe(u8, "b")); - defer allocator.free(delete_b.text); - try testing.expect(equal_a.eql(equal_a)); - try testing.expect(!insert_a.eql(equal_a)); - try testing.expect(!equal_a.eql(equal_b)); - try testing.expect(!equal_a.eql(delete_b)); // Convert chars up to lines. - var diffs = DiffList{}; + var diffs = try DiffList.initCapacity(allocator, 2); defer deinitDiffList(allocator, &diffs); - try diffs.appendSlice(allocator, &.{ - Diff{ .operation = .equal, .text = try allocator.dupe(u8, "\u{0001}\u{0002}\u{0001}") }, - Diff{ .operation = .insert, .text = try allocator.dupe(u8, "\u{0002}\u{0001}\u{0002}") }, - }); + + diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "\u{0001}\u{0002}\u{0001}") }); + diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "\u{0002}\u{0001}\u{0002}") }); + var tmp_vector = std.ArrayList([]const u8).init(allocator); defer tmp_vector.deinit(); try tmp_vector.append(""); @@ -1660,437 +1660,385 @@ test diffCharsToLines { try tmp_vector.append("beta\n"); try diffCharsToLines(allocator, diffs.items, tmp_vector.items); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ - Diff.init(.equal, "alpha\nbeta\nalpha\n"), - Diff.init(.insert, "beta\nalpha\nbeta\n"), - }), diffs.items); + try testing.expectEqualDeep(&[_]Diff{ + .{ .operation = .equal, .text = "alpha\nbeta\nalpha\n" }, + .{ .operation = .insert, .text = "beta\nalpha\nbeta\n" }, + }, diffs.items); // TODO: Implement exhaustive tests } test diffCleanupMerge { - const allocator = std.testing.allocator; + const allocator = testing.allocator; // Cleanup a messy diff. - var diffs = DiffList{}; - defer deinitDiffList(allocator, &diffs); - try testing.expectEqualDeep( - @as([]const Diff, &[0]Diff{}), - diffs.items, - ); // Null case - - try diffs.appendSlice(allocator, &[_]Diff{ - .{ - .operation = .equal, - .text = try allocator.dupe(u8, "a"), - }, - .{ - .operation = .delete, - .text = try allocator.dupe(u8, "b"), - }, - .{ - .operation = .insert, - .text = try allocator.dupe(u8, "c"), - }, - }); - try diffCleanupMerge(allocator, &diffs); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ .{ .operation = .equal, .text = "a" }, .{ .operation = .delete, .text = "b" }, .{ .operation = .insert, .text = "c" } }), diffs.items); // No change case - - var diffs2 = DiffList{}; - defer deinitDiffList(allocator, &diffs2); - try diffs2.appendSlice(allocator, &[_]Diff{ - .{ - .operation = .equal, - .text = try allocator.dupe(u8, "a"), - }, - .{ - .operation = .equal, - .text = try allocator.dupe(u8, "b"), - }, - .{ - .operation = .equal, - .text = try allocator.dupe(u8, "c"), - }, - }); - try diffCleanupMerge(allocator, &diffs2); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ - .{ .operation = .equal, .text = "abc" }, - }), diffs2.items); // Merge equalities - - var diffs3 = DiffList{}; - defer deinitDiffList(allocator, &diffs3); - try diffs3.appendSlice(allocator, &[_]Diff{ - .{ - .operation = .delete, - .text = try allocator.dupe(u8, "a"), - }, - .{ - .operation = .delete, - .text = try allocator.dupe(u8, "b"), - }, - .{ - .operation = .delete, - .text = try allocator.dupe(u8, "c"), - }, - }); - try diffCleanupMerge(allocator, &diffs3); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ - .{ .operation = .delete, .text = "abc" }, - }), diffs3.items); // Merge deletions - - var diffs4 = DiffList{}; - defer deinitDiffList(allocator, &diffs4); - try diffs4.appendSlice(allocator, &[_]Diff{ - .{ - .operation = .insert, - .text = try allocator.dupe(u8, "a"), - }, - .{ - .operation = .insert, - .text = try allocator.dupe(u8, "b"), - }, - .{ - .operation = .insert, - .text = try allocator.dupe(u8, "c"), - }, - }); - try diffCleanupMerge(allocator, &diffs4); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ - .{ .operation = .insert, .text = "abc" }, - }), diffs4.items); // Merge insertions - - var diffs5 = DiffList{}; - defer deinitDiffList(allocator, &diffs5); - try diffs5.appendSlice(allocator, &[_]Diff{ - .{ - .operation = .delete, - .text = try allocator.dupe(u8, "a"), - }, - .{ - .operation = .insert, - .text = try allocator.dupe(u8, "b"), - }, - .{ - .operation = .delete, - .text = try allocator.dupe(u8, "c"), - }, - .{ - .operation = .insert, - .text = try allocator.dupe(u8, "d"), - }, - .{ - .operation = .equal, - .text = try allocator.dupe(u8, "e"), - }, - .{ - .operation = .equal, - .text = try allocator.dupe(u8, "f"), - }, - }); - try diffCleanupMerge(allocator, &diffs5); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ - .{ .operation = .delete, .text = "ac" }, - .{ .operation = .insert, .text = "bd" }, - .{ .operation = .equal, .text = "ef" }, - }), diffs5.items); // Merge interweave - - var diffs6 = DiffList{}; - defer deinitDiffList(allocator, &diffs6); - try diffs6.appendSlice(allocator, &[_]Diff{ - .{ - .operation = .delete, - .text = try allocator.dupe(u8, "a"), - }, - .{ - .operation = .insert, - .text = try allocator.dupe(u8, "abc"), - }, - .{ - .operation = .delete, - .text = try allocator.dupe(u8, "dc"), - }, - }); - try diffCleanupMerge(allocator, &diffs6); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ - .{ .operation = .equal, .text = "a" }, - .{ .operation = .delete, .text = "d" }, - .{ .operation = .insert, .text = "b" }, - .{ .operation = .equal, .text = "c" }, - }), diffs6.items); // Prefix and suffix detection - - var diffs7 = DiffList{}; - defer deinitDiffList(allocator, &diffs7); - try diffs7.appendSlice(allocator, &[_]Diff{ - .{ - .operation = .equal, - .text = try allocator.dupe(u8, "x"), - }, - .{ - .operation = .delete, - .text = try allocator.dupe(u8, "a"), - }, - .{ - .operation = .insert, - .text = try allocator.dupe(u8, "abc"), - }, - .{ - .operation = .delete, - .text = try allocator.dupe(u8, "dc"), - }, - .{ - .operation = .equal, - .text = try allocator.dupe(u8, "y"), - }, - }); - try diffCleanupMerge(allocator, &diffs7); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ - .{ .operation = .equal, .text = "xa" }, - .{ .operation = .delete, .text = "d" }, - .{ .operation = .insert, .text = "b" }, - .{ .operation = .equal, .text = "cy" }, - }), diffs7.items); // Prefix and suffix detection with equalities - - var diffs8 = DiffList{}; - defer deinitDiffList(allocator, &diffs8); - try diffs8.appendSlice(allocator, &[_]Diff{ - .{ - .operation = .equal, - .text = try allocator.dupe(u8, "a"), - }, - .{ - .operation = .insert, - .text = try allocator.dupe(u8, "ba"), - }, - .{ - .operation = .equal, - .text = try allocator.dupe(u8, "c"), - }, - }); - try diffCleanupMerge(allocator, &diffs8); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ - .{ .operation = .insert, .text = "ab" }, - .{ .operation = .equal, .text = "ac" }, - }), diffs8.items); // Slide edit left - - var diffs9 = DiffList{}; - defer deinitDiffList(allocator, &diffs9); - try diffs9.appendSlice(allocator, &[_]Diff{ - .{ - .operation = .equal, - .text = try allocator.dupe(u8, "c"), - }, - .{ - .operation = .insert, - .text = try allocator.dupe(u8, "ab"), - }, - .{ - .operation = .equal, - .text = try allocator.dupe(u8, "a"), - }, - }); - try diffCleanupMerge(allocator, &diffs9); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ - .{ .operation = .equal, .text = "ca" }, - .{ .operation = .insert, .text = "ba" }, - }), diffs9.items); // Slide edit right - - var diffs10 = DiffList{}; - defer deinitDiffList(allocator, &diffs10); - try diffs10.appendSlice(allocator, &[_]Diff{ - Diff.init( - .equal, - try allocator.dupe(u8, "a"), - ), - Diff.init( - .delete, - try allocator.dupe(u8, "b"), - ), - Diff.init( - .equal, - try allocator.dupe(u8, "c"), - ), - Diff.init( - .delete, - try allocator.dupe(u8, "ac"), - ), - Diff.init( - .equal, - try allocator.dupe(u8, "x"), - ), - }); - try diffCleanupMerge(allocator, &diffs10); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ - Diff.init(.delete, "abc"), - Diff.init(.equal, "acx"), - }), diffs10.items); // Slide edit left recursive - - var diffs11 = DiffList{}; - defer deinitDiffList(allocator, &diffs11); - try diffs11.appendSlice(allocator, &[_]Diff{ - Diff.init( - .equal, - try allocator.dupe(u8, "x"), - ), - Diff.init( - .delete, - try allocator.dupe(u8, "ca"), - ), - Diff.init( - .equal, - try allocator.dupe(u8, "c"), - ), - Diff.init( - .delete, - try allocator.dupe(u8, "b"), - ), - Diff.init( - .equal, - try allocator.dupe(u8, "a"), - ), - }); - try diffCleanupMerge(allocator, &diffs11); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ - Diff.init(.equal, "xca"), - Diff.init(.delete, "cba"), - }), diffs11.items); // Slide edit right recursive - - var diffs12 = DiffList{}; - defer deinitDiffList(allocator, &diffs12); - try diffs12.appendSlice(allocator, &[_]Diff{ - Diff.init( - .delete, - try allocator.dupe(u8, "b"), - ), - Diff.init( - .insert, - try allocator.dupe(u8, "ab"), - ), - Diff.init( - .equal, - try allocator.dupe(u8, "c"), - ), - }); - try diffCleanupMerge(allocator, &diffs12); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ - Diff.init(.insert, "a"), - Diff.init(.equal, "bc"), - }), diffs12.items); // Empty merge - - var diffs13 = DiffList{}; - defer deinitDiffList(allocator, &diffs13); - try diffs13.appendSlice(allocator, &[_]Diff{ - Diff.init(.equal, ""), - Diff.init(.insert, try allocator.dupe(u8, "a")), - Diff.init(.equal, try allocator.dupe(u8, "b")), - }); - try diffCleanupMerge(allocator, &diffs13); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ - Diff.init(.insert, "a"), - Diff.init(.equal, "b"), - }), diffs13.items); // Empty equality + { + // No change case + var diffs = try DiffList.initCapacity(allocator, 3); + defer deinitDiffList(allocator, &diffs); + + diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "a") }); + diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "b") }); + diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "c") }); + + try diffCleanupMerge(allocator, &diffs); + try diffCleanupMerge(allocator, &diffs); + try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ .{ .operation = .equal, .text = "a" }, .{ .operation = .delete, .text = "b" }, .{ .operation = .insert, .text = "c" } }), diffs.items); // No change case + try diffCleanupMerge(allocator, &diffs); + try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ .{ .operation = .equal, .text = "a" }, .{ .operation = .delete, .text = "b" }, .{ .operation = .insert, .text = "c" } }), diffs.items); // No change case + + try testing.expectEqualDeep(&[_]Diff{ + .{ .operation = .equal, .text = "a" }, + .{ .operation = .delete, .text = "b" }, + .{ .operation = .insert, .text = "c" }, + }, diffs.items); + } + + { + // Merge equalities + var diffs = try DiffList.initCapacity(allocator, 3); + defer deinitDiffList(allocator, &diffs); + + diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "a") }); + diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "b") }); + diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "c") }); + + try diffCleanupMerge(allocator, &diffs); + + try testing.expectEqualDeep(&[_]Diff{ + .{ .operation = .equal, .text = "abc" }, + }, diffs.items); + } + + { + // Merge deletions + var diffs = try DiffList.initCapacity(allocator, 3); + defer deinitDiffList(allocator, &diffs); + + diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "a") }); + diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "b") }); + diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "c") }); + + try diffCleanupMerge(allocator, &diffs); + + try testing.expectEqualDeep(&[_]Diff{ + .{ .operation = .delete, .text = "abc" }, + }, diffs.items); + } + + { + + // Merge insertions + var diffs = try DiffList.initCapacity(allocator, 3); + defer deinitDiffList(allocator, &diffs); + + diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "a") }); + diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "b") }); + diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "c") }); + + try diffCleanupMerge(allocator, &diffs); + + try testing.expectEqualDeep(&[_]Diff{ + .{ .operation = .insert, .text = "abc" }, + }, diffs.items); + } + + { + // Merge interweave + var diffs = try DiffList.initCapacity(allocator, 6); + defer deinitDiffList(allocator, &diffs); + + diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "a") }); + diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "b") }); + diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "c") }); + diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "d") }); + diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "e") }); + diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "f") }); + + try diffCleanupMerge(allocator, &diffs); + + try testing.expectEqualDeep(&[_]Diff{ + .{ .operation = .delete, .text = "ac" }, + .{ .operation = .insert, .text = "bd" }, + .{ .operation = .equal, .text = "ef" }, + }, diffs.items); + } + + { + // Prefix and suffix detection + var diffs = try DiffList.initCapacity(allocator, 3); + defer deinitDiffList(allocator, &diffs); + + diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "a") }); + diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "abc") }); + diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "dc") }); + + try diffCleanupMerge(allocator, &diffs); + + try testing.expectEqualDeep(&[_]Diff{ + .{ .operation = .equal, .text = "a" }, + .{ .operation = .delete, .text = "d" }, + .{ .operation = .insert, .text = "b" }, + .{ .operation = .equal, .text = "c" }, + }, diffs.items); + } + + { + // Prefix and suffix detection with equalities + var diffs = try DiffList.initCapacity(allocator, 5); + defer deinitDiffList(allocator, &diffs); + + diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "x") }); + diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "a") }); + diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "abc") }); + diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "dc") }); + diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "y") }); + + try diffCleanupMerge(allocator, &diffs); + + try testing.expectEqualDeep(&[_]Diff{ + .{ .operation = .equal, .text = "xa" }, + .{ .operation = .delete, .text = "d" }, + .{ .operation = .insert, .text = "b" }, + .{ .operation = .equal, .text = "cy" }, + }, diffs.items); + } + + { + // Slide edit left + var diffs = try DiffList.initCapacity(allocator, 3); + defer deinitDiffList(allocator, &diffs); + + diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "a") }); + diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "ba") }); + diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "c") }); + + try diffCleanupMerge(allocator, &diffs); + + try testing.expectEqualDeep(&[_]Diff{ + .{ .operation = .insert, .text = "ab" }, + .{ .operation = .equal, .text = "ac" }, + }, diffs.items); + } + + { + + // Slide edit right + var diffs = try DiffList.initCapacity(allocator, 3); + defer deinitDiffList(allocator, &diffs); + + diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "c") }); + diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "ab") }); + diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "a") }); + + try diffCleanupMerge(allocator, &diffs); + + try testing.expectEqualDeep(&[_]Diff{ + .{ .operation = .equal, .text = "ca" }, + .{ .operation = .insert, .text = "ba" }, + }, diffs.items); + } + + { + + // Slide edit left recursive + var diffs = try DiffList.initCapacity(allocator, 5); + defer deinitDiffList(allocator, &diffs); + + diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "a") }); + diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "b") }); + diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "c") }); + diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "ac") }); + diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "x") }); + + try diffCleanupMerge(allocator, &diffs); + + try testing.expectEqualDeep(&.{ + Diff.init(.delete, "abc"), + Diff.init(.equal, "acx"), + }, diffs.items); + } + + { + // Slide edit right recursive + var diffs = try DiffList.initCapacity(allocator, 5); + defer deinitDiffList(allocator, &diffs); + + diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "x") }); + diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "ca") }); + diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "c") }); + diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "b") }); + diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "a") }); + + try diffCleanupMerge(allocator, &diffs); + + try testing.expectEqualDeep(&.{ + Diff.init(.equal, "xca"), + Diff.init(.delete, "cba"), + }, diffs.items); + } + + { + // Empty merge + var diffs = try DiffList.initCapacity(allocator, 3); + defer deinitDiffList(allocator, &diffs); + + diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "b") }); + diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "ab") }); + diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "c") }); + + try diffCleanupMerge(allocator, &diffs); + + try testing.expectEqualDeep(&.{ + Diff.init(.insert, "a"), + Diff.init(.equal, "bc"), + }, diffs.items); + } + + { + // Empty equality + var diffs = try DiffList.initCapacity(allocator, 3); + defer deinitDiffList(allocator, &diffs); + + diffs.appendAssumeCapacity(.{ .operation = .equal, .text = "" }); + diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "a") }); + diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "b") }); + + try diffCleanupMerge(allocator, &diffs); + + try testing.expectEqualDeep(&.{ + Diff.init(.insert, "a"), + Diff.init(.equal, "b"), + }, diffs.items); + } } test diffCleanupSemanticLossless { - const allocator = std.testing.allocator; - var diffs = DiffList{}; - try diffCleanupSemanticLossless(allocator, &diffs); - try testing.expectEqualDeep(@as([]const Diff, &[0]Diff{}), diffs.items); // Null case - - var diffs2 = DiffList{}; - defer deinitDiffList(allocator, &diffs2); - try diffs2.appendSlice(allocator, &.{ - Diff.init(.equal, try allocator.dupe(u8, "AAA\r\n\r\nBBB")), - Diff.init(.insert, try allocator.dupe(u8, "\r\nDDD\r\n\r\nBBB")), - Diff.init(.equal, try allocator.dupe(u8, "\r\nEEE")), - }); - try diffCleanupSemanticLossless(allocator, &diffs2); - try testing.expectEqualDeep(@as([]const Diff, &.{ - Diff.init(.equal, "AAA\r\n\r\n"), - Diff.init(.insert, "BBB\r\nDDD\r\n\r\n"), - Diff.init(.equal, "BBB\r\nEEE"), - }), diffs2.items); - - var diffs3 = DiffList{}; - defer deinitDiffList(allocator, &diffs3); - try diffs3.appendSlice(allocator, &.{ - Diff.init(.equal, try allocator.dupe(u8, "AAA\r\nBBB")), - Diff.init(.insert, try allocator.dupe(u8, " DDD\r\nBBB")), - Diff.init(.equal, try allocator.dupe(u8, " EEE")), - }); - try diffCleanupSemanticLossless(allocator, &diffs3); - try testing.expectEqualDeep(@as([]const Diff, &.{ - Diff.init(.equal, "AAA\r\n"), - Diff.init(.insert, "BBB DDD\r\n"), - Diff.init(.equal, "BBB EEE"), - }), diffs3.items); - - var diffs4 = DiffList{}; - defer deinitDiffList(allocator, &diffs4); - try diffs4.appendSlice(allocator, &.{ - Diff.init(.equal, try allocator.dupe(u8, "The c")), - Diff.init(.insert, try allocator.dupe(u8, "ow and the c")), - Diff.init(.equal, try allocator.dupe(u8, "at.")), - }); - try diffCleanupSemanticLossless(allocator, &diffs4); - try testing.expectEqualDeep(@as([]const Diff, &.{ - Diff.init(.equal, "The "), - Diff.init(.insert, "cow and the "), - Diff.init(.equal, "cat."), - }), diffs4.items); - - var diffs5 = DiffList{}; - defer deinitDiffList(allocator, &diffs5); - try diffs5.appendSlice(allocator, &.{ - Diff.init(.equal, try allocator.dupe(u8, "The-c")), - Diff.init(.insert, try allocator.dupe(u8, "ow-and-the-c")), - Diff.init(.equal, try allocator.dupe(u8, "at.")), - }); - try diffCleanupSemanticLossless(allocator, &diffs5); - try testing.expectEqualDeep(@as([]const Diff, &.{ - Diff.init(.equal, "The-"), - Diff.init(.insert, "cow-and-the-"), - Diff.init(.equal, "cat."), - }), diffs5.items); - - var diffs6 = DiffList{}; - defer deinitDiffList(allocator, &diffs6); - try diffs6.appendSlice(allocator, &.{ - Diff.init(.equal, try allocator.dupe(u8, "a")), - Diff.init(.delete, try allocator.dupe(u8, "a")), - Diff.init(.equal, try allocator.dupe(u8, "ax")), - }); - try diffCleanupSemanticLossless(allocator, &diffs6); - try testing.expectEqualDeep(@as([]const Diff, &.{ - Diff.init(.delete, "a"), - Diff.init(.equal, "aax"), - }), diffs6.items); - - var diffs7 = DiffList{}; - defer deinitDiffList(allocator, &diffs7); - try diffs7.appendSlice(allocator, &.{ - Diff.init(.equal, try allocator.dupe(u8, "xa")), - Diff.init(.delete, try allocator.dupe(u8, "a")), - Diff.init(.equal, try allocator.dupe(u8, "a")), - }); - try diffCleanupSemanticLossless(allocator, &diffs7); - try testing.expectEqualDeep(@as([]const Diff, &.{ - Diff.init(.equal, "xaa"), - Diff.init(.delete, "a"), - }), diffs7.items); - - var diffs8 = DiffList{}; - defer deinitDiffList(allocator, &diffs8); - try diffs8.appendSlice(allocator, &.{ - Diff.init(.equal, try allocator.dupe(u8, "The xxx. The ")), - Diff.init(.insert, try allocator.dupe(u8, "zzz. The ")), - Diff.init(.equal, try allocator.dupe(u8, "yyy.")), - }); - try diffCleanupSemanticLossless(allocator, &diffs8); - try testing.expectEqualDeep(@as([]const Diff, &.{ - Diff.init(.equal, "The xxx."), - Diff.init(.insert, " The zzz."), - Diff.init(.equal, " The yyy."), - }), diffs8.items); + const allocator = testing.allocator; + + { + // Null case + var diffs: DiffList = .{}; + try diffCleanupSemanticLossless(allocator, &diffs); + try testing.expectEqualDeep(&[_]Diff{}, diffs.items); + } + + { + var diffs = try DiffList.initCapacity(allocator, 3); + defer deinitDiffList(allocator, &diffs); + + diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "AAA\r\n\r\nBBB") }); + diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "\r\nDDD\r\n\r\nBBB") }); + diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "\r\nEEE") }); + + try diffCleanupSemanticLossless(allocator, &diffs); + + try testing.expectEqualDeep(&[_]Diff{ + Diff.init(.equal, "AAA\r\n\r\n"), + Diff.init(.insert, "BBB\r\nDDD\r\n\r\n"), + Diff.init(.equal, "BBB\r\nEEE"), + }, diffs.items); + } + + { + var diffs = try DiffList.initCapacity(allocator, 3); + defer deinitDiffList(allocator, &diffs); + + diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "AAA\r\nBBB") }); + diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, " DDD\r\nBBB") }); + diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, " EEE") }); + + try diffCleanupSemanticLossless(allocator, &diffs); + + try testing.expectEqualDeep(&[_]Diff{ + Diff.init(.equal, "AAA\r\n"), + Diff.init(.insert, "BBB DDD\r\n"), + Diff.init(.equal, "BBB EEE"), + }, diffs.items); + } + + { + var diffs = try DiffList.initCapacity(allocator, 3); + defer deinitDiffList(allocator, &diffs); + + diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "The c") }); + diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "ow and the c") }); + diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "at.") }); + + try diffCleanupSemanticLossless(allocator, &diffs); + + try testing.expectEqualDeep(&[_]Diff{ + Diff.init(.equal, "The "), + Diff.init(.insert, "cow and the "), + Diff.init(.equal, "cat."), + }, diffs.items); + } + + { + var diffs = try DiffList.initCapacity(allocator, 3); + defer deinitDiffList(allocator, &diffs); + + diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "The-c") }); + diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "ow-and-the-c") }); + diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "at.") }); + + try diffCleanupSemanticLossless(allocator, &diffs); + + try testing.expectEqualDeep(&[_]Diff{ + Diff.init(.equal, "The-"), + Diff.init(.insert, "cow-and-the-"), + Diff.init(.equal, "cat."), + }, diffs.items); + } + + { + var diffs = try DiffList.initCapacity(allocator, 3); + defer deinitDiffList(allocator, &diffs); + + diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "a") }); + diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "a") }); + diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "ax") }); + + try diffCleanupSemanticLossless(allocator, &diffs); + + try testing.expectEqualDeep(&[_]Diff{ + Diff.init(.delete, "a"), + Diff.init(.equal, "aax"), + }, diffs.items); + } + + { + var diffs = try DiffList.initCapacity(allocator, 3); + defer deinitDiffList(allocator, &diffs); + + diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "xa") }); + diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "a") }); + diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "a") }); + + try diffCleanupSemanticLossless(allocator, &diffs); + + try testing.expectEqualDeep(&[_]Diff{ + Diff.init(.equal, "xaa"), + Diff.init(.delete, "a"), + }, diffs.items); + } + + { + var diffs = try DiffList.initCapacity(allocator, 3); + defer deinitDiffList(allocator, &diffs); + + diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "The xxx. The ") }); + diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "zzz. The ") }); + diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "yyy.") }); + + try diffCleanupSemanticLossless(allocator, &diffs); + + try testing.expectEqualDeep(&[_]Diff{ + Diff.init(.equal, "The xxx."), + Diff.init(.insert, " The zzz."), + Diff.init(.equal, " The yyy."), + }, diffs.items); + } } fn rebuildtexts(allocator: std.mem.Allocator, diffs: DiffList) ![2][]const u8 { @@ -2114,331 +2062,509 @@ fn rebuildtexts(allocator: std.mem.Allocator, diffs: DiffList) ![2][]const u8 { } test diffBisect { - const allocator = std.testing.allocator; - // Normal. + const allocator = testing.allocator; + + const this: DiffMatchPatch = .{ .diff_timeout = 0 }; + const a = "cat"; const b = "map"; - // Since the resulting diff hasn't been normalized, it would be ok if - // the insertion and deletion pairs are swapped. - // If the order changes, tweak this test as required. - var diffs = DiffList{}; - defer deinitDiffList(allocator, &diffs); - var this = default; - try diffs.appendSlice(allocator, &.{ - Diff.init(.delete, try allocator.dupe(u8, "c")), - Diff.init(.insert, try allocator.dupe(u8, "m")), - Diff.init(.equal, try allocator.dupe(u8, "a")), - Diff.init(.delete, try allocator.dupe(u8, "t")), - Diff.init(.insert, try allocator.dupe(u8, "p")), - }); - // Travis TODO not sure if maxInt(u64) is correct for DateTime.MaxValue - var diff_bisect = try this.diffBisect( - allocator, - a, - b, - std.math.maxInt(u64), - ); - defer deinitDiffList(allocator, &diff_bisect); - try testing.expectEqualDeep(diffs, diff_bisect); // Normal. - - // Timeout. - var diffs2 = DiffList{}; - defer deinitDiffList(allocator, &diffs2); - try diffs2.appendSlice(allocator, &.{ - Diff.init(.delete, try allocator.dupe(u8, "cat")), - Diff.init(.insert, try allocator.dupe(u8, "map")), - }); - // Travis TODO not sure if 0 is correct for DateTime.MinValue - var diff_bisect2 = try this.diffBisect(allocator, a, b, 0); - defer deinitDiffList(allocator, &diff_bisect2); - try testing.expectEqualDeep(diffs2, diff_bisect2); // Timeout. -} -const talloc = testing.allocator; + { + // Normal. + + // Since the resulting diff hasn't been normalized, it would be ok if + // the insertion and deletion pairs are swapped. + // If the order changes, tweak this test as required. + // Travis TODO not sure if maxInt(u64) is correct for DateTime.MaxValue + var diffs = try this.diffBisect( + allocator, + a, + b, + std.math.maxInt(u64), + ); + defer deinitDiffList(allocator, &diffs); + + try testing.expectEqualDeep(&[_]Diff{ + .{ .operation = .delete, .text = "c" }, + .{ .operation = .insert, .text = "m" }, + .{ .operation = .equal, .text = "a" }, + .{ .operation = .delete, .text = "t" }, + .{ .operation = .insert, .text = "p" }, + }, diffs.items); + } + + { + // Timeout. + var diffs2 = DiffList{}; + defer deinitDiffList(allocator, &diffs2); + try diffs2.appendSlice(allocator, &.{ + Diff.init(.delete, try allocator.dupe(u8, "cat")), + Diff.init(.insert, try allocator.dupe(u8, "map")), + }); + // Travis TODO not sure if 0 is correct for DateTime.MinValue + var diffs = try this.diffBisect(allocator, a, b, 0); + defer deinitDiffList(allocator, &diffs); + + try testing.expectEqualDeep(&[_]Diff{ + .{ .operation = .delete, .text = "cat" }, + .{ .operation = .insert, .text = "map" }, + }, diffs.items); + } +} test diff { - var arena = std.heap.ArenaAllocator.init(talloc); - defer arena.deinit(); - const allocator = std.testing.allocator; + const allocator = testing.allocator; - // Perform a trivial diff. - var diffs = DiffList{}; - defer diffs.deinit(arena.allocator()); - var this = DiffMatchPatch{}; - try testing.expectEqualDeep(diffs.items, (try this.diff(arena.allocator(), "", "", false)).items); // diff: Null case. + const this: DiffMatchPatch = .{ .diff_timeout = 0 }; - // TODO This is the last set of tests using the arena. Someone should - // rewrite them not to do so. -Sam - diffs.items.len = 0; - try diffs.appendSlice(arena.allocator(), &.{Diff.init(.equal, "abc")}); - try testing.expectEqualDeep(diffs.items, (try this.diff(arena.allocator(), "abc", "abc", false)).items); // diff: Equality. + { + // diff: Null case. + var diffs = try this.diff(allocator, "", "", false); + defer deinitDiffList(allocator, &diffs); - diffs.items.len = 0; - try diffs.appendSlice(arena.allocator(), &.{ Diff.init(.equal, "ab"), Diff.init(.insert, "123"), Diff.init(.equal, "c") }); - try testing.expectEqualDeep(diffs.items, (try this.diff(arena.allocator(), "abc", "ab123c", false)).items); // diff: Simple insertion. + try testing.expectEqual(0, diffs.items.len); + } - diffs.items.len = 0; - try diffs.appendSlice(arena.allocator(), &.{ Diff.init(.equal, "a"), Diff.init(.delete, "123"), Diff.init(.equal, "bc") }); - try testing.expectEqualDeep(diffs.items, (try this.diff(arena.allocator(), "a123bc", "abc", false)).items); // diff: Simple deletion. + { + // diff: Equality. + var diffs = try this.diff(allocator, "abc", "abc", false); + defer deinitDiffList(allocator, &diffs); - diffs.items.len = 0; - try diffs.appendSlice(arena.allocator(), &.{ Diff.init(.equal, "a"), Diff.init(.insert, "123"), Diff.init(.equal, "b"), Diff.init(.insert, "456"), Diff.init(.equal, "c") }); - try testing.expectEqualDeep(diffs.items, (try this.diff(arena.allocator(), "abc", "a123b456c", false)).items); // diff: Two insertions. + try testing.expectEqualDeep(&[_]Diff{ + .{ .operation = .equal, .text = "abc" }, + }, diffs.items); + } - diffs.items.len = 0; - try diffs.appendSlice(arena.allocator(), &.{ Diff.init(.equal, "a"), Diff.init(.delete, "123"), Diff.init(.equal, "b"), Diff.init(.delete, "456"), Diff.init(.equal, "c") }); - try testing.expectEqualDeep(diffs.items, (try this.diff(arena.allocator(), "a123b456c", "abc", false)).items); // diff: Two deletions. + { + // diff: Simple insertion. + var diffs = try this.diff(allocator, "abc", "ab123c", false); + defer deinitDiffList(allocator, &diffs); + + try testing.expectEqualDeep(&[_]Diff{ + .{ .operation = .equal, .text = "ab" }, + .{ .operation = .insert, .text = "123" }, + .{ .operation = .equal, .text = "c" }, + }, diffs.items); + } - // Perform a real diff. - // Switch off the timeout. - this.diff_timeout = 0; - diffs.items.len = 0; - try diffs.appendSlice(arena.allocator(), &.{ Diff.init(.delete, "a"), Diff.init(.insert, "b") }); - try testing.expectEqualDeep(diffs.items, (try this.diff(arena.allocator(), "a", "b", false)).items); // diff: Simple case #1. + { + // diff: Simple deletion. + var diffs = try this.diff(allocator, "a123bc", "abc", false); + defer deinitDiffList(allocator, &diffs); + + try testing.expectEqualDeep(&[_]Diff{ + .{ .operation = .equal, .text = "a" }, + .{ .operation = .delete, .text = "123" }, + .{ .operation = .equal, .text = "bc" }, + }, diffs.items); + } - diffs.items.len = 0; - try diffs.appendSlice(arena.allocator(), &.{ Diff.init(.delete, "Apple"), Diff.init(.insert, "Banana"), Diff.init(.equal, "s are a"), Diff.init(.insert, "lso"), Diff.init(.equal, " fruit.") }); - try testing.expectEqualDeep(diffs.items, (try this.diff(arena.allocator(), "Apples are a fruit.", "Bananas are also fruit.", false)).items); // diff: Simple case #2. + { + // diff: Two insertions. + var diffs = try this.diff(allocator, "abc", "a123b456c", false); + defer deinitDiffList(allocator, &diffs); + + try testing.expectEqualDeep(&[_]Diff{ + .{ .operation = .equal, .text = "a" }, + .{ .operation = .insert, .text = "123" }, + .{ .operation = .equal, .text = "b" }, + .{ .operation = .insert, .text = "456" }, + .{ .operation = .equal, .text = "c" }, + }, diffs.items); + } - diffs.items.len = 0; - try diffs.appendSlice(arena.allocator(), &.{ Diff.init(.delete, "a"), Diff.init(.insert, "\u{0680}"), Diff.init(.equal, "x"), Diff.init(.delete, "\t"), Diff.init(.insert, "\x00") }); - try testing.expectEqualDeep(diffs.items, (try this.diff(arena.allocator(), "ax\t", "\u{0680}x\x00", false)).items); // diff: Simple case #3. + { + // diff: Two deletions. + var diffs = try this.diff(allocator, "a123b456c", "abc", false); + defer deinitDiffList(allocator, &diffs); + + try testing.expectEqualDeep(&[_]Diff{ + .{ .operation = .equal, .text = "a" }, + .{ .operation = .delete, .text = "123" }, + .{ .operation = .equal, .text = "b" }, + .{ .operation = .delete, .text = "456" }, + .{ .operation = .equal, .text = "c" }, + }, diffs.items); + } + + // Perform a real diff. + { + // diff: Simple case #1. + var diffs = try this.diff(allocator, "a", "b", false); + defer deinitDiffList(allocator, &diffs); + + try testing.expectEqualDeep(&[_]Diff{ + .{ .operation = .delete, .text = "a" }, + .{ .operation = .insert, .text = "b" }, + }, diffs.items); + } - diffs.items.len = 0; - try diffs.appendSlice(arena.allocator(), &.{ Diff.init(.delete, "1"), Diff.init(.equal, "a"), Diff.init(.delete, "y"), Diff.init(.equal, "b"), Diff.init(.delete, "2"), Diff.init(.insert, "xab") }); - try testing.expectEqualDeep(diffs.items, (try this.diff(arena.allocator(), "1ayb2", "abxab", false)).items); // diff: Overlap #1. + { + // diff: Simple case #2. + var diffs = try this.diff(allocator, "Apples are a fruit.", "Bananas are also fruit.", false); + defer deinitDiffList(allocator, &diffs); + + try testing.expectEqualDeep(&[_]Diff{ + .{ .operation = .delete, .text = "Apple" }, + .{ .operation = .insert, .text = "Banana" }, + .{ .operation = .equal, .text = "s are a" }, + .{ .operation = .insert, .text = "lso" }, + .{ .operation = .equal, .text = " fruit." }, + }, diffs.items); + } - diffs.items.len = 0; - try diffs.appendSlice(arena.allocator(), &.{ Diff.init(.insert, "xaxcx"), Diff.init(.equal, "abc"), Diff.init(.delete, "y") }); - try testing.expectEqualDeep(diffs.items, (try this.diff(arena.allocator(), "abcy", "xaxcxabc", false)).items); // diff: Overlap #2. + { + // diff: Simple case #3. + var diffs = try this.diff(allocator, "ax\t", "\u{0680}x\x00", false); + defer deinitDiffList(allocator, &diffs); + + try testing.expectEqualDeep(&[_]Diff{ + .{ .operation = .delete, .text = "a" }, + .{ .operation = .insert, .text = "\u{0680}" }, + .{ .operation = .equal, .text = "x" }, + .{ .operation = .delete, .text = "\t" }, + .{ .operation = .insert, .text = "\x00" }, + }, diffs.items); + } - diffs.items.len = 0; - try diffs.appendSlice(arena.allocator(), &.{ Diff.init(.delete, "ABCD"), Diff.init(.equal, "a"), Diff.init(.delete, "="), Diff.init(.insert, "-"), Diff.init(.equal, "bcd"), Diff.init(.delete, "="), Diff.init(.insert, "-"), Diff.init(.equal, "efghijklmnopqrs"), Diff.init(.delete, "EFGHIJKLMNOefg") }); - try testing.expectEqualDeep(diffs.items, (try this.diff(arena.allocator(), "ABCDa=bcd=efghijklmnopqrsEFGHIJKLMNOefg", "a-bcd-efghijklmnopqrs", false)).items); // diff: Overlap #3. + { + // diff: Overlap #1. + var diffs = try this.diff(allocator, "1ayb2", "abxab", false); + defer deinitDiffList(allocator, &diffs); + + try testing.expectEqualDeep(&[_]Diff{ + .{ .operation = .delete, .text = "1" }, + .{ .operation = .equal, .text = "a" }, + .{ .operation = .delete, .text = "y" }, + .{ .operation = .equal, .text = "b" }, + .{ .operation = .delete, .text = "2" }, + .{ .operation = .insert, .text = "xab" }, + }, diffs.items); + } - diffs.items.len = 0; - try diffs.appendSlice(arena.allocator(), &.{ Diff.init(.insert, " "), Diff.init(.equal, "a"), Diff.init(.insert, "nd"), Diff.init(.equal, " [[Pennsylvania]]"), Diff.init(.delete, " and [[New") }); - try testing.expectEqualDeep(diffs.items, (try this.diff(arena.allocator(), "a [[Pennsylvania]] and [[New", " and [[Pennsylvania]]", false)).items); // diff: Large equality. + { + // diff: Overlap #2. + var diffs = try this.diff(allocator, "abcy", "xaxcxabc", false); + defer deinitDiffList(allocator, &diffs); + + try testing.expectEqualDeep(&[_]Diff{ + .{ .operation = .insert, .text = "xaxcx" }, + .{ .operation = .equal, .text = "abc" }, + .{ .operation = .delete, .text = "y" }, + }, diffs.items); + } - // end of Arena Zone + { + // diff: Overlap #3. + var diffs = try this.diff(allocator, "ABCDa=bcd=efghijklmnopqrsEFGHIJKLMNOefg", "a-bcd-efghijklmnopqrs", false); + defer deinitDiffList(allocator, &diffs); + + try testing.expectEqualDeep(&[_]Diff{ + .{ .operation = .delete, .text = "ABCD" }, + .{ .operation = .equal, .text = "a" }, + .{ .operation = .delete, .text = "=" }, + .{ .operation = .insert, .text = "-" }, + .{ .operation = .equal, .text = "bcd" }, + .{ .operation = .delete, .text = "=" }, + .{ .operation = .insert, .text = "-" }, + .{ .operation = .equal, .text = "efghijklmnopqrs" }, + .{ .operation = .delete, .text = "EFGHIJKLMNOefg" }, + }, diffs.items); + } + + { + // diff: Large equality. + var diffs = try this.diff(allocator, "a [[Pennsylvania]] and [[New", " and [[Pennsylvania]]", false); + defer deinitDiffList(allocator, &diffs); + + try testing.expectEqualDeep(&[_]Diff{ + .{ .operation = .insert, .text = " " }, + .{ .operation = .equal, .text = "a" }, + .{ .operation = .insert, .text = "nd" }, + .{ .operation = .equal, .text = " [[Pennsylvania]]" }, + .{ .operation = .delete, .text = " and [[New" }, + }, diffs.items); + } - this.diff_timeout = 100; // 100ms // Increase the text lengths by 1024 times to ensure a timeout. { const a = "`Twas brillig, and the slithy toves\nDid gyre and gimble in the wabe:\nAll mimsy were the borogoves,\nAnd the mome raths outgrabe.\n" ** 1024; const b = "I am the very model of a modern major general,\nI've information vegetable, animal, and mineral,\nI know the kings of England, and I quote the fights historical,\nFrom Marathon to Waterloo, in order categorical.\n" ** 1024; + + const with_timout: DiffMatchPatch = .{ + .diff_timeout = 100, // 100ms + }; + const start_time = std.time.milliTimestamp(); - var time_diff = try this.diff(allocator, a, b, false); - defer deinitDiffList(allocator, &time_diff); + { + var time_diff = try with_timout.diff(allocator, a, b, false); + defer deinitDiffList(allocator, &time_diff); + } const end_time = std.time.milliTimestamp(); + // Test that we took at least the timeout period. - try testing.expect(this.diff_timeout <= end_time - start_time); // diff: Timeout min. + try testing.expect(with_timout.diff_timeout <= end_time - start_time); // diff: Timeout min. // Test that we didn't take forever (be forgiving). // Theoretically this test could fail very occasionally if the // OS task swaps or locks up for a second at the wrong moment. - try testing.expect((this.diff_timeout) * 10000 * 2 > end_time - start_time); // diff: Timeout max. - this.diff_timeout = 0; + try testing.expect((with_timout.diff_timeout) * 10000 * 2 > end_time - start_time); // diff: Timeout max. } + { // Test the linemode speedup. // Must be long to pass the 100 char cutoff. const a = "1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n"; const b = "abcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\n"; + var diff_checked = try this.diff(allocator, a, b, true); defer deinitDiffList(allocator, &diff_checked); + var diff_unchecked = try this.diff(allocator, a, b, false); defer deinitDiffList(allocator, &diff_unchecked); - try testing.expectEqualDeep(diff_checked, diff_unchecked); // diff: Simple line-mode. + + try testing.expectEqualDeep(diff_checked.items, diff_unchecked.items); // diff: Simple line-mode. } + { const a = "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"; const b = "abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghij"; + var diff_checked = try this.diff(allocator, a, b, true); defer deinitDiffList(allocator, &diff_checked); + var diff_unchecked = try this.diff(allocator, a, b, false); defer deinitDiffList(allocator, &diff_unchecked); - try testing.expectEqualDeep(diff_checked, diff_unchecked); // diff: Single line-mode. - } - const a = "1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n"; - const b = "abcdefghij\n1234567890\n1234567890\n1234567890\nabcdefghij\n1234567890\n1234567890\n1234567890\nabcdefghij\n1234567890\n1234567890\n1234567890\nabcdefghij\n"; - var diffs_linemode = try this.diff(allocator, a, b, true); - defer deinitDiffList(allocator, &diffs_linemode); - const texts_linemode = try rebuildtexts(allocator, diffs_linemode); - defer { - allocator.free(texts_linemode[0]); - allocator.free(texts_linemode[1]); + try testing.expectEqualDeep(diff_checked.items, diff_unchecked.items); // diff: Single line-mode. } - var diffs_textmode = try this.diff(allocator, a, b, false); - defer deinitDiffList(allocator, &diffs_textmode); - const texts_textmode = try rebuildtexts(allocator, diffs_textmode); - defer { - allocator.free(texts_textmode[0]); - allocator.free(texts_textmode[1]); + + { + // diff: Overlap line-mode. + const a = "1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n"; + const b = "abcdefghij\n1234567890\n1234567890\n1234567890\nabcdefghij\n1234567890\n1234567890\n1234567890\nabcdefghij\n1234567890\n1234567890\n1234567890\nabcdefghij\n"; + + var diffs_linemode = try this.diff(allocator, a, b, true); + defer deinitDiffList(allocator, &diffs_linemode); + + const texts_linemode = try rebuildtexts(allocator, diffs_linemode); + defer { + allocator.free(texts_linemode[0]); + allocator.free(texts_linemode[1]); + } + + var diffs_textmode = try this.diff(allocator, a, b, false); + defer deinitDiffList(allocator, &diffs_textmode); + + const texts_textmode = try rebuildtexts(allocator, diffs_textmode); + defer { + allocator.free(texts_textmode[0]); + allocator.free(texts_textmode[1]); + } + + try testing.expectEqualStrings(texts_textmode[0], texts_linemode[0]); + try testing.expectEqualStrings(texts_textmode[1], texts_linemode[1]); } - try testing.expectEqualDeep(texts_textmode, texts_linemode); // diff: Overlap line-mode. } test diffCleanupSemantic { - const alloc = std.testing.allocator; - // Cleanup semantically trivial equalities. - // Null case. - var diffs_empty = DiffList{}; - defer deinitDiffList(alloc, &diffs_empty); - // var this = default; - try diffCleanupSemantic(alloc, &diffs_empty); - try testing.expectEqual(@as(usize, 0), diffs_empty.items.len); // Null case + const allocator = testing.allocator; - var diffs = DiffList{}; - defer deinitDiffList(alloc, &diffs); - diffs.items.len = 0; - try diffs.appendSlice(alloc, &.{ - Diff.init(.delete, try alloc.dupe(u8, "ab")), - Diff.init(.insert, try alloc.dupe(u8, "cd")), - Diff.init(.equal, try alloc.dupe(u8, "12")), - Diff.init(.delete, try alloc.dupe(u8, "e")), - }); - try diffCleanupSemantic(alloc, &diffs); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // No elimination #1 - Diff.init(.delete, "ab"), - Diff.init(.insert, "cd"), - Diff.init(.equal, "12"), - Diff.init(.delete, "e"), - }), diffs.items); - - var diffs2 = DiffList{}; - defer deinitDiffList(alloc, &diffs2); - diffs2.items.len = 0; - try diffs2.appendSlice(alloc, &.{ - Diff.init(.delete, try alloc.dupe(u8, "abc")), - Diff.init(.insert, try alloc.dupe(u8, "ABC")), - Diff.init(.equal, try alloc.dupe(u8, "1234")), - Diff.init(.delete, try alloc.dupe(u8, "wxyz")), - }); - try diffCleanupSemantic(alloc, &diffs2); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // No elimination #2 - Diff.init(.delete, "abc"), - Diff.init(.insert, "ABC"), - Diff.init(.equal, "1234"), - Diff.init(.delete, "wxyz"), - }), diffs2.items); - - var diffs3 = DiffList{}; - defer deinitDiffList(alloc, &diffs3); - try diffs3.appendSlice(alloc, &.{ - Diff.init(.delete, try alloc.dupe(u8, "a")), - Diff.init(.equal, try alloc.dupe(u8, "b")), - Diff.init(.delete, try alloc.dupe(u8, "c")), - }); - try diffCleanupSemantic(alloc, &diffs3); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // Simple elimination - Diff.init(.delete, "abc"), - Diff.init(.insert, "b"), - }), diffs3.items); - - var diffs4 = DiffList{}; - defer deinitDiffList(alloc, &diffs4); - try diffs4.appendSlice(alloc, &.{ - Diff.init(.delete, try alloc.dupe(u8, "ab")), - Diff.init(.equal, try alloc.dupe(u8, "cd")), - Diff.init(.delete, try alloc.dupe(u8, "e")), - Diff.init(.equal, try alloc.dupe(u8, "f")), - Diff.init(.insert, try alloc.dupe(u8, "g")), - }); - try diffCleanupSemantic(alloc, &diffs4); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // Backpass elimination - Diff.init(.delete, "abcdef"), - Diff.init(.insert, "cdfg"), - }), diffs4.items); - - var diffs5 = DiffList{}; - defer deinitDiffList(alloc, &diffs5); - try diffs5.appendSlice(alloc, &.{ - Diff.init(.insert, try alloc.dupe(u8, "1")), - Diff.init(.equal, try alloc.dupe(u8, "A")), - Diff.init(.delete, try alloc.dupe(u8, "B")), - Diff.init(.insert, try alloc.dupe(u8, "2")), - Diff.init(.equal, try alloc.dupe(u8, "_")), - Diff.init(.insert, try alloc.dupe(u8, "1")), - Diff.init(.equal, try alloc.dupe(u8, "A")), - Diff.init(.delete, try alloc.dupe(u8, "B")), - Diff.init(.insert, try alloc.dupe(u8, "2")), - }); - try diffCleanupSemantic(alloc, &diffs5); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // Multiple elimination - Diff.init(.delete, "AB_AB"), - Diff.init(.insert, "1A2_1A2"), - }), diffs5.items); - - var diffs6 = DiffList{}; - defer deinitDiffList(alloc, &diffs6); - try diffs6.appendSlice(alloc, &.{ - Diff.init(.equal, try alloc.dupe(u8, "The c")), - Diff.init(.delete, try alloc.dupe(u8, "ow and the c")), - Diff.init(.equal, try alloc.dupe(u8, "at.")), - }); - try diffCleanupSemantic(alloc, &diffs6); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // Word boundaries - Diff.init(.equal, "The "), - Diff.init(.delete, "cow and the "), - Diff.init(.equal, "cat."), - }), diffs6.items); - - var diffs7 = DiffList{}; - defer deinitDiffList(alloc, &diffs7); - try diffs7.appendSlice(alloc, &.{ - Diff.init(.delete, try alloc.dupe(u8, "abcxx")), - Diff.init(.insert, try alloc.dupe(u8, "xxdef")), - }); - try diffCleanupSemantic(alloc, &diffs7); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // No overlap elimination - Diff.init(.delete, "abcxx"), - Diff.init(.insert, "xxdef"), - }), diffs7.items); - - var diffs8 = DiffList{}; - defer deinitDiffList(alloc, &diffs8); - try diffs8.appendSlice(alloc, &.{ - Diff.init(.delete, try alloc.dupe(u8, "abcxxx")), - Diff.init(.insert, try alloc.dupe(u8, "xxxdef")), - }); - try diffCleanupSemantic(alloc, &diffs8); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // Overlap elimination - Diff.init(.delete, "abc"), - Diff.init(.equal, "xxx"), - Diff.init(.insert, "def"), - }), diffs8.items); - - var diffs9 = DiffList{}; - defer deinitDiffList(alloc, &diffs9); - try diffs9.appendSlice(alloc, &.{ - Diff.init(.delete, try alloc.dupe(u8, "xxxabc")), - Diff.init(.insert, try alloc.dupe(u8, "defxxx")), - }); - try diffCleanupSemantic(alloc, &diffs9); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // Reverse overlap elimination - Diff.init(.insert, "def"), - Diff.init(.equal, "xxx"), - Diff.init(.delete, "abc"), - }), diffs9.items); - - var diffs10 = DiffList{}; - defer deinitDiffList(alloc, &diffs10); - try diffs10.appendSlice(alloc, &.{ - Diff.init(.delete, try alloc.dupe(u8, "abcd1212")), - Diff.init(.insert, try alloc.dupe(u8, "1212efghi")), - Diff.init(.equal, try alloc.dupe(u8, "----")), - Diff.init(.delete, try alloc.dupe(u8, "A3")), - Diff.init(.insert, try alloc.dupe(u8, "3BC")), - }); - try diffCleanupSemantic(alloc, &diffs10); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ // Two overlap eliminations - Diff.init(.delete, "abcd"), - Diff.init(.equal, "1212"), - Diff.init(.insert, "efghi"), - Diff.init(.equal, "----"), - Diff.init(.delete, "A"), - Diff.init(.equal, "3"), - Diff.init(.insert, "BC"), - }), diffs10.items); + { + // Null case. + var diffs: DiffList = .{}; + defer deinitDiffList(allocator, &diffs); + try diffCleanupSemantic(allocator, &diffs); + try testing.expectEqual(@as(usize, 0), diffs.items.len); // Null case + } + + { + // No elimination #1 + var diffs = try DiffList.initCapacity(allocator, 4); + defer deinitDiffList(allocator, &diffs); + + diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "ab") }); + diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "cd") }); + diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "12") }); + diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "e") }); + + try diffCleanupSemantic(allocator, &diffs); + + try testing.expectEqualDeep(&[_]Diff{ + .{ .operation = .delete, .text = "ab" }, + .{ .operation = .insert, .text = "cd" }, + .{ .operation = .equal, .text = "12" }, + .{ .operation = .delete, .text = "e" }, + }, diffs.items); + } + + { + // No elimination #2 + var diffs = try DiffList.initCapacity(allocator, 4); + defer deinitDiffList(allocator, &diffs); + + diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "abc") }); + diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "ABC") }); + diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "1234") }); + diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "wxyz") }); + + try diffCleanupSemantic(allocator, &diffs); + + try testing.expectEqualDeep(&[_]Diff{ + .{ .operation = .delete, .text = "abc" }, + .{ .operation = .insert, .text = "ABC" }, + .{ .operation = .equal, .text = "1234" }, + .{ .operation = .delete, .text = "wxyz" }, + }, diffs.items); + } + + { + // Simple elimination + var diffs = try DiffList.initCapacity(allocator, 3); + defer deinitDiffList(allocator, &diffs); + + diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "a") }); + diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "b") }); + diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "c") }); + + try diffCleanupSemantic(allocator, &diffs); + + try testing.expectEqualDeep(&[_]Diff{ + .{ .operation = .delete, .text = "abc" }, + .{ .operation = .insert, .text = "b" }, + }, diffs.items); + } + + { + // Backpass elimination + var diffs = try DiffList.initCapacity(allocator, 5); + defer deinitDiffList(allocator, &diffs); + + diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "ab") }); + diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "cd") }); + diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "e") }); + diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "f") }); + diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "g") }); + + try diffCleanupSemantic(allocator, &diffs); + + try testing.expectEqualDeep(&[_]Diff{ + .{ .operation = .delete, .text = "abcdef" }, + .{ .operation = .insert, .text = "cdfg" }, + }, diffs.items); + } + + { + // Multiple elimination + var diffs = try DiffList.initCapacity(allocator, 9); + defer deinitDiffList(allocator, &diffs); + + diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "1") }); + diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "A") }); + diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "B") }); + diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "2") }); + diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "_") }); + diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "1") }); + diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "A") }); + diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "B") }); + diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "2") }); + + try diffCleanupSemantic(allocator, &diffs); + + try testing.expectEqualDeep(&[_]Diff{ + .{ .operation = .delete, .text = "AB_AB" }, + .{ .operation = .insert, .text = "1A2_1A2" }, + }, diffs.items); + } + + { + // Word boundaries + var diffs = try DiffList.initCapacity(allocator, 3); + defer deinitDiffList(allocator, &diffs); + + diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "The c") }); + diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "ow and the c") }); + diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "at.") }); + + try diffCleanupSemantic(allocator, &diffs); + + try testing.expectEqualDeep(&[_]Diff{ + .{ .operation = .equal, .text = "The " }, + .{ .operation = .delete, .text = "cow and the " }, + .{ .operation = .equal, .text = "cat." }, + }, diffs.items); + } + + { + // No overlap elimination + var diffs = try DiffList.initCapacity(allocator, 2); + defer deinitDiffList(allocator, &diffs); + + diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "abcxx") }); + diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "xxdef") }); + + try diffCleanupSemantic(allocator, &diffs); + + try testing.expectEqualDeep(&[_]Diff{ + .{ .operation = .delete, .text = "abcxx" }, + .{ .operation = .insert, .text = "xxdef" }, + }, diffs.items); + } + + { + // Overlap elimination + var diffs = try DiffList.initCapacity(allocator, 2); + defer deinitDiffList(allocator, &diffs); + + diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "abcxxx") }); + diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "xxxdef") }); + + try diffCleanupSemantic(allocator, &diffs); + + try testing.expectEqualDeep(&[_]Diff{ + .{ .operation = .delete, .text = "abc" }, + .{ .operation = .equal, .text = "xxx" }, + .{ .operation = .insert, .text = "def" }, + }, diffs.items); + } + + { + // Reverse overlap elimination + var diffs = try DiffList.initCapacity(allocator, 2); + defer deinitDiffList(allocator, &diffs); + + diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "xxxabc") }); + diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "defxxx") }); + + try diffCleanupSemantic(allocator, &diffs); + + try testing.expectEqualDeep(&[_]Diff{ + .{ .operation = .insert, .text = "def" }, + .{ .operation = .equal, .text = "xxx" }, + .{ .operation = .delete, .text = "abc" }, + }, diffs.items); + } + + { + // Two overlap eliminations + var diffs = try DiffList.initCapacity(allocator, 5); + defer deinitDiffList(allocator, &diffs); + + diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "abcd1212") }); + diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "1212efghi") }); + diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "----") }); + diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "A3") }); + diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "3BC") }); + + try diffCleanupSemantic(allocator, &diffs); + + try testing.expectEqualDeep(&[_]Diff{ + .{ .operation = .delete, .text = "abcd" }, + .{ .operation = .equal, .text = "1212" }, + .{ .operation = .insert, .text = "efghi" }, + .{ .operation = .equal, .text = "----" }, + .{ .operation = .delete, .text = "A" }, + .{ .operation = .equal, .text = "3" }, + .{ .operation = .insert, .text = "BC" }, + }, diffs.items); + } } From b35dc243ce56d42c18d749272c10c3c019a4f238 Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Sun, 7 Jul 2024 12:49:54 -0400 Subject: [PATCH 30/60] Convert to ensuring capacity before append/insert --- DiffMatchPatch.zig | 146 ++++++++++++++++++++++++++++++--------------- 1 file changed, 98 insertions(+), 48 deletions(-) diff --git a/DiffMatchPatch.zig b/DiffMatchPatch.zig index b273a63..6c9aeee 100644 --- a/DiffMatchPatch.zig +++ b/DiffMatchPatch.zig @@ -131,8 +131,13 @@ fn diffInternal( // Check for equality (speedup). if (std.mem.eql(u8, before, after)) { var diffs = DiffList{}; + if (before.len != 0) { - try diffsAppend(allocator, &diffs, .equal, before); + try diffs.ensureUnusedCapacity(allocator, 1); + diffs.appendAssumeCapacity(Diff.init( + .equal, + try allocator.dupe(u8, before), + )); } return diffs; } @@ -154,11 +159,20 @@ fn diffInternal( errdefer deinitDiffList(allocator, &diffs); // Restore the prefix and suffix. + if (common_prefix.len != 0) { - try diffsInsert(allocator, &diffs, 0, .equal, common_prefix); + try diffs.ensureUnusedCapacity(allocator, 1); + diffs.insertAssumeCapacity(0, Diff.init( + .equal, + try allocator.dupe(u8, common_prefix), + )); } if (common_suffix.len != 0) { - try diffsAppend(allocator, &diffs, .equal, common_suffix); + try diffs.ensureUnusedCapacity(allocator, 1); + diffs.appendAssumeCapacity(Diff.init( + .equal, + try allocator.dupe(u8, common_suffix), + )); } try diffCleanupMerge(allocator, &diffs); @@ -213,13 +227,21 @@ fn diffCompute( if (before.len == 0) { // Just add some text (speedup). - try diffsAppend(allocator, &diffs, .insert, after); + try diffs.ensureUnusedCapacity(allocator, 1); + diffs.appendAssumeCapacity(Diff.init( + .insert, + try allocator.dupe(u8, after), + )); return diffs; } if (after.len == 0) { // Just delete some text (speedup). - try diffsAppend(allocator, &diffs, .delete, before); + try diffs.ensureUnusedCapacity(allocator, 1); + diffs.appendAssumeCapacity(Diff.init( + .delete, + try allocator.dupe(u8, before), + )); return diffs; } @@ -232,17 +254,34 @@ fn diffCompute( .delete else .insert; - try diffsAppend(allocator, &diffs, op, long_text[0..index]); - try diffsAppend(allocator, &diffs, .equal, short_text); - try diffsAppend(allocator, &diffs, op, long_text[index + short_text.len ..]); + try diffs.ensureUnusedCapacity(allocator, 3); + diffs.appendAssumeCapacity(Diff{ + .operation = op, + .text = try allocator.dupe(u8, long_text[0..index]), + }); + diffs.appendAssumeCapacity(Diff{ + .operation = .equal, + .text = try allocator.dupe(u8, short_text), + }); + diffs.appendAssumeCapacity(Diff{ + .operation = op, + .text = try allocator.dupe(u8, long_text[index + short_text.len ..]), + }); return diffs; } if (short_text.len == 1) { // Single character string. // After the previous speedup, the character can't be an equality. - try diffsAppend(allocator, &diffs, .delete, before); - try diffsAppend(allocator, &diffs, .insert, after); + try diffs.ensureUnusedCapacity(allocator, 2); + diffs.appendAssumeCapacity(Diff{ + .operation = .delete, + .text = try allocator.dupe(u8, before), + }); + diffs.appendAssumeCapacity(Diff{ + .operation = .insert, + .text = try allocator.dupe(u8, after), + }); return diffs; } @@ -276,7 +315,11 @@ fn diffCompute( // Merge the results. diffs = diffs_a; - try diffsAppend(allocator, &diffs, .equal, half_match.common_middle); + try diffs.ensureUnusedCapacity(allocator, 1); + diffs.appendAssumeCapacity(Diff.init(.equal, try allocator.dupe( + u8, + half_match.common_middle, + ))); try diffs.appendSlice(allocator, diffs_b.items); return diffs; } @@ -574,8 +617,15 @@ fn diffBisect( // Diff took too long and hit the deadline or // number of diffs equals number of characters, no commonality at all. var diffs = DiffList{}; - try diffsAppend(allocator, &diffs, .delete, before); - try diffsAppend(allocator, &diffs, .insert, after); + try diffs.ensureUnusedCapacity(allocator, 2); + diffs.appendAssumeCapacity(Diff.init( + .delete, + try allocator.dupe(u8, before), + )); + diffs.appendAssumeCapacity(Diff.init( + .insert, + try allocator.dupe(u8, after), + )); return diffs; } @@ -890,11 +940,19 @@ fn diffCleanupMerge(allocator: std.mem.Allocator, diffs: *DiffList) DiffError!vo } if (text_delete.items.len != 0) { - try diffsInsert(allocator, diffs, pointer, .delete, text_delete.items); + try diffs.ensureUnusedCapacity(allocator, 1); + diffs.insertAssumeCapacity(pointer, Diff.init( + .delete, + try allocator.dupe(u8, text_delete.items), + )); pointer += 1; } if (text_insert.items.len != 0) { - try diffsInsert(allocator, diffs, pointer, .insert, text_insert.items); + try diffs.ensureUnusedCapacity(allocator, 1); + diffs.insertAssumeCapacity(pointer, Diff.init( + .insert, + try allocator.dupe(u8, text_insert.items), + )); pointer += 1; } pointer += 1; @@ -1020,12 +1078,13 @@ fn diffCleanupSemantic(allocator: std.mem.Allocator, diffs: *DiffList) DiffError (last_equality.?.len <= @max(length_insertions2, length_deletions2))) { // Duplicate record. - try diffsInsert( - allocator, - diffs, + try diffs.ensureUnusedCapacity(allocator, 1); + diffs.insertAssumeCapacity( @intCast(equalities.items[equalities.items.len - 1]), - .delete, - last_equality.?, + Diff.init( + .delete, + try allocator.dupe(u8, last_equality.?), + ), ); // Change second copy to insert. diffs.items[@intCast(equalities.items[equalities.items.len - 1] + 1)].operation = .insert; @@ -1074,13 +1133,14 @@ fn diffCleanupSemantic(allocator: std.mem.Allocator, diffs: *DiffList) DiffError // Overlap found. // Insert an equality and trim the surrounding edits. defer allocator.free(deletion); - defer allocator.free(insertion); - try diffsInsert( - allocator, - diffs, + + try diffs.ensureUnusedCapacity(allocator, 1); + diffs.insertAssumeCapacity( @intCast(pointer), - .equal, - insertion[0..overlap_length1], + Diff.init( + .equal, + try allocator.dupe(u8, insertion[0..overlap_length1]), + ), ); diffs.items[@intCast(pointer - 1)].text = try allocator.dupe(u8, deletion[0 .. deletion.len - overlap_length1]); @@ -1096,12 +1156,13 @@ fn diffCleanupSemantic(allocator: std.mem.Allocator, diffs: *DiffList) DiffError // Insert an equality and swap and trim the surrounding edits. defer allocator.free(deletion); defer allocator.free(insertion); - try diffsInsert( - allocator, - diffs, + try diffs.ensureUnusedCapacity(allocator, 1); + diffs.insertAssumeCapacity( @intCast(pointer), - .equal, - deletion[0..overlap_length2], + Diff.init( + .equal, + try allocator.dupe(u8, deletion[0..overlap_length2]), + ), ); diffs.items[@intCast(pointer - 1)].operation = .insert; const new_minus = try allocator.dupe(u8, insertion[0 .. insertion.len - overlap_length2]); @@ -1342,12 +1403,13 @@ pub fn diffCleanupEfficiency( ((if (pre_ins) 1 else 0) + (if (pre_del) 1 else 0) + (if (post_ins) 1 else 0) + (if (post_del) 1 else 0)) == 3))) { // Duplicate record. - try diffsInsert( - allocator, - &diffs, + try diffs.ensureUnusedCapacity(allocator, 1); + diffs.insertAssumeCapacity( equalities.items[equalities.items.len - 1], - .delete, - last_equality, + Diff.init( + .delete, + try allocator.dupe(u8, last_equality), + ), ); // Change second copy to insert. diffs.items[equalities.items[equalities.items.len - 1] + 1].operation = .insert; @@ -1425,18 +1487,6 @@ fn diffCommonOverlap(text1_in: []const u8, text2_in: []const u8) usize { } } -fn diffsAppend(allocator: Allocator, diffs: *DiffList, op: Diff.Operation, text: []const u8) !void { - const new_text = try allocator.dupe(u8, text); - errdefer allocator.free(new_text); - try diffs.append(allocator, Diff{ .operation = op, .text = new_text }); -} - -fn diffsInsert(allocator: Allocator, diffs: *DiffList, index: usize, op: Diff.Operation, text: []const u8) !void { - const new_text = try allocator.dupe(u8, text); - errdefer allocator.free(new_text); - try diffs.insert(allocator, index, Diff{ .operation = op, .text = new_text }); -} - // DONE [✅]: Allocate all text in diffs to // not cause segfault while freeing From 58fbf0f8e34bc08d01b28a29f204a02134d34521 Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Sun, 7 Jul 2024 12:55:52 -0400 Subject: [PATCH 31/60] Restore deleted free --- DiffMatchPatch.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DiffMatchPatch.zig b/DiffMatchPatch.zig index 6c9aeee..59b566f 100644 --- a/DiffMatchPatch.zig +++ b/DiffMatchPatch.zig @@ -1133,7 +1133,7 @@ fn diffCleanupSemantic(allocator: std.mem.Allocator, diffs: *DiffList) DiffError // Overlap found. // Insert an equality and trim the surrounding edits. defer allocator.free(deletion); - + defer allocator.free(insertion); try diffs.ensureUnusedCapacity(allocator, 1); diffs.insertAssumeCapacity( @intCast(pointer), From 39e0af82761e2bc739bad382648215d820173ac6 Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Sun, 7 Jul 2024 12:59:52 -0400 Subject: [PATCH 32/60] Consistent use of Diff.init() --- DiffMatchPatch.zig | 50 ++++++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/DiffMatchPatch.zig b/DiffMatchPatch.zig index 59b566f..5134390 100644 --- a/DiffMatchPatch.zig +++ b/DiffMatchPatch.zig @@ -255,18 +255,18 @@ fn diffCompute( else .insert; try diffs.ensureUnusedCapacity(allocator, 3); - diffs.appendAssumeCapacity(Diff{ - .operation = op, - .text = try allocator.dupe(u8, long_text[0..index]), - }); - diffs.appendAssumeCapacity(Diff{ - .operation = .equal, - .text = try allocator.dupe(u8, short_text), - }); - diffs.appendAssumeCapacity(Diff{ - .operation = op, - .text = try allocator.dupe(u8, long_text[index + short_text.len ..]), - }); + diffs.appendAssumeCapacity(Diff.init( + op, + try allocator.dupe(u8, long_text[0..index]), + )); + diffs.appendAssumeCapacity(Diff.init( + .equal, + try allocator.dupe(u8, short_text), + )); + diffs.appendAssumeCapacity(Diff.init( + op, + try allocator.dupe(u8, long_text[index + short_text.len ..]), + )); return diffs; } @@ -274,14 +274,14 @@ fn diffCompute( // Single character string. // After the previous speedup, the character can't be an equality. try diffs.ensureUnusedCapacity(allocator, 2); - diffs.appendAssumeCapacity(Diff{ - .operation = .delete, - .text = try allocator.dupe(u8, before), - }); - diffs.appendAssumeCapacity(Diff{ - .operation = .insert, - .text = try allocator.dupe(u8, after), - }); + diffs.appendAssumeCapacity(Diff.init( + .delete, + try allocator.dupe(u8, before), + )); + diffs.appendAssumeCapacity(Diff.init( + .insert, + try allocator.dupe(u8, after), + )); return diffs; } @@ -316,10 +316,12 @@ fn diffCompute( // Merge the results. diffs = diffs_a; try diffs.ensureUnusedCapacity(allocator, 1); - diffs.appendAssumeCapacity(Diff.init(.equal, try allocator.dupe( - u8, - half_match.common_middle, - ))); + diffs.appendAssumeCapacity( + Diff.init(.equal, try allocator.dupe( + u8, + half_match.common_middle, + )), + ); try diffs.appendSlice(allocator, diffs_b.items); return diffs; } From 34d3a7b8ec3edd19865dbb7438aa2a371c0dded4 Mon Sep 17 00:00:00 2001 From: Techatrix Date: Sun, 7 Jul 2024 20:12:20 +0200 Subject: [PATCH 33/60] use `testing.checkAllAllocationFailures` --- DiffMatchPatch.zig | 1531 ++++++++++++++++++++++---------------------- 1 file changed, 780 insertions(+), 751 deletions(-) diff --git a/DiffMatchPatch.zig b/DiffMatchPatch.zig index 5134390..d9ba1f7 100644 --- a/DiffMatchPatch.zig +++ b/DiffMatchPatch.zig @@ -1518,108 +1518,163 @@ test diffCommonOverlap { try testing.expectEqual(@as(usize, 0), diffCommonOverlap("fi", "\u{fb01}")); // Unicode } -test diffHalfMatch { - const allocator = testing.allocator; +fn testDiffHalfMatch( + allocator: std.mem.Allocator, + params: struct { + dmp: DiffMatchPatch, + before: []const u8, + after: []const u8, + expected: ?HalfMatchResult, + }, +) !void { + const maybe_result = try params.dmp.diffHalfMatch(allocator, params.before, params.after); + defer if (maybe_result) |result| result.deinit(allocator); + try testing.expectEqualDeep(params.expected, maybe_result); +} - var one_timeout = DiffMatchPatch{}; - one_timeout.diff_timeout = 1; - const dh1 = try one_timeout.diffHalfMatch(allocator, "1234567890", "abcdef"); - try testing.expectEqual( - @as(?HalfMatchResult, null), - dh1, - ); // No match #1 - const dh2 = try one_timeout.diffHalfMatch(allocator, "12345", "23"); - try testing.expectEqual( - @as(?HalfMatchResult, null), - dh2, - ); // No match #2 +test diffHalfMatch { + const one_timeout: DiffMatchPatch = .{ .diff_timeout = 1 }; + + // No match #1 + try testing.checkAllAllocationFailures(testing.allocator, testDiffHalfMatch, .{.{ + .dmp = one_timeout, + .before = "1234567890", + .after = "abcdef", + .expected = null, + }}); + + // No match #2 + try testing.checkAllAllocationFailures(testing.allocator, testDiffHalfMatch, .{.{ + .dmp = one_timeout, + .before = "12345", + .after = "23", + .expected = null, + }}); + + if (true) return error.SkipZigTest; // TODO // Single matches - var dh3 = (try one_timeout.diffHalfMatch(allocator, "1234567890", "a345678z")).?; - defer dh3.deinit(allocator); - try testing.expectEqualDeep(HalfMatchResult{ - .prefix_before = "12", - .suffix_before = "90", - .prefix_after = "a", - .suffix_after = "z", - .common_middle = "345678", - }, dh3); // Single Match #1 - - var dh4 = (try one_timeout.diffHalfMatch(allocator, "a345678z", "1234567890")).?; - defer dh4.deinit(allocator); - try testing.expectEqualDeep(HalfMatchResult{ - .prefix_before = "a", - .suffix_before = "z", - .prefix_after = "12", - .suffix_after = "90", - .common_middle = "345678", - }, dh4); // Single Match #2 - - var dh5 = (try one_timeout.diffHalfMatch(allocator, "abc56789z", "1234567890")).?; - defer dh5.deinit(allocator); - try testing.expectEqualDeep(HalfMatchResult{ - .prefix_before = "abc", - .suffix_before = "z", - .prefix_after = "1234", - .suffix_after = "0", - .common_middle = "56789", - }, dh5); // Single Match #3 - - var dh6 = (try one_timeout.diffHalfMatch(allocator, "a23456xyz", "1234567890")).?; - defer dh6.deinit(allocator); - try testing.expectEqualDeep(HalfMatchResult{ - .prefix_before = "a", - .suffix_before = "xyz", - .prefix_after = "1", - .suffix_after = "7890", - .common_middle = "23456", - }, dh6); // Single Match #4 - - // Multiple matches - var dh7 = (try one_timeout.diffHalfMatch(allocator, "121231234123451234123121", "a1234123451234z")).?; - defer dh7.deinit(allocator); - try testing.expectEqualDeep(HalfMatchResult{ - .prefix_before = "12123", - .suffix_before = "123121", - .prefix_after = "a", - .suffix_after = "z", - .common_middle = "1234123451234", - }, dh7); // Multiple Matches #1 - - var dh8 = (try one_timeout.diffHalfMatch(allocator, "x-=-=-=-=-=-=-=-=-=-=-=-=", "xx-=-=-=-=-=-=-=")).?; - defer dh8.deinit(allocator); - try testing.expectEqualDeep(HalfMatchResult{ - .prefix_before = "", - .suffix_before = "-=-=-=-=-=", - .prefix_after = "x", - .suffix_after = "", - .common_middle = "x-=-=-=-=-=-=-=", - }, dh8); // Multiple Matches #2 - - var dh9 = (try one_timeout.diffHalfMatch(allocator, "-=-=-=-=-=-=-=-=-=-=-=-=y", "-=-=-=-=-=-=-=yy")).?; - defer dh9.deinit(allocator); - try testing.expectEqualDeep(HalfMatchResult{ - .prefix_before = "-=-=-=-=-=", - .suffix_before = "", - .prefix_after = "", - .suffix_after = "y", - .common_middle = "-=-=-=-=-=-=-=y", - }, dh9); // Multiple Matches #3 + try testing.checkAllAllocationFailures(testing.allocator, testDiffHalfMatch, .{.{ + .dmp = one_timeout, + .before = "1234567890", + .after = "a345678z", + .expected = .{ + .prefix_before = "12", + .suffix_before = "90", + .prefix_after = "a", + .suffix_after = "z", + .common_middle = "345678", + }, + }}); + + // Single Match #2 + try testing.checkAllAllocationFailures(testing.allocator, testDiffHalfMatch, .{.{ + .dmp = one_timeout, + .before = "a345678z", + .after = "1234567890", + .expected = .{ + .prefix_before = "a", + .suffix_before = "z", + .prefix_after = "12", + .suffix_after = "90", + .common_middle = "345678", + }, + }}); + + // Single Match #3 + try testing.checkAllAllocationFailures(testing.allocator, testDiffHalfMatch, .{.{ + .dmp = one_timeout, + .before = "abc56789z", + .after = "1234567890", + .expected = .{ + .prefix_before = "abc", + .suffix_before = "z", + .prefix_after = "1234", + .suffix_after = "0", + .common_middle = "56789", + }, + }}); + + // Single Match #4 + try testing.checkAllAllocationFailures(testing.allocator, testDiffHalfMatch, .{.{ + .dmp = one_timeout, + .before = "a23456xyz", + .after = "1234567890", + .expected = .{ + .prefix_before = "a", + .suffix_before = "xyz", + .prefix_after = "1", + .suffix_after = "7890", + .common_middle = "23456", + }, + }}); + + // Multiple matches #1 + try testing.checkAllAllocationFailures(testing.allocator, testDiffHalfMatch, .{.{ + .dmp = one_timeout, + .before = "121231234123451234123121", + .after = "a1234123451234z", + .expected = .{ + .prefix_before = "12123", + .suffix_before = "123121", + .prefix_after = "a", + .suffix_after = "z", + .common_middle = "1234123451234", + }, + }}); + + // Multiple Matches #2 + try testing.checkAllAllocationFailures(testing.allocator, testDiffHalfMatch, .{.{ + .dmp = one_timeout, + .before = "x-=-=-=-=-=-=-=-=-=-=-=-=", + .after = "xx-=-=-=-=-=-=-=", + .expected = .{ + .prefix_before = "", + .suffix_before = "-=-=-=-=-=", + .prefix_after = "x", + .suffix_after = "", + .common_middle = "x-=-=-=-=-=-=-=", + }, + }}); + + // Multiple Matches #3 + try testing.checkAllAllocationFailures(testing.allocator, testDiffHalfMatch, .{.{ + .dmp = one_timeout, + .before = "-=-=-=-=-=-=-=-=-=-=-=-=y", + .after = "-=-=-=-=-=-=-=yy", + .expected = .{ + .prefix_before = "-=-=-=-=-=", + .suffix_before = "", + .prefix_after = "", + .suffix_after = "y", + .common_middle = "-=-=-=-=-=-=-=y", + }, + }}); // Other cases + // Optimal diff would be -q+x=H-i+e=lloHe+Hu=llo-Hew+y not -qHillo+x=HelloHe-w+Hulloy - var dh10 = (try one_timeout.diffHalfMatch(allocator, "qHilloHelloHew", "xHelloHeHulloy")).?; - defer dh10.deinit(allocator); - try testing.expectEqualDeep(HalfMatchResult{ - .prefix_before = "qHillo", - .suffix_before = "w", - .prefix_after = "x", - .suffix_after = "Hulloy", - .common_middle = "HelloHe", - }, dh10); // Non-optimal halfmatch - - one_timeout.diff_timeout = 0; - try testing.expectEqualDeep(@as(?HalfMatchResult, null), try one_timeout.diffHalfMatch(allocator, "qHilloHelloHew", "xHelloHeHulloy")); // Non-optimal halfmatch + // Non-optimal halfmatch + try testing.checkAllAllocationFailures(testing.allocator, testDiffHalfMatch, .{.{ + .dmp = one_timeout, + .before = "qHilloHelloHew", + .after = "xHelloHeHulloy", + .expected = .{ + .prefix_before = "qHillo", + .suffix_before = "w", + .prefix_after = "x", + .suffix_after = "Hulloy", + .common_middle = "HelloHe", + }, + }}); + + // Non-optimal halfmatch + try testing.checkAllAllocationFailures(testing.allocator, testDiffHalfMatch, .{.{ + .dmp = .{ .diff_timeout = 0 }, + .before = "qHilloHelloHew", + .after = "xHelloHeHulloy", + .expected = null, + }}); } test diffLinesToChars { @@ -1695,404 +1750,371 @@ test diffLinesToChars { // try testing.expectEqualDeep(tmp_array_list.items, result.line_array.items); } -test diffCharsToLines { - const allocator = std.testing.allocator; - - // Convert chars up to lines. - var diffs = try DiffList.initCapacity(allocator, 2); +fn testDiffCharsToLines( + allocator: std.mem.Allocator, + params: struct { + diffs: []const Diff, + line_array: []const []const u8, + expected: []const Diff, + }, +) !void { + var diffs = try DiffList.initCapacity(allocator, params.diffs.len); defer deinitDiffList(allocator, &diffs); - diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "\u{0001}\u{0002}\u{0001}") }); - diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "\u{0002}\u{0001}\u{0002}") }); + for (params.diffs) |item| { + diffs.appendAssumeCapacity(.{ .operation = item.operation, .text = try allocator.dupe(u8, item.text) }); + } - var tmp_vector = std.ArrayList([]const u8).init(allocator); - defer tmp_vector.deinit(); - try tmp_vector.append(""); - try tmp_vector.append("alpha\n"); - try tmp_vector.append("beta\n"); - try diffCharsToLines(allocator, diffs.items, tmp_vector.items); + try diffCharsToLines(allocator, diffs.items, params.line_array); - try testing.expectEqualDeep(&[_]Diff{ - .{ .operation = .equal, .text = "alpha\nbeta\nalpha\n" }, - .{ .operation = .insert, .text = "beta\nalpha\nbeta\n" }, - }, diffs.items); + try testing.expectEqualDeep(params.expected, diffs.items); +} + +test diffCharsToLines { + if (true) return error.SkipZigTest; // TODO + + // Convert chars up to lines. + try testing.checkAllAllocationFailures(testing.allocator, testDiffCharsToLines, .{.{ + .diffs = &.{ + .{ .operation = .equal, .text = "\u{0001}\u{0002}\u{0001}" }, + .{ .operation = .insert, .text = "\u{0002}\u{0001}\u{0002}" }, + }, + .line_array = &[_][]const u8{ + "", + "alpha\n", + "beta\n", + }, + .expected = &.{ + .{ .operation = .equal, .text = "alpha\nbeta\nalpha\n" }, + .{ .operation = .insert, .text = "beta\nalpha\nbeta\n" }, + }, + }}); // TODO: Implement exhaustive tests } -test diffCleanupMerge { - const allocator = testing.allocator; - // Cleanup a messy diff. +fn testDiffCleanupMerge(allocator: std.mem.Allocator, params: struct { + input: []const Diff, + expected: []const Diff, +}) !void { + var diffs = try DiffList.initCapacity(allocator, params.input.len); + defer deinitDiffList(allocator, &diffs); - { - // No change case - var diffs = try DiffList.initCapacity(allocator, 3); - defer deinitDiffList(allocator, &diffs); + for (params.input) |item| { + diffs.appendAssumeCapacity(.{ .operation = item.operation, .text = try allocator.dupe(u8, item.text) }); + } + + try diffCleanupMerge(allocator, &diffs); - diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "a") }); - diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "b") }); - diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "c") }); + try testing.expectEqualDeep(params.expected, diffs.items); +} - try diffCleanupMerge(allocator, &diffs); - try diffCleanupMerge(allocator, &diffs); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ .{ .operation = .equal, .text = "a" }, .{ .operation = .delete, .text = "b" }, .{ .operation = .insert, .text = "c" } }), diffs.items); // No change case - try diffCleanupMerge(allocator, &diffs); - try testing.expectEqualDeep(@as([]const Diff, &[_]Diff{ .{ .operation = .equal, .text = "a" }, .{ .operation = .delete, .text = "b" }, .{ .operation = .insert, .text = "c" } }), diffs.items); // No change case +test diffCleanupMerge { + // Cleanup a messy diff. - try testing.expectEqualDeep(&[_]Diff{ + // No change case + try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupMerge, .{.{ + .input = &.{ .{ .operation = .equal, .text = "a" }, .{ .operation = .delete, .text = "b" }, .{ .operation = .insert, .text = "c" }, - }, diffs.items); - } - - { - // Merge equalities - var diffs = try DiffList.initCapacity(allocator, 3); - defer deinitDiffList(allocator, &diffs); - - diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "a") }); - diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "b") }); - diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "c") }); - - try diffCleanupMerge(allocator, &diffs); + }, + .expected = &.{ + .{ .operation = .equal, .text = "a" }, + .{ .operation = .delete, .text = "b" }, + .{ .operation = .insert, .text = "c" }, + }, + }}); - try testing.expectEqualDeep(&[_]Diff{ + // Merge equalities + try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupMerge, .{.{ + .input = &.{ + .{ .operation = .equal, .text = "a" }, + .{ .operation = .equal, .text = "b" }, + .{ .operation = .equal, .text = "c" }, + }, + .expected = &.{ .{ .operation = .equal, .text = "abc" }, - }, diffs.items); - } - - { - // Merge deletions - var diffs = try DiffList.initCapacity(allocator, 3); - defer deinitDiffList(allocator, &diffs); - - diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "a") }); - diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "b") }); - diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "c") }); + }, + }}); - try diffCleanupMerge(allocator, &diffs); - - try testing.expectEqualDeep(&[_]Diff{ + // Merge deletions + try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupMerge, .{.{ + .input = &.{ + .{ .operation = .delete, .text = "a" }, + .{ .operation = .delete, .text = "b" }, + .{ .operation = .delete, .text = "c" }, + }, + .expected = &.{ .{ .operation = .delete, .text = "abc" }, - }, diffs.items); - } + }, + }}); - { - - // Merge insertions - var diffs = try DiffList.initCapacity(allocator, 3); - defer deinitDiffList(allocator, &diffs); - - diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "a") }); - diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "b") }); - diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "c") }); - - try diffCleanupMerge(allocator, &diffs); - - try testing.expectEqualDeep(&[_]Diff{ + // Merge insertions + try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupMerge, .{.{ + .input = &.{ + .{ .operation = .insert, .text = "a" }, + .{ .operation = .insert, .text = "b" }, + .{ .operation = .insert, .text = "c" }, + }, + .expected = &.{ .{ .operation = .insert, .text = "abc" }, - }, diffs.items); - } - - { - // Merge interweave - var diffs = try DiffList.initCapacity(allocator, 6); - defer deinitDiffList(allocator, &diffs); - - diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "a") }); - diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "b") }); - diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "c") }); - diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "d") }); - diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "e") }); - diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "f") }); + }, + }}); - try diffCleanupMerge(allocator, &diffs); - - try testing.expectEqualDeep(&[_]Diff{ + // Merge interweave + try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupMerge, .{.{ + .input = &.{ + .{ .operation = .delete, .text = "a" }, + .{ .operation = .insert, .text = "b" }, + .{ .operation = .delete, .text = "c" }, + .{ .operation = .insert, .text = "d" }, + .{ .operation = .equal, .text = "e" }, + .{ .operation = .equal, .text = "f" }, + }, + .expected = &.{ .{ .operation = .delete, .text = "ac" }, .{ .operation = .insert, .text = "bd" }, .{ .operation = .equal, .text = "ef" }, - }, diffs.items); - } + }, + }}); - { - // Prefix and suffix detection - var diffs = try DiffList.initCapacity(allocator, 3); - defer deinitDiffList(allocator, &diffs); - - diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "a") }); - diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "abc") }); - diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "dc") }); - - try diffCleanupMerge(allocator, &diffs); - - try testing.expectEqualDeep(&[_]Diff{ + // Prefix and suffix detection + try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupMerge, .{.{ + .input = &.{ + .{ .operation = .delete, .text = "a" }, + .{ .operation = .insert, .text = "abc" }, + .{ .operation = .delete, .text = "dc" }, + }, + .expected = &.{ .{ .operation = .equal, .text = "a" }, .{ .operation = .delete, .text = "d" }, .{ .operation = .insert, .text = "b" }, .{ .operation = .equal, .text = "c" }, - }, diffs.items); - } - - { - // Prefix and suffix detection with equalities - var diffs = try DiffList.initCapacity(allocator, 5); - defer deinitDiffList(allocator, &diffs); - - diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "x") }); - diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "a") }); - diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "abc") }); - diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "dc") }); - diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "y") }); + }, + }}); - try diffCleanupMerge(allocator, &diffs); + if (true) return error.SkipZigTest; // TODO - try testing.expectEqualDeep(&[_]Diff{ + // Prefix and suffix detection with equalities + try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupMerge, .{.{ + .input = &.{ + .{ .operation = .equal, .text = "x" }, + .{ .operation = .delete, .text = "a" }, + .{ .operation = .insert, .text = "abc" }, + .{ .operation = .delete, .text = "dc" }, + .{ .operation = .equal, .text = "y" }, + }, + .expected = &.{ .{ .operation = .equal, .text = "xa" }, .{ .operation = .delete, .text = "d" }, .{ .operation = .insert, .text = "b" }, .{ .operation = .equal, .text = "cy" }, - }, diffs.items); - } - - { - // Slide edit left - var diffs = try DiffList.initCapacity(allocator, 3); - defer deinitDiffList(allocator, &diffs); - - diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "a") }); - diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "ba") }); - diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "c") }); - - try diffCleanupMerge(allocator, &diffs); + }, + }}); - try testing.expectEqualDeep(&[_]Diff{ + // Slide edit left + try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupMerge, .{.{ + .input = &.{ + .{ .operation = .equal, .text = "a" }, + .{ .operation = .insert, .text = "ba" }, + .{ .operation = .equal, .text = "c" }, + }, + .expected = &.{ .{ .operation = .insert, .text = "ab" }, .{ .operation = .equal, .text = "ac" }, - }, diffs.items); - } - - { - - // Slide edit right - var diffs = try DiffList.initCapacity(allocator, 3); - defer deinitDiffList(allocator, &diffs); - - diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "c") }); - diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "ab") }); - diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "a") }); + }, + }}); - try diffCleanupMerge(allocator, &diffs); - - try testing.expectEqualDeep(&[_]Diff{ + // Slide edit right + try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupMerge, .{.{ + .input = &.{ + .{ .operation = .equal, .text = "c" }, + .{ .operation = .insert, .text = "ab" }, + .{ .operation = .equal, .text = "a" }, + }, + .expected = &.{ .{ .operation = .equal, .text = "ca" }, .{ .operation = .insert, .text = "ba" }, - }, diffs.items); - } - - { - - // Slide edit left recursive - var diffs = try DiffList.initCapacity(allocator, 5); - defer deinitDiffList(allocator, &diffs); - - diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "a") }); - diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "b") }); - diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "c") }); - diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "ac") }); - diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "x") }); - - try diffCleanupMerge(allocator, &diffs); - - try testing.expectEqualDeep(&.{ - Diff.init(.delete, "abc"), - Diff.init(.equal, "acx"), - }, diffs.items); - } - - { - // Slide edit right recursive - var diffs = try DiffList.initCapacity(allocator, 5); - defer deinitDiffList(allocator, &diffs); - - diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "x") }); - diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "ca") }); - diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "c") }); - diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "b") }); - diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "a") }); - - try diffCleanupMerge(allocator, &diffs); - - try testing.expectEqualDeep(&.{ - Diff.init(.equal, "xca"), - Diff.init(.delete, "cba"), - }, diffs.items); - } + }, + }}); - { - // Empty merge - var diffs = try DiffList.initCapacity(allocator, 3); - defer deinitDiffList(allocator, &diffs); + // Slide edit left recursive + try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupMerge, .{.{ + .input = &.{ + .{ .operation = .equal, .text = "a" }, + .{ .operation = .delete, .text = "b" }, + .{ .operation = .equal, .text = "c" }, + .{ .operation = .delete, .text = "ac" }, + .{ .operation = .equal, .text = "x" }, + }, + .expected = &.{ + .{ .operation = .delete, .text = "abc" }, + .{ .operation = .equal, .text = "acx" }, + }, + }}); - diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "b") }); - diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "ab") }); - diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "c") }); + // Slide edit right recursive + try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupMerge, .{.{ + .input = &.{ + .{ .operation = .equal, .text = "x" }, + .{ .operation = .delete, .text = "ca" }, + .{ .operation = .equal, .text = "c" }, + .{ .operation = .delete, .text = "b" }, + .{ .operation = .equal, .text = "a" }, + }, + .expected = &.{ + .{ .operation = .equal, .text = "xca" }, + .{ .operation = .delete, .text = "cba" }, + }, + }}); + + // Empty merge + try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupMerge, .{.{ + .input = &.{ + .{ .operation = .delete, .text = "b" }, + .{ .operation = .insert, .text = "ab" }, + .{ .operation = .equal, .text = "c" }, + }, + .expected = &.{ + .{ .operation = .insert, .text = "a" }, + .{ .operation = .equal, .text = "bc" }, + }, + }}); + + // Empty equality + try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupMerge, .{.{ + .input = &.{ + .{ .operation = .equal, .text = "" }, + .{ .operation = .insert, .text = "a" }, + .{ .operation = .equal, .text = "b" }, + }, + .expected = &.{ + .{ .operation = .insert, .text = "a" }, + .{ .operation = .equal, .text = "b" }, + }, + }}); +} - try diffCleanupMerge(allocator, &diffs); +fn testDiffCleanupSemanticLossless( + allocator: std.mem.Allocator, + params: struct { + input: []const Diff, + expected: []const Diff, + }, +) !void { + var diffs = try DiffList.initCapacity(allocator, params.input.len); + defer deinitDiffList(allocator, &diffs); - try testing.expectEqualDeep(&.{ - Diff.init(.insert, "a"), - Diff.init(.equal, "bc"), - }, diffs.items); + for (params.input) |item| { + diffs.appendAssumeCapacity(.{ .operation = item.operation, .text = try allocator.dupe(u8, item.text) }); } - { - // Empty equality - var diffs = try DiffList.initCapacity(allocator, 3); - defer deinitDiffList(allocator, &diffs); + try diffCleanupSemanticLossless(allocator, &diffs); - diffs.appendAssumeCapacity(.{ .operation = .equal, .text = "" }); - diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "a") }); - diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "b") }); - - try diffCleanupMerge(allocator, &diffs); - - try testing.expectEqualDeep(&.{ - Diff.init(.insert, "a"), - Diff.init(.equal, "b"), - }, diffs.items); - } + try testing.expectEqualDeep(params.expected, diffs.items); } test diffCleanupSemanticLossless { - const allocator = testing.allocator; - - { - // Null case - var diffs: DiffList = .{}; - try diffCleanupSemanticLossless(allocator, &diffs); - try testing.expectEqualDeep(&[_]Diff{}, diffs.items); - } - - { - var diffs = try DiffList.initCapacity(allocator, 3); - defer deinitDiffList(allocator, &diffs); - - diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "AAA\r\n\r\nBBB") }); - diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "\r\nDDD\r\n\r\nBBB") }); - diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "\r\nEEE") }); - - try diffCleanupSemanticLossless(allocator, &diffs); - - try testing.expectEqualDeep(&[_]Diff{ - Diff.init(.equal, "AAA\r\n\r\n"), - Diff.init(.insert, "BBB\r\nDDD\r\n\r\n"), - Diff.init(.equal, "BBB\r\nEEE"), - }, diffs.items); - } - - { - var diffs = try DiffList.initCapacity(allocator, 3); - defer deinitDiffList(allocator, &diffs); - - diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "AAA\r\nBBB") }); - diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, " DDD\r\nBBB") }); - diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, " EEE") }); - - try diffCleanupSemanticLossless(allocator, &diffs); - - try testing.expectEqualDeep(&[_]Diff{ - Diff.init(.equal, "AAA\r\n"), - Diff.init(.insert, "BBB DDD\r\n"), - Diff.init(.equal, "BBB EEE"), - }, diffs.items); - } - - { - var diffs = try DiffList.initCapacity(allocator, 3); - defer deinitDiffList(allocator, &diffs); - - diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "The c") }); - diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "ow and the c") }); - diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "at.") }); - - try diffCleanupSemanticLossless(allocator, &diffs); - - try testing.expectEqualDeep(&[_]Diff{ - Diff.init(.equal, "The "), - Diff.init(.insert, "cow and the "), - Diff.init(.equal, "cat."), - }, diffs.items); - } - - { - var diffs = try DiffList.initCapacity(allocator, 3); - defer deinitDiffList(allocator, &diffs); - - diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "The-c") }); - diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "ow-and-the-c") }); - diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "at.") }); - - try diffCleanupSemanticLossless(allocator, &diffs); - - try testing.expectEqualDeep(&[_]Diff{ - Diff.init(.equal, "The-"), - Diff.init(.insert, "cow-and-the-"), - Diff.init(.equal, "cat."), - }, diffs.items); - } - - { - var diffs = try DiffList.initCapacity(allocator, 3); - defer deinitDiffList(allocator, &diffs); - - diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "a") }); - diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "a") }); - diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "ax") }); - - try diffCleanupSemanticLossless(allocator, &diffs); - - try testing.expectEqualDeep(&[_]Diff{ - Diff.init(.delete, "a"), - Diff.init(.equal, "aax"), - }, diffs.items); - } - - { - var diffs = try DiffList.initCapacity(allocator, 3); - defer deinitDiffList(allocator, &diffs); - - diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "xa") }); - diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "a") }); - diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "a") }); - - try diffCleanupSemanticLossless(allocator, &diffs); - - try testing.expectEqualDeep(&[_]Diff{ - Diff.init(.equal, "xaa"), - Diff.init(.delete, "a"), - }, diffs.items); - } - - { - var diffs = try DiffList.initCapacity(allocator, 3); - defer deinitDiffList(allocator, &diffs); - - diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "The xxx. The ") }); - diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "zzz. The ") }); - diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "yyy.") }); + // Null case + try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupSemanticLossless, .{.{ + .input = &[_]Diff{}, + .expected = &[_]Diff{}, + }}); + + if (true) return error.SkipZigTest; // TODO + + try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupSemanticLossless, .{.{ + .input = &.{ + .{ .operation = .equal, .text = "AAA\r\n\r\nBBB" }, + .{ .operation = .insert, .text = "\r\nDDD\r\n\r\nBBB" }, + .{ .operation = .equal, .text = "\r\nEEE" }, + }, + .expected = &.{ + .{ .operation = .equal, .text = "AAA\r\n\r\n" }, + .{ .operation = .insert, .text = "BBB\r\nDDD\r\n\r\n" }, + .{ .operation = .equal, .text = "BBB\r\nEEE" }, + }, + }}); + + try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupSemanticLossless, .{.{ + .input = &.{ + .{ .operation = .equal, .text = "AAA\r\nBBB" }, + .{ .operation = .insert, .text = " DDD\r\nBBB" }, + .{ .operation = .equal, .text = " EEE" }, + }, + .expected = &.{ + .{ .operation = .equal, .text = "AAA\r\n" }, + .{ .operation = .insert, .text = "BBB DDD\r\n" }, + .{ .operation = .equal, .text = "BBB EEE" }, + }, + }}); + + try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupSemanticLossless, .{.{ + .input = &.{ + .{ .operation = .equal, .text = "The c" }, + .{ .operation = .insert, .text = "ow and the c" }, + .{ .operation = .equal, .text = "at." }, + }, + .expected = &.{ + .{ .operation = .equal, .text = "The " }, + .{ .operation = .insert, .text = "cow and the " }, + .{ .operation = .equal, .text = "cat." }, + }, + }}); + + try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupSemanticLossless, .{.{ + .input = &.{ + .{ .operation = .equal, .text = "The-c" }, + .{ .operation = .insert, .text = "ow-and-the-c" }, + .{ .operation = .equal, .text = "at." }, + }, + .expected = &.{ + .{ .operation = .equal, .text = "The-" }, + .{ .operation = .insert, .text = "cow-and-the-" }, + .{ .operation = .equal, .text = "cat." }, + }, + }}); - try diffCleanupSemanticLossless(allocator, &diffs); + try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupSemanticLossless, .{.{ + .input = &.{ + .{ .operation = .equal, .text = "a" }, + .{ .operation = .delete, .text = "a" }, + .{ .operation = .equal, .text = "ax" }, + }, + .expected = &.{ + .{ .operation = .delete, .text = "a" }, + .{ .operation = .equal, .text = "aax" }, + }, + }}); - try testing.expectEqualDeep(&[_]Diff{ - Diff.init(.equal, "The xxx."), - Diff.init(.insert, " The zzz."), - Diff.init(.equal, " The yyy."), - }, diffs.items); - } + try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupSemanticLossless, .{.{ + .input = &.{ + .{ .operation = .equal, .text = "xa" }, + .{ .operation = .delete, .text = "a" }, + .{ .operation = .equal, .text = "a" }, + }, + .expected = &.{ + .{ .operation = .equal, .text = "xaa" }, + .{ .operation = .delete, .text = "a" }, + }, + }}); + + try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupSemanticLossless, .{.{ + .input = &.{ + .{ .operation = .equal, .text = "The xxx. The " }, + .{ .operation = .insert, .text = "zzz. The " }, + .{ .operation = .equal, .text = "yyy." }, + }, + .expected = &.{ + .{ .operation = .equal, .text = "The xxx." }, + .{ .operation = .insert, .text = " The zzz." }, + .{ .operation = .equal, .text = " The yyy." }, + }, + }}); } +/// TODO this function obviously leaks memory on error fn rebuildtexts(allocator: std.mem.Allocator, diffs: DiffList) ![2][]const u8 { var text = [2]std.ArrayList(u8){ std.ArrayList(u8).init(allocator), @@ -2113,205 +2135,231 @@ fn rebuildtexts(allocator: std.mem.Allocator, diffs: DiffList) ![2][]const u8 { }; } -test diffBisect { - const allocator = testing.allocator; +fn testDiffBisect( + allocator: std.mem.Allocator, + params: struct { + dmp: DiffMatchPatch, + before: []const u8, + after: []const u8, + deadline: u64, + expected: []const Diff, + }, +) !void { + var diffs = try params.dmp.diffBisect(allocator, params.before, params.after, params.deadline); + defer deinitDiffList(allocator, &diffs); + try testing.expectEqualDeep(params.expected, diffs.items); +} +test diffBisect { const this: DiffMatchPatch = .{ .diff_timeout = 0 }; const a = "cat"; const b = "map"; - { - // Normal. - - // Since the resulting diff hasn't been normalized, it would be ok if - // the insertion and deletion pairs are swapped. - // If the order changes, tweak this test as required. - // Travis TODO not sure if maxInt(u64) is correct for DateTime.MaxValue - var diffs = try this.diffBisect( - allocator, - a, - b, - std.math.maxInt(u64), - ); - defer deinitDiffList(allocator, &diffs); - - try testing.expectEqualDeep(&[_]Diff{ + // Normal + try testing.checkAllAllocationFailures(testing.allocator, testDiffBisect, .{.{ + .dmp = this, + .before = a, + .after = b, + .deadline = std.math.maxInt(u64), // Travis TODO not sure if maxInt(u64) is correct for DateTime.MaxValue + .expected = &.{ .{ .operation = .delete, .text = "c" }, .{ .operation = .insert, .text = "m" }, .{ .operation = .equal, .text = "a" }, .{ .operation = .delete, .text = "t" }, .{ .operation = .insert, .text = "p" }, - }, diffs.items); - } - - { - // Timeout. - var diffs2 = DiffList{}; - defer deinitDiffList(allocator, &diffs2); - try diffs2.appendSlice(allocator, &.{ - Diff.init(.delete, try allocator.dupe(u8, "cat")), - Diff.init(.insert, try allocator.dupe(u8, "map")), - }); - // Travis TODO not sure if 0 is correct for DateTime.MinValue - var diffs = try this.diffBisect(allocator, a, b, 0); - defer deinitDiffList(allocator, &diffs); - - try testing.expectEqualDeep(&[_]Diff{ + }, + }}); + + if (true) return error.SkipZigTest; // TODO + + // Timeout + try testing.checkAllAllocationFailures(testing.allocator, testDiffBisect, .{.{ + .dmp = this, + .before = a, + .after = b, + .deadline = 0, // Travis TODO not sure if 0 is correct for DateTime.MinValue + .expected = &.{ .{ .operation = .delete, .text = "cat" }, .{ .operation = .insert, .text = "map" }, - }, diffs.items); - } + }, + }}); +} + +fn testDiff( + allocator: std.mem.Allocator, + params: struct { + dmp: DiffMatchPatch, + before: []const u8, + after: []const u8, + check_lines: bool, + expected: []const Diff, + }, +) !void { + var diffs = try params.dmp.diff(allocator, params.before, params.after, params.check_lines); + defer deinitDiffList(allocator, &diffs); + try testing.expectEqualDeep(params.expected, diffs.items); } test diff { - const allocator = testing.allocator; + if (true) return error.SkipZigTest; // TODO const this: DiffMatchPatch = .{ .diff_timeout = 0 }; - { - // diff: Null case. - var diffs = try this.diff(allocator, "", "", false); - defer deinitDiffList(allocator, &diffs); - - try testing.expectEqual(0, diffs.items.len); - } - - { - // diff: Equality. - var diffs = try this.diff(allocator, "abc", "abc", false); - defer deinitDiffList(allocator, &diffs); - - try testing.expectEqualDeep(&[_]Diff{ + // Null case. + try testing.checkAllAllocationFailures(testing.allocator, testDiff, .{.{ + .dmp = this, + .before = "", + .after = "", + .check_lines = false, + .expected = &[_]Diff{}, + }}); + + // Equality. + try testing.checkAllAllocationFailures(testing.allocator, testDiff, .{.{ + .dmp = this, + .before = "abc", + .after = "abc", + .check_lines = false, + .expected = &.{ .{ .operation = .equal, .text = "abc" }, - }, diffs.items); - } - - { - // diff: Simple insertion. - var diffs = try this.diff(allocator, "abc", "ab123c", false); - defer deinitDiffList(allocator, &diffs); - - try testing.expectEqualDeep(&[_]Diff{ + }, + }}); + + // Simple insertion. + try testing.checkAllAllocationFailures(testing.allocator, testDiff, .{.{ + .dmp = this, + .before = "abc", + .after = "ab123c", + .check_lines = false, + .expected = &.{ .{ .operation = .equal, .text = "ab" }, .{ .operation = .insert, .text = "123" }, .{ .operation = .equal, .text = "c" }, - }, diffs.items); - } - - { - // diff: Simple deletion. - var diffs = try this.diff(allocator, "a123bc", "abc", false); - defer deinitDiffList(allocator, &diffs); - - try testing.expectEqualDeep(&[_]Diff{ + }, + }}); + + // Simple deletion. + try testing.checkAllAllocationFailures(testing.allocator, testDiff, .{.{ + .dmp = this, + .before = "a123bc", + .after = "abc", + .check_lines = false, + .expected = &.{ .{ .operation = .equal, .text = "a" }, .{ .operation = .delete, .text = "123" }, .{ .operation = .equal, .text = "bc" }, - }, diffs.items); - } - - { - // diff: Two insertions. - var diffs = try this.diff(allocator, "abc", "a123b456c", false); - defer deinitDiffList(allocator, &diffs); - - try testing.expectEqualDeep(&[_]Diff{ + }, + }}); + + // Two insertions. + try testing.checkAllAllocationFailures(testing.allocator, testDiff, .{.{ + .dmp = this, + .before = "abc", + .after = "a123b456c", + .check_lines = false, + .expected = &.{ .{ .operation = .equal, .text = "a" }, .{ .operation = .insert, .text = "123" }, .{ .operation = .equal, .text = "b" }, .{ .operation = .insert, .text = "456" }, .{ .operation = .equal, .text = "c" }, - }, diffs.items); - } - - { - // diff: Two deletions. - var diffs = try this.diff(allocator, "a123b456c", "abc", false); - defer deinitDiffList(allocator, &diffs); - - try testing.expectEqualDeep(&[_]Diff{ + }, + }}); + + // Two deletions. + try testing.checkAllAllocationFailures(testing.allocator, testDiff, .{.{ + .dmp = this, + .before = "a123b456c", + .after = "abc", + .check_lines = false, + .expected = &.{ .{ .operation = .equal, .text = "a" }, .{ .operation = .delete, .text = "123" }, .{ .operation = .equal, .text = "b" }, .{ .operation = .delete, .text = "456" }, .{ .operation = .equal, .text = "c" }, - }, diffs.items); - } - - // Perform a real diff. - { - // diff: Simple case #1. - var diffs = try this.diff(allocator, "a", "b", false); - defer deinitDiffList(allocator, &diffs); - - try testing.expectEqualDeep(&[_]Diff{ + }, + }}); + + // Simple case #1 + try testing.checkAllAllocationFailures(testing.allocator, testDiff, .{.{ + .dmp = this, + .before = "a", + .after = "b", + .check_lines = false, + .expected = &.{ .{ .operation = .delete, .text = "a" }, .{ .operation = .insert, .text = "b" }, - }, diffs.items); - } - - { - // diff: Simple case #2. - var diffs = try this.diff(allocator, "Apples are a fruit.", "Bananas are also fruit.", false); - defer deinitDiffList(allocator, &diffs); - - try testing.expectEqualDeep(&[_]Diff{ + }, + }}); + + // Simple case #2 + try testing.checkAllAllocationFailures(testing.allocator, testDiff, .{.{ + .dmp = this, + .before = "Apples are a fruit.", + .after = "Bananas are also fruit.", + .check_lines = false, + .expected = &.{ .{ .operation = .delete, .text = "Apple" }, .{ .operation = .insert, .text = "Banana" }, .{ .operation = .equal, .text = "s are a" }, .{ .operation = .insert, .text = "lso" }, .{ .operation = .equal, .text = " fruit." }, - }, diffs.items); - } - - { - // diff: Simple case #3. - var diffs = try this.diff(allocator, "ax\t", "\u{0680}x\x00", false); - defer deinitDiffList(allocator, &diffs); - - try testing.expectEqualDeep(&[_]Diff{ + }, + }}); + + // Simple case #3 + try testing.checkAllAllocationFailures(testing.allocator, testDiff, .{.{ + .dmp = this, + .before = "ax\t", + .after = "\u{0680}x\x00", + .check_lines = false, + .expected = &.{ .{ .operation = .delete, .text = "a" }, .{ .operation = .insert, .text = "\u{0680}" }, .{ .operation = .equal, .text = "x" }, .{ .operation = .delete, .text = "\t" }, .{ .operation = .insert, .text = "\x00" }, - }, diffs.items); - } - - { - // diff: Overlap #1. - var diffs = try this.diff(allocator, "1ayb2", "abxab", false); - defer deinitDiffList(allocator, &diffs); - - try testing.expectEqualDeep(&[_]Diff{ + }, + }}); + + // Overlap #1 + try testing.checkAllAllocationFailures(testing.allocator, testDiff, .{.{ + .dmp = this, + .before = "1ayb2", + .after = "abxab", + .check_lines = false, + .expected = &.{ .{ .operation = .delete, .text = "1" }, .{ .operation = .equal, .text = "a" }, .{ .operation = .delete, .text = "y" }, .{ .operation = .equal, .text = "b" }, .{ .operation = .delete, .text = "2" }, .{ .operation = .insert, .text = "xab" }, - }, diffs.items); - } - - { - // diff: Overlap #2. - var diffs = try this.diff(allocator, "abcy", "xaxcxabc", false); - defer deinitDiffList(allocator, &diffs); - - try testing.expectEqualDeep(&[_]Diff{ + }, + }}); + + // Overlap #2 + try testing.checkAllAllocationFailures(testing.allocator, testDiff, .{.{ + .dmp = this, + .before = "abcy", + .after = "xaxcxabc", + .check_lines = false, + .expected = &.{ .{ .operation = .insert, .text = "xaxcx" }, .{ .operation = .equal, .text = "abc" }, .{ .operation = .delete, .text = "y" }, - }, diffs.items); - } - - { - // diff: Overlap #3. - var diffs = try this.diff(allocator, "ABCDa=bcd=efghijklmnopqrsEFGHIJKLMNOefg", "a-bcd-efghijklmnopqrs", false); - defer deinitDiffList(allocator, &diffs); - - try testing.expectEqualDeep(&[_]Diff{ + }, + }}); + + // Overlap #3 + try testing.checkAllAllocationFailures(testing.allocator, testDiff, .{.{ + .dmp = this, + .before = "ABCDa=bcd=efghijklmnopqrsEFGHIJKLMNOefg", + .after = "a-bcd-efghijklmnopqrs", + .check_lines = false, + .expected = &.{ .{ .operation = .delete, .text = "ABCD" }, .{ .operation = .equal, .text = "a" }, .{ .operation = .delete, .text = "=" }, @@ -2321,22 +2369,26 @@ test diff { .{ .operation = .insert, .text = "-" }, .{ .operation = .equal, .text = "efghijklmnopqrs" }, .{ .operation = .delete, .text = "EFGHIJKLMNOefg" }, - }, diffs.items); - } - - { - // diff: Large equality. - var diffs = try this.diff(allocator, "a [[Pennsylvania]] and [[New", " and [[Pennsylvania]]", false); - defer deinitDiffList(allocator, &diffs); - - try testing.expectEqualDeep(&[_]Diff{ + }, + }}); + + // Large equality + try testing.checkAllAllocationFailures(testing.allocator, testDiff, .{.{ + .dmp = this, + .before = "a [[Pennsylvania]] and [[New", + .after = " and [[Pennsylvania]]", + .check_lines = false, + .expected = &.{ .{ .operation = .insert, .text = " " }, .{ .operation = .equal, .text = "a" }, .{ .operation = .insert, .text = "nd" }, .{ .operation = .equal, .text = " [[Pennsylvania]]" }, .{ .operation = .delete, .text = " and [[New" }, - }, diffs.items); - } + }, + }}); + + const allocator = testing.allocator; + // TODO these tests should be checked for allocation failure // Increase the text lengths by 1024 times to ensure a timeout. { @@ -2418,198 +2470,175 @@ test diff { } } -test diffCleanupSemantic { - const allocator = testing.allocator; +fn testDiffCleanupSemantic( + allocator: std.mem.Allocator, + params: struct { + input: []const Diff, + expected: []const Diff, + }, +) !void { + var diffs = try DiffList.initCapacity(allocator, params.input.len); + defer deinitDiffList(allocator, &diffs); - { - // Null case. - var diffs: DiffList = .{}; - defer deinitDiffList(allocator, &diffs); - try diffCleanupSemantic(allocator, &diffs); - try testing.expectEqual(@as(usize, 0), diffs.items.len); // Null case + for (params.input) |item| { + diffs.appendAssumeCapacity(.{ .operation = item.operation, .text = try allocator.dupe(u8, item.text) }); } - { - // No elimination #1 - var diffs = try DiffList.initCapacity(allocator, 4); - defer deinitDiffList(allocator, &diffs); - - diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "ab") }); - diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "cd") }); - diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "12") }); - diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "e") }); + try diffCleanupSemantic(allocator, &diffs); - try diffCleanupSemantic(allocator, &diffs); + try testing.expectEqualDeep(params.expected, diffs.items); +} - try testing.expectEqualDeep(&[_]Diff{ +test diffCleanupSemantic { + // Null case. + try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupSemantic, .{.{ + .input = &[_]Diff{}, + .expected = &[_]Diff{}, + }}); + + // No elimination #1 + try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupSemantic, .{.{ + .input = &.{ .{ .operation = .delete, .text = "ab" }, .{ .operation = .insert, .text = "cd" }, .{ .operation = .equal, .text = "12" }, .{ .operation = .delete, .text = "e" }, - }, diffs.items); - } - - { - // No elimination #2 - var diffs = try DiffList.initCapacity(allocator, 4); - defer deinitDiffList(allocator, &diffs); - - diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "abc") }); - diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "ABC") }); - diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "1234") }); - diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "wxyz") }); - - try diffCleanupSemantic(allocator, &diffs); + }, + .expected = &.{ + .{ .operation = .delete, .text = "ab" }, + .{ .operation = .insert, .text = "cd" }, + .{ .operation = .equal, .text = "12" }, + .{ .operation = .delete, .text = "e" }, + }, + }}); - try testing.expectEqualDeep(&[_]Diff{ + // No elimination #2 + try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupSemantic, .{.{ + .input = &.{ .{ .operation = .delete, .text = "abc" }, .{ .operation = .insert, .text = "ABC" }, .{ .operation = .equal, .text = "1234" }, .{ .operation = .delete, .text = "wxyz" }, - }, diffs.items); - } - - { - // Simple elimination - var diffs = try DiffList.initCapacity(allocator, 3); - defer deinitDiffList(allocator, &diffs); - - diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "a") }); - diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "b") }); - diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "c") }); - - try diffCleanupSemantic(allocator, &diffs); + }, + .expected = &.{ + .{ .operation = .delete, .text = "abc" }, + .{ .operation = .insert, .text = "ABC" }, + .{ .operation = .equal, .text = "1234" }, + .{ .operation = .delete, .text = "wxyz" }, + }, + }}); - try testing.expectEqualDeep(&[_]Diff{ + // Simple elimination + try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupSemantic, .{.{ + .input = &.{ + .{ .operation = .delete, .text = "a" }, + .{ .operation = .equal, .text = "b" }, + .{ .operation = .delete, .text = "c" }, + }, + .expected = &.{ .{ .operation = .delete, .text = "abc" }, .{ .operation = .insert, .text = "b" }, - }, diffs.items); - } - - { - // Backpass elimination - var diffs = try DiffList.initCapacity(allocator, 5); - defer deinitDiffList(allocator, &diffs); - - diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "ab") }); - diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "cd") }); - diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "e") }); - diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "f") }); - diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "g") }); - - try diffCleanupSemantic(allocator, &diffs); + }, + }}); - try testing.expectEqualDeep(&[_]Diff{ + // Backpass elimination + try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupSemantic, .{.{ + .input = &.{ + .{ .operation = .delete, .text = "ab" }, + .{ .operation = .equal, .text = "cd" }, + .{ .operation = .delete, .text = "e" }, + .{ .operation = .equal, .text = "f" }, + .{ .operation = .insert, .text = "g" }, + }, + .expected = &.{ .{ .operation = .delete, .text = "abcdef" }, .{ .operation = .insert, .text = "cdfg" }, - }, diffs.items); - } - - { - // Multiple elimination - var diffs = try DiffList.initCapacity(allocator, 9); - defer deinitDiffList(allocator, &diffs); - - diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "1") }); - diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "A") }); - diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "B") }); - diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "2") }); - diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "_") }); - diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "1") }); - diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "A") }); - diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "B") }); - diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "2") }); - - try diffCleanupSemantic(allocator, &diffs); - - try testing.expectEqualDeep(&[_]Diff{ + }, + }}); + + // Multiple elimination + try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupSemantic, .{.{ + .input = &.{ + .{ .operation = .insert, .text = "1" }, + .{ .operation = .equal, .text = "A" }, + .{ .operation = .delete, .text = "B" }, + .{ .operation = .insert, .text = "2" }, + .{ .operation = .equal, .text = "_" }, + .{ .operation = .insert, .text = "1" }, + .{ .operation = .equal, .text = "A" }, + .{ .operation = .delete, .text = "B" }, + .{ .operation = .insert, .text = "2" }, + }, + .expected = &.{ .{ .operation = .delete, .text = "AB_AB" }, .{ .operation = .insert, .text = "1A2_1A2" }, - }, diffs.items); - } - - { - // Word boundaries - var diffs = try DiffList.initCapacity(allocator, 3); - defer deinitDiffList(allocator, &diffs); - - diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "The c") }); - diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "ow and the c") }); - diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "at.") }); - - try diffCleanupSemantic(allocator, &diffs); - - try testing.expectEqualDeep(&[_]Diff{ + }, + }}); + + if (true) return error.SkipZigTest; // TODO + + // Word boundaries + try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupSemantic, .{.{ + .input = &.{ + .{ .operation = .equal, .text = "The c" }, + .{ .operation = .delete, .text = "ow and the c" }, + .{ .operation = .equal, .text = "at." }, + }, + .expected = &.{ .{ .operation = .equal, .text = "The " }, .{ .operation = .delete, .text = "cow and the " }, .{ .operation = .equal, .text = "cat." }, - }, diffs.items); - } - - { - // No overlap elimination - var diffs = try DiffList.initCapacity(allocator, 2); - defer deinitDiffList(allocator, &diffs); - - diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "abcxx") }); - diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "xxdef") }); - - try diffCleanupSemantic(allocator, &diffs); + }, + }}); - try testing.expectEqualDeep(&[_]Diff{ + // No overlap elimination + try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupSemantic, .{.{ + .input = &.{ .{ .operation = .delete, .text = "abcxx" }, .{ .operation = .insert, .text = "xxdef" }, - }, diffs.items); - } - - { - // Overlap elimination - var diffs = try DiffList.initCapacity(allocator, 2); - defer deinitDiffList(allocator, &diffs); - - diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "abcxxx") }); - diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "xxxdef") }); - - try diffCleanupSemantic(allocator, &diffs); - - try testing.expectEqualDeep(&[_]Diff{ + }, + .expected = &.{ + .{ .operation = .delete, .text = "abcxx" }, + .{ .operation = .insert, .text = "xxdef" }, + }, + }}); + + // Overlap elimination + try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupSemantic, .{.{ + .input = &.{ + .{ .operation = .delete, .text = "abcxxx" }, + .{ .operation = .insert, .text = "xxxdef" }, + }, + .expected = &.{ .{ .operation = .delete, .text = "abc" }, .{ .operation = .equal, .text = "xxx" }, .{ .operation = .insert, .text = "def" }, - }, diffs.items); - } - - { - // Reverse overlap elimination - var diffs = try DiffList.initCapacity(allocator, 2); - defer deinitDiffList(allocator, &diffs); - - diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "xxxabc") }); - diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "defxxx") }); - - try diffCleanupSemantic(allocator, &diffs); - - try testing.expectEqualDeep(&[_]Diff{ + }, + }}); + + // Reverse overlap elimination + try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupSemantic, .{.{ + .input = &.{ + .{ .operation = .delete, .text = "xxxabc" }, + .{ .operation = .insert, .text = "defxxx" }, + }, + .expected = &.{ .{ .operation = .insert, .text = "def" }, .{ .operation = .equal, .text = "xxx" }, .{ .operation = .delete, .text = "abc" }, - }, diffs.items); - } - - { - // Two overlap eliminations - var diffs = try DiffList.initCapacity(allocator, 5); - defer deinitDiffList(allocator, &diffs); - - diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "abcd1212") }); - diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "1212efghi") }); - diffs.appendAssumeCapacity(.{ .operation = .equal, .text = try allocator.dupe(u8, "----") }); - diffs.appendAssumeCapacity(.{ .operation = .delete, .text = try allocator.dupe(u8, "A3") }); - diffs.appendAssumeCapacity(.{ .operation = .insert, .text = try allocator.dupe(u8, "3BC") }); - - try diffCleanupSemantic(allocator, &diffs); - - try testing.expectEqualDeep(&[_]Diff{ + }, + }}); + + // Two overlap eliminations + try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupSemantic, .{.{ + .input = &.{ + .{ .operation = .delete, .text = "abcd1212" }, + .{ .operation = .insert, .text = "1212efghi" }, + .{ .operation = .equal, .text = "----" }, + .{ .operation = .delete, .text = "A3" }, + .{ .operation = .insert, .text = "3BC" }, + }, + .expected = &.{ .{ .operation = .delete, .text = "abcd" }, .{ .operation = .equal, .text = "1212" }, .{ .operation = .insert, .text = "efghi" }, @@ -2617,6 +2646,6 @@ test diffCleanupSemantic { .{ .operation = .delete, .text = "A" }, .{ .operation = .equal, .text = "3" }, .{ .operation = .insert, .text = "BC" }, - }, diffs.items); - } + }, + }}); } From 8cb7f017670edd3147e78ba3f548388a90b323cf Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Sun, 7 Jul 2024 14:47:46 -0400 Subject: [PATCH 34/60] errdefer halfmatches --- DiffMatchPatch.zig | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/DiffMatchPatch.zig b/DiffMatchPatch.zig index d9ba1f7..3f6789e 100644 --- a/DiffMatchPatch.zig +++ b/DiffMatchPatch.zig @@ -378,8 +378,14 @@ fn diffHalfMatch( // First check if the second quarter is the seed for a half-match. const half_match_1 = try dmp.diffHalfMatchInternal(allocator, long_text, short_text, (long_text.len + 3) / 4); + errdefer { + if (half_match_1) |h_m| h_m.deinit(allocator); + } // Check again based on the third quarter. const half_match_2 = try dmp.diffHalfMatchInternal(allocator, long_text, short_text, (long_text.len + 1) / 2); + errdefer { + if (half_match_2) |h_m| h_m.deinit(allocator); + } var half_match: ?HalfMatchResult = null; if (half_match_1 == null and half_match_2 == null) { @@ -471,12 +477,14 @@ fn diffHalfMatchInternal( errdefer allocator.free(prefix_after); const suffix_after = try allocator.dupe(u8, best_short_text_b); errdefer allocator.free(suffix_after); + const best_common_text = try best_common.toOwnedSlice(allocator); + errdefer allocator.free(best_common_text); return .{ .prefix_before = prefix_before, .suffix_before = suffix_before, .prefix_after = prefix_after, .suffix_after = suffix_after, - .common_middle = try best_common.toOwnedSlice(allocator), + .common_middle = best_common_text, }; } else { return null; @@ -1551,8 +1559,6 @@ test diffHalfMatch { .expected = null, }}); - if (true) return error.SkipZigTest; // TODO - // Single matches try testing.checkAllAllocationFailures(testing.allocator, testDiffHalfMatch, .{.{ .dmp = one_timeout, From aedb7d60c0fc73edfb0a59b605d8c2a9d22c169b Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Sun, 7 Jul 2024 16:44:46 -0400 Subject: [PATCH 35/60] Fixes two leaks --- DiffMatchPatch.zig | 98 +++++++++++++++++++++++----------------------- 1 file changed, 50 insertions(+), 48 deletions(-) diff --git a/DiffMatchPatch.zig b/DiffMatchPatch.zig index 3f6789e..daec121 100644 --- a/DiffMatchPatch.zig +++ b/DiffMatchPatch.zig @@ -862,13 +862,12 @@ fn diffCharsToLines( defer text.deinit(allocator); for (diffs) |*d| { - text.items.len = 0; var j: usize = 0; while (j < d.text.len) : (j += 1) { try text.appendSlice(allocator, line_array[d.text[j]]); } allocator.free(d.text); - d.text = try allocator.dupe(u8, text.items); + d.text = try text.toOwnedSlice(allocator); } } @@ -913,16 +912,15 @@ fn diffCleanupMerge(allocator: std.mem.Allocator, diffs: *DiffList) DiffError!vo { const ii = pointer - count_delete - count_insert - 1; var nt = try allocator.alloc(u8, diffs.items[ii].text.len + common_length); - const ot = diffs.items[ii].text; defer allocator.free(ot); @memcpy(nt[0..ot.len], ot); @memcpy(nt[ot.len..], text_insert.items[0..common_length]); diffs.items[ii].text = nt; } else { + try diffs.ensureUnusedCapacity(allocator, 1); const text = try allocator.dupe(u8, text_insert.items[0..common_length]); - errdefer allocator.free(text); - try diffs.insert(allocator, 0, Diff.init(.equal, text)); + diffs.insertAssumeCapacity(0, Diff.init(.equal, text)); pointer += 1; } try text_insert.replaceRange(allocator, 0, common_length, &.{}); @@ -933,11 +931,11 @@ fn diffCleanupMerge(allocator: std.mem.Allocator, diffs: *DiffList) DiffError!vo common_length = diffCommonSuffix(text_insert.items, text_delete.items); if (common_length != 0) { const old_text = diffs.items[pointer].text; - defer allocator.free(old_text); diffs.items[pointer].text = try std.mem.concat(allocator, u8, &.{ text_insert.items[text_insert.items.len - common_length ..], old_text, }); + defer allocator.free(old_text); text_insert.items.len -= common_length; text_delete.items.len -= common_length; } @@ -1004,38 +1002,38 @@ fn diffCleanupMerge(allocator: std.mem.Allocator, diffs: *DiffList) DiffError!vo { // This is a single edit surrounded by equalities. if (std.mem.endsWith(u8, diffs.items[pointer].text, diffs.items[pointer - 1].text)) { + const old_pt = diffs.items[pointer].text; const pt = try std.mem.concat(allocator, u8, &.{ diffs.items[pointer - 1].text, diffs.items[pointer].text[0 .. diffs.items[pointer].text.len - diffs.items[pointer - 1].text.len], }); + defer allocator.free(old_pt); + diffs.items[pointer].text = pt; + const old_pt1t = diffs.items[pointer + 1].text; const p1t = try std.mem.concat(allocator, u8, &.{ diffs.items[pointer - 1].text, diffs.items[pointer + 1].text, }); - const old_pt = diffs.items[pointer].text; - defer allocator.free(old_pt); - const old_pt1t = diffs.items[pointer + 1].text; defer allocator.free(old_pt1t); - diffs.items[pointer].text = pt; diffs.items[pointer + 1].text = p1t; freeRangeDiffList(allocator, diffs, pointer - 1, 1); try diffs.replaceRange(allocator, pointer - 1, 1, &.{}); changes = true; } else if (std.mem.startsWith(u8, diffs.items[pointer].text, diffs.items[pointer + 1].text)) { + const old_ptm1 = diffs.items[pointer - 1].text; const pm1t = try std.mem.concat(allocator, u8, &.{ diffs.items[pointer - 1].text, diffs.items[pointer + 1].text, }); + defer allocator.free(old_ptm1); + diffs.items[pointer - 1].text = pm1t; + const old_pt = diffs.items[pointer].text; + defer allocator.free(old_pt); const pt = try std.mem.concat(allocator, u8, &.{ diffs.items[pointer].text[diffs.items[pointer + 1].text.len..], diffs.items[pointer + 1].text, }); - const old_ptm1 = diffs.items[pointer - 1].text; - defer allocator.free(old_ptm1); - const old_pt = diffs.items[pointer].text; - defer allocator.free(old_pt); - diffs.items[pointer - 1].text = pm1t; diffs.items[pointer].text = pt; freeRangeDiffList(allocator, diffs, pointer + 1, 1); try diffs.replaceRange(allocator, pointer + 1, 1, &.{}); @@ -1777,14 +1775,16 @@ fn testDiffCharsToLines( } test diffCharsToLines { - if (true) return error.SkipZigTest; // TODO - // Convert chars up to lines. + var diff_list = DiffList{}; + defer deinitDiffList(testing.allocator, &diff_list); + try diff_list.ensureTotalCapacity(testing.allocator, 2); + diff_list.appendSliceAssumeCapacity(&.{ + Diff.init(.equal, try testing.allocator.dupe(u8, "\u{0001}\u{0002}\u{0001}")), + Diff.init(.insert, try testing.allocator.dupe(u8, "\u{0002}\u{0001}\u{0002}")), + }); try testing.checkAllAllocationFailures(testing.allocator, testDiffCharsToLines, .{.{ - .diffs = &.{ - .{ .operation = .equal, .text = "\u{0001}\u{0002}\u{0001}" }, - .{ .operation = .insert, .text = "\u{0002}\u{0001}\u{0002}" }, - }, + .diffs = diff_list.items, .line_array = &[_][]const u8{ "", "alpha\n", @@ -1900,8 +1900,6 @@ test diffCleanupMerge { }, }}); - if (true) return error.SkipZigTest; // TODO - // Prefix and suffix detection with equalities try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupMerge, .{.{ .input = &.{ @@ -1933,17 +1931,19 @@ test diffCleanupMerge { }}); // Slide edit right - try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupMerge, .{.{ - .input = &.{ - .{ .operation = .equal, .text = "c" }, - .{ .operation = .insert, .text = "ab" }, - .{ .operation = .equal, .text = "a" }, - }, - .expected = &.{ - .{ .operation = .equal, .text = "ca" }, - .{ .operation = .insert, .text = "ba" }, - }, - }}); + if (false) { // TODO #23 This test needs to dupe its data + try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupMerge, .{.{ + .input = &.{ + .{ .operation = .equal, .text = "c" }, + .{ .operation = .insert, .text = "ab" }, + .{ .operation = .equal, .text = "a" }, + }, + .expected = &.{ + .{ .operation = .equal, .text = "ca" }, + .{ .operation = .insert, .text = "ba" }, + }, + }}); + } // Slide edit left recursive try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupMerge, .{.{ @@ -1960,20 +1960,22 @@ test diffCleanupMerge { }, }}); - // Slide edit right recursive - try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupMerge, .{.{ - .input = &.{ - .{ .operation = .equal, .text = "x" }, - .{ .operation = .delete, .text = "ca" }, - .{ .operation = .equal, .text = "c" }, - .{ .operation = .delete, .text = "b" }, - .{ .operation = .equal, .text = "a" }, - }, - .expected = &.{ - .{ .operation = .equal, .text = "xca" }, - .{ .operation = .delete, .text = "cba" }, - }, - }}); + if (false) { // TODO #23 This test needs to dupe its data + // Slide edit right recursive + try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupMerge, .{.{ + .input = &.{ + .{ .operation = .equal, .text = "x" }, + .{ .operation = .delete, .text = "ca" }, + .{ .operation = .equal, .text = "c" }, + .{ .operation = .delete, .text = "b" }, + .{ .operation = .equal, .text = "a" }, + }, + .expected = &.{ + .{ .operation = .equal, .text = "xca" }, + .{ .operation = .delete, .text = "cba" }, + }, + }}); + } // Empty merge try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupMerge, .{.{ From 460f047e24378f749cfd5c071bbe6d0244012650 Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Sun, 7 Jul 2024 17:06:37 -0400 Subject: [PATCH 36/60] More memory order bugs --- DiffMatchPatch.zig | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/DiffMatchPatch.zig b/DiffMatchPatch.zig index daec121..e7cb9e5 100644 --- a/DiffMatchPatch.zig +++ b/DiffMatchPatch.zig @@ -627,6 +627,7 @@ fn diffBisect( // Diff took too long and hit the deadline or // number of diffs equals number of characters, no commonality at all. var diffs = DiffList{}; + errdefer deinitDiffList(allocator, &diffs); try diffs.ensureUnusedCapacity(allocator, 2); diffs.appendAssumeCapacity(Diff.init( .delete, @@ -1279,18 +1280,21 @@ pub fn diffCleanupSemanticLossless( if (!std.mem.eql(u8, diffs.items[pointer - 1].text, best_equality_1.items)) { // We have an improvement, save it back to the diff. if (best_equality_1.items.len != 0) { - allocator.free(diffs.items[pointer - 1].text); + const old_text = diffs.items[pointer - 1].text; diffs.items[pointer - 1].text = try allocator.dupe(u8, best_equality_1.items); + allocator.free(old_text); } else { const old_diff = diffs.orderedRemove(pointer - 1); allocator.free(old_diff.text); pointer -= 1; } - allocator.free(diffs.items[pointer].text); + const old_text1 = diffs.items[pointer].text; diffs.items[pointer].text = try allocator.dupe(u8, best_edit.items); + allocator.free(old_text1); if (best_equality_2.items.len != 0) { - allocator.free(diffs.items[pointer + 1].text); + const old_text2 = diffs.items[pointer + 1].text; diffs.items[pointer + 1].text = try allocator.dupe(u8, best_equality_2.items); + allocator.free(old_text2); } else { const old_diff = diffs.orderedRemove(pointer + 1); allocator.free(old_diff.text); @@ -2023,6 +2027,19 @@ fn testDiffCleanupSemanticLossless( try testing.expectEqualDeep(params.expected, diffs.items); } +fn sliceToDiffList(allocator: Allocator, diff_slice: []const Diff) !DiffList { + var diff_list = DiffList{}; + errdefer deinitDiffList(allocator, &diff_list); + try diff_list.ensureTotalCapacity(allocator, diff_slice.len); + for (diff_slice) |d| { + diff_list.appendAssumeCapacity(Diff.init( + d.operation, + try allocator.dupe(u8, d.text), + )); + } + return diff_list; +} + test diffCleanupSemanticLossless { // Null case try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupSemanticLossless, .{.{ @@ -2030,8 +2047,7 @@ test diffCleanupSemanticLossless { .expected = &[_]Diff{}, }}); - if (true) return error.SkipZigTest; // TODO - + //defer deinitDiffList(allocator, &diffs); try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupSemanticLossless, .{.{ .input = &.{ .{ .operation = .equal, .text = "AAA\r\n\r\nBBB" }, @@ -2179,8 +2195,6 @@ test diffBisect { }, }}); - if (true) return error.SkipZigTest; // TODO - // Timeout try testing.checkAllAllocationFailures(testing.allocator, testDiffBisect, .{.{ .dmp = this, From d5dff307f64a70585608a61747b065982b984012 Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Sun, 7 Jul 2024 17:13:58 -0400 Subject: [PATCH 37/60] Last of the skipped tests eliminated There are a few tests which need to have their data duped, to prevent a segfault. All clearly marked. --- DiffMatchPatch.zig | 94 +++++++++++++++++++++++----------------------- 1 file changed, 46 insertions(+), 48 deletions(-) diff --git a/DiffMatchPatch.zig b/DiffMatchPatch.zig index e7cb9e5..be82049 100644 --- a/DiffMatchPatch.zig +++ b/DiffMatchPatch.zig @@ -131,7 +131,7 @@ fn diffInternal( // Check for equality (speedup). if (std.mem.eql(u8, before, after)) { var diffs = DiffList{}; - + errdefer deinitDiffList(allocator, &diffs); if (before.len != 0) { try diffs.ensureUnusedCapacity(allocator, 1); diffs.appendAssumeCapacity(Diff.init( @@ -2224,8 +2224,6 @@ fn testDiff( } test diff { - if (true) return error.SkipZigTest; // TODO - const this: DiffMatchPatch = .{ .diff_timeout = 0 }; // Null case. @@ -2597,8 +2595,6 @@ test diffCleanupSemantic { }, }}); - if (true) return error.SkipZigTest; // TODO - // Word boundaries try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupSemantic, .{.{ .input = &.{ @@ -2625,49 +2621,51 @@ test diffCleanupSemantic { }, }}); - // Overlap elimination - try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupSemantic, .{.{ - .input = &.{ - .{ .operation = .delete, .text = "abcxxx" }, - .{ .operation = .insert, .text = "xxxdef" }, - }, - .expected = &.{ - .{ .operation = .delete, .text = "abc" }, - .{ .operation = .equal, .text = "xxx" }, - .{ .operation = .insert, .text = "def" }, - }, - }}); + if (false) { // TODO #23 This test needs to dupe its data + // Overlap elimination + try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupSemantic, .{.{ + .input = &.{ + .{ .operation = .delete, .text = "abcxxx" }, + .{ .operation = .insert, .text = "xxxdef" }, + }, + .expected = &.{ + .{ .operation = .delete, .text = "abc" }, + .{ .operation = .equal, .text = "xxx" }, + .{ .operation = .insert, .text = "def" }, + }, + }}); - // Reverse overlap elimination - try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupSemantic, .{.{ - .input = &.{ - .{ .operation = .delete, .text = "xxxabc" }, - .{ .operation = .insert, .text = "defxxx" }, - }, - .expected = &.{ - .{ .operation = .insert, .text = "def" }, - .{ .operation = .equal, .text = "xxx" }, - .{ .operation = .delete, .text = "abc" }, - }, - }}); + // Reverse overlap elimination + try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupSemantic, .{.{ + .input = &.{ + .{ .operation = .delete, .text = "xxxabc" }, + .{ .operation = .insert, .text = "defxxx" }, + }, + .expected = &.{ + .{ .operation = .insert, .text = "def" }, + .{ .operation = .equal, .text = "xxx" }, + .{ .operation = .delete, .text = "abc" }, + }, + }}); - // Two overlap eliminations - try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupSemantic, .{.{ - .input = &.{ - .{ .operation = .delete, .text = "abcd1212" }, - .{ .operation = .insert, .text = "1212efghi" }, - .{ .operation = .equal, .text = "----" }, - .{ .operation = .delete, .text = "A3" }, - .{ .operation = .insert, .text = "3BC" }, - }, - .expected = &.{ - .{ .operation = .delete, .text = "abcd" }, - .{ .operation = .equal, .text = "1212" }, - .{ .operation = .insert, .text = "efghi" }, - .{ .operation = .equal, .text = "----" }, - .{ .operation = .delete, .text = "A" }, - .{ .operation = .equal, .text = "3" }, - .{ .operation = .insert, .text = "BC" }, - }, - }}); + // Two overlap eliminations + try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupSemantic, .{.{ + .input = &.{ + .{ .operation = .delete, .text = "abcd1212" }, + .{ .operation = .insert, .text = "1212efghi" }, + .{ .operation = .equal, .text = "----" }, + .{ .operation = .delete, .text = "A3" }, + .{ .operation = .insert, .text = "3BC" }, + }, + .expected = &.{ + .{ .operation = .delete, .text = "abcd" }, + .{ .operation = .equal, .text = "1212" }, + .{ .operation = .insert, .text = "efghi" }, + .{ .operation = .equal, .text = "----" }, + .{ .operation = .delete, .text = "A" }, + .{ .operation = .equal, .text = "3" }, + .{ .operation = .insert, .text = "BC" }, + }, + }}); + } } From ddcbe8f38bf2ffb2a522734aa52ce0c8c3cf0226 Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Sun, 7 Jul 2024 19:36:22 -0400 Subject: [PATCH 38/60] Errdefer in rebuildtexts --- DiffMatchPatch.zig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/DiffMatchPatch.zig b/DiffMatchPatch.zig index be82049..43f35cb 100644 --- a/DiffMatchPatch.zig +++ b/DiffMatchPatch.zig @@ -2144,6 +2144,10 @@ fn rebuildtexts(allocator: std.mem.Allocator, diffs: DiffList) ![2][]const u8 { std.ArrayList(u8).init(allocator), std.ArrayList(u8).init(allocator), }; + errdefer { + allocator.free(text[0]); + allocator.free(text[1]); + } for (diffs.items) |myDiff| { if (myDiff.operation != .insert) { From 593d38f2f88d0bfe28286731e91c4988ca030b7f Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Sun, 7 Jul 2024 20:03:57 -0400 Subject: [PATCH 39/60] Tests for rebuildtexts --- DiffMatchPatch.zig | 64 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 61 insertions(+), 3 deletions(-) diff --git a/DiffMatchPatch.zig b/DiffMatchPatch.zig index 43f35cb..b2454e6 100644 --- a/DiffMatchPatch.zig +++ b/DiffMatchPatch.zig @@ -2138,15 +2138,14 @@ test diffCleanupSemanticLossless { }}); } -/// TODO this function obviously leaks memory on error fn rebuildtexts(allocator: std.mem.Allocator, diffs: DiffList) ![2][]const u8 { var text = [2]std.ArrayList(u8){ std.ArrayList(u8).init(allocator), std.ArrayList(u8).init(allocator), }; errdefer { - allocator.free(text[0]); - allocator.free(text[1]); + text[0].deinit(); + text[1].deinit(); } for (diffs.items) |myDiff| { @@ -2163,6 +2162,65 @@ fn rebuildtexts(allocator: std.mem.Allocator, diffs: DiffList) ![2][]const u8 { }; } +fn testRebuildTexts(allocator: Allocator, diffs: DiffList, params: struct { + before: []const u8, + after: []const u8, +}) !void { + const texts = try rebuildtexts(allocator, diffs); + defer { + allocator.free(texts[0]); + allocator.free(texts[1]); + } + try testing.expectEqualStrings(params.before, texts[0]); + try testing.expectEqualStrings(params.after, texts[1]); +} + +test rebuildtexts { + { + var diffs = try sliceToDiffList(testing.allocator, &.{ + .{ .operation = .insert, .text = "abcabc" }, + .{ .operation = .equal, .text = "defdef" }, + .{ .operation = .delete, .text = "ghighi" }, + }); + defer deinitDiffList(testing.allocator, &diffs); + try testing.checkAllAllocationFailures(testing.allocator, testRebuildTexts, .{ + diffs, + .{ + .before = "defdefghighi", + .after = "abcabcdefdef", + }, + }); + } + { + var diffs = try sliceToDiffList(testing.allocator, &.{ + .{ .operation = .insert, .text = "xxx" }, + .{ .operation = .delete, .text = "yyy" }, + }); + defer deinitDiffList(testing.allocator, &diffs); + try testing.checkAllAllocationFailures(testing.allocator, testRebuildTexts, .{ + diffs, + .{ + .before = "yyy", + .after = "xxx", + }, + }); + } + { + var diffs = try sliceToDiffList(testing.allocator, &.{ + .{ .operation = .equal, .text = "xyz" }, + .{ .operation = .equal, .text = "pdq" }, + }); + defer deinitDiffList(testing.allocator, &diffs); + try testing.checkAllAllocationFailures(testing.allocator, testRebuildTexts, .{ + diffs, + .{ + .before = "xyzpdq", + .after = "xyzpdq", + }, + }); + } +} + fn testDiffBisect( allocator: std.mem.Allocator, params: struct { From 39b99aac99c72722b76547ff324ff8d7f9022baa Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Sun, 7 Jul 2024 20:45:16 -0400 Subject: [PATCH 40/60] Last of the leaks --- DiffMatchPatch.zig | 167 ++++++++++++++++++++++----------------------- 1 file changed, 81 insertions(+), 86 deletions(-) diff --git a/DiffMatchPatch.zig b/DiffMatchPatch.zig index b2454e6..3b863fd 100644 --- a/DiffMatchPatch.zig +++ b/DiffMatchPatch.zig @@ -1009,14 +1009,14 @@ fn diffCleanupMerge(allocator: std.mem.Allocator, diffs: *DiffList) DiffError!vo diffs.items[pointer].text[0 .. diffs.items[pointer].text.len - diffs.items[pointer - 1].text.len], }); - defer allocator.free(old_pt); + allocator.free(old_pt); diffs.items[pointer].text = pt; const old_pt1t = diffs.items[pointer + 1].text; const p1t = try std.mem.concat(allocator, u8, &.{ diffs.items[pointer - 1].text, diffs.items[pointer + 1].text, }); - defer allocator.free(old_pt1t); + allocator.free(old_pt1t); diffs.items[pointer + 1].text = p1t; freeRangeDiffList(allocator, diffs, pointer - 1, 1); try diffs.replaceRange(allocator, pointer - 1, 1, &.{}); @@ -1027,14 +1027,14 @@ fn diffCleanupMerge(allocator: std.mem.Allocator, diffs: *DiffList) DiffError!vo diffs.items[pointer - 1].text, diffs.items[pointer + 1].text, }); - defer allocator.free(old_ptm1); + allocator.free(old_ptm1); diffs.items[pointer - 1].text = pm1t; const old_pt = diffs.items[pointer].text; - defer allocator.free(old_pt); const pt = try std.mem.concat(allocator, u8, &.{ diffs.items[pointer].text[diffs.items[pointer + 1].text.len..], diffs.items[pointer + 1].text, }); + allocator.free(old_pt); diffs.items[pointer].text = pt; freeRangeDiffList(allocator, diffs, pointer + 1, 1); try diffs.replaceRange(allocator, pointer + 1, 1, &.{}); @@ -1141,8 +1141,6 @@ fn diffCleanupSemantic(allocator: std.mem.Allocator, diffs: *DiffList) DiffError { // Overlap found. // Insert an equality and trim the surrounding edits. - defer allocator.free(deletion); - defer allocator.free(insertion); try diffs.ensureUnusedCapacity(allocator, 1); diffs.insertAssumeCapacity( @intCast(pointer), @@ -1153,8 +1151,10 @@ fn diffCleanupSemantic(allocator: std.mem.Allocator, diffs: *DiffList) DiffError ); diffs.items[@intCast(pointer - 1)].text = try allocator.dupe(u8, deletion[0 .. deletion.len - overlap_length1]); + allocator.free(deletion); diffs.items[@intCast(pointer + 1)].text = try allocator.dupe(u8, insertion[overlap_length1..]); + allocator.free(insertion); pointer += 1; } } else { @@ -1163,8 +1163,6 @@ fn diffCleanupSemantic(allocator: std.mem.Allocator, diffs: *DiffList) DiffError { // Reverse overlap found. // Insert an equality and swap and trim the surrounding edits. - defer allocator.free(deletion); - defer allocator.free(insertion); try diffs.ensureUnusedCapacity(allocator, 1); diffs.insertAssumeCapacity( @intCast(pointer), @@ -1173,11 +1171,14 @@ fn diffCleanupSemantic(allocator: std.mem.Allocator, diffs: *DiffList) DiffError try allocator.dupe(u8, deletion[0..overlap_length2]), ), ); - diffs.items[@intCast(pointer - 1)].operation = .insert; const new_minus = try allocator.dupe(u8, insertion[0 .. insertion.len - overlap_length2]); + errdefer allocator.free(new_minus); // necessary due to swap + const new_plus = try allocator.dupe(u8, deletion[overlap_length2..]); + allocator.free(deletion); + allocator.free(insertion); + diffs.items[@intCast(pointer - 1)].operation = .insert; diffs.items[@intCast(pointer - 1)].text = new_minus; diffs.items[@intCast(pointer + 1)].operation = .delete; - const new_plus = try allocator.dupe(u8, deletion[overlap_length2..]); diffs.items[@intCast(pointer + 1)].text = new_plus; pointer += 1; } @@ -1290,7 +1291,7 @@ pub fn diffCleanupSemanticLossless( } const old_text1 = diffs.items[pointer].text; diffs.items[pointer].text = try allocator.dupe(u8, best_edit.items); - allocator.free(old_text1); + defer allocator.free(old_text1); if (best_equality_2.items.len != 0) { const old_text2 = diffs.items[pointer + 1].text; diffs.items[pointer + 1].text = try allocator.dupe(u8, best_equality_2.items); @@ -1935,19 +1936,17 @@ test diffCleanupMerge { }}); // Slide edit right - if (false) { // TODO #23 This test needs to dupe its data - try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupMerge, .{.{ - .input = &.{ - .{ .operation = .equal, .text = "c" }, - .{ .operation = .insert, .text = "ab" }, - .{ .operation = .equal, .text = "a" }, - }, - .expected = &.{ - .{ .operation = .equal, .text = "ca" }, - .{ .operation = .insert, .text = "ba" }, - }, - }}); - } + try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupMerge, .{.{ + .input = &.{ + .{ .operation = .equal, .text = "c" }, + .{ .operation = .insert, .text = "ab" }, + .{ .operation = .equal, .text = "a" }, + }, + .expected = &.{ + .{ .operation = .equal, .text = "ca" }, + .{ .operation = .insert, .text = "ba" }, + }, + }}); // Slide edit left recursive try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupMerge, .{.{ @@ -1964,22 +1963,20 @@ test diffCleanupMerge { }, }}); - if (false) { // TODO #23 This test needs to dupe its data - // Slide edit right recursive - try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupMerge, .{.{ - .input = &.{ - .{ .operation = .equal, .text = "x" }, - .{ .operation = .delete, .text = "ca" }, - .{ .operation = .equal, .text = "c" }, - .{ .operation = .delete, .text = "b" }, - .{ .operation = .equal, .text = "a" }, - }, - .expected = &.{ - .{ .operation = .equal, .text = "xca" }, - .{ .operation = .delete, .text = "cba" }, - }, - }}); - } + // Slide edit right recursive + try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupMerge, .{.{ + .input = &.{ + .{ .operation = .equal, .text = "x" }, + .{ .operation = .delete, .text = "ca" }, + .{ .operation = .equal, .text = "c" }, + .{ .operation = .delete, .text = "b" }, + .{ .operation = .equal, .text = "a" }, + }, + .expected = &.{ + .{ .operation = .equal, .text = "xca" }, + .{ .operation = .delete, .text = "cba" }, + }, + }}); // Empty merge try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupMerge, .{.{ @@ -2683,51 +2680,49 @@ test diffCleanupSemantic { }, }}); - if (false) { // TODO #23 This test needs to dupe its data - // Overlap elimination - try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupSemantic, .{.{ - .input = &.{ - .{ .operation = .delete, .text = "abcxxx" }, - .{ .operation = .insert, .text = "xxxdef" }, - }, - .expected = &.{ - .{ .operation = .delete, .text = "abc" }, - .{ .operation = .equal, .text = "xxx" }, - .{ .operation = .insert, .text = "def" }, - }, - }}); + // Overlap elimination + try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupSemantic, .{.{ + .input = &.{ + .{ .operation = .delete, .text = "abcxxx" }, + .{ .operation = .insert, .text = "xxxdef" }, + }, + .expected = &.{ + .{ .operation = .delete, .text = "abc" }, + .{ .operation = .equal, .text = "xxx" }, + .{ .operation = .insert, .text = "def" }, + }, + }}); - // Reverse overlap elimination - try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupSemantic, .{.{ - .input = &.{ - .{ .operation = .delete, .text = "xxxabc" }, - .{ .operation = .insert, .text = "defxxx" }, - }, - .expected = &.{ - .{ .operation = .insert, .text = "def" }, - .{ .operation = .equal, .text = "xxx" }, - .{ .operation = .delete, .text = "abc" }, - }, - }}); - - // Two overlap eliminations - try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupSemantic, .{.{ - .input = &.{ - .{ .operation = .delete, .text = "abcd1212" }, - .{ .operation = .insert, .text = "1212efghi" }, - .{ .operation = .equal, .text = "----" }, - .{ .operation = .delete, .text = "A3" }, - .{ .operation = .insert, .text = "3BC" }, - }, - .expected = &.{ - .{ .operation = .delete, .text = "abcd" }, - .{ .operation = .equal, .text = "1212" }, - .{ .operation = .insert, .text = "efghi" }, - .{ .operation = .equal, .text = "----" }, - .{ .operation = .delete, .text = "A" }, - .{ .operation = .equal, .text = "3" }, - .{ .operation = .insert, .text = "BC" }, - }, - }}); - } + // Reverse overlap elimination + try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupSemantic, .{.{ + .input = &.{ + .{ .operation = .delete, .text = "xxxabc" }, + .{ .operation = .insert, .text = "defxxx" }, + }, + .expected = &.{ + .{ .operation = .insert, .text = "def" }, + .{ .operation = .equal, .text = "xxx" }, + .{ .operation = .delete, .text = "abc" }, + }, + }}); + + // Two overlap eliminations + try testing.checkAllAllocationFailures(testing.allocator, testDiffCleanupSemantic, .{.{ + .input = &.{ + .{ .operation = .delete, .text = "abcd1212" }, + .{ .operation = .insert, .text = "1212efghi" }, + .{ .operation = .equal, .text = "----" }, + .{ .operation = .delete, .text = "A3" }, + .{ .operation = .insert, .text = "3BC" }, + }, + .expected = &.{ + .{ .operation = .delete, .text = "abcd" }, + .{ .operation = .equal, .text = "1212" }, + .{ .operation = .insert, .text = "efghi" }, + .{ .operation = .equal, .text = "----" }, + .{ .operation = .delete, .text = "A" }, + .{ .operation = .equal, .text = "3" }, + .{ .operation = .insert, .text = "BC" }, + }, + }}); } From db41749056eb98590814eeff087e51f688fa5f43 Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Mon, 8 Jul 2024 13:01:54 -0400 Subject: [PATCH 41/60] Null test for diffCleanupEfficiency --- DiffMatchPatch.zig | 60 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 49 insertions(+), 11 deletions(-) diff --git a/DiffMatchPatch.zig b/DiffMatchPatch.zig index 3b863fd..6e673a2 100644 --- a/DiffMatchPatch.zig +++ b/DiffMatchPatch.zig @@ -1361,6 +1361,13 @@ fn diffCleanupSemanticScore(one: []const u8, two: []const u8) usize { return 0; } +inline fn boolInt(b: bool) usize { + if (b) + return 1 + else + return 0; +} + /// Reduce the number of edits by eliminating operationally trivial /// equalities. pub fn diffCleanupEfficiency( @@ -1370,11 +1377,11 @@ pub fn diffCleanupEfficiency( ) DiffError!void { var changes = false; // Stack of indices where equalities are found. - var equalities = DiffList{}; - defer deinitDiffList(allocator, equalities); + var equalities = std.ArrayList(usize).init(allocator); + defer equalities.deinit(); // Always equal to equalities[equalitiesLength-1][1] - var last_equality = ""; - var pointer: isize = 0; // Index of current position. + var last_equality: []const u8 = ""; + var ipointer: isize = 0; // Index of current position. // Is there an insertion operation before the last equality. var pre_ins = false; // Is there a deletion operation before the last equality. @@ -1383,11 +1390,12 @@ pub fn diffCleanupEfficiency( var post_ins = false; // Is there a deletion operation after the last equality. var post_del = false; - while (pointer < diffs.Count) { + while (ipointer < diffs.items.len) { + const pointer: usize = @intCast(ipointer); if (diffs.items[pointer].operation == .equal) { // Equality found. if (diffs.items[pointer].text.len < dmp.diff_edit_cost and (post_ins or post_del)) { // Candidate found. - equalities.Push(pointer); + try equalities.append(pointer); pre_ins = post_ins; pre_del = post_del; last_equality = diffs.items[pointer].text; @@ -1410,10 +1418,10 @@ pub fn diffCleanupEfficiency( // ABXC // AXCD // ABXC - if ((last_equality.Length != 0) and + if ((last_equality.len != 0) and ((pre_ins and pre_del and post_ins and post_del) or - ((last_equality.Length < dmp.diff_edit_cost / 2) and - ((if (pre_ins) 1 else 0) + (if (pre_del) 1 else 0) + (if (post_ins) 1 else 0) + (if (post_del) 1 else 0)) == 3))) + ((last_equality.len < dmp.diff_edit_cost / 2) and + (boolInt(pre_ins) + boolInt(pre_del) + boolInt(post_ins) + boolInt(post_del) == 3)))) { // Duplicate record. try diffs.ensureUnusedCapacity(allocator, 1); @@ -1438,14 +1446,14 @@ pub fn diffCleanupEfficiency( _ = equalities.pop(); } - pointer = if (equalities.items.len > 0) equalities.items[equalities.items.len - 1] else -1; + ipointer = if (equalities.items.len > 0) @intCast(equalities.items[equalities.items.len - 1]) else -1; post_ins = false; post_del = false; } changes = true; } } - pointer += 1; + ipointer += 1; } if (changes) { @@ -2726,3 +2734,33 @@ test diffCleanupSemantic { }, }}); } + +fn testDiffCleanupEfficienct( + allocator: Allocator, + dmp: DiffMatchPatch, + params: struct { + input: []Diff, + expected: []Diff, + }, +) !void { + var diffs = try DiffList.initCapacity(allocator, params.input.len); + defer deinitDiffList(allocator, &diffs); + for (params.input) |item| { + diffs.appendAssumeCapacity(.{ .operation = item.operation, .text = try allocator.dupe(u8, item.text) }); + } + try dmp.diffCleanupEfficiency(allocator, &diffs); + + try testing.expectEqualDeep(params.expected, diffs.items); +} + +test "diffCleanupEfficiency" { + const allocator = testing.allocator; + var dmp = DiffMatchPatch{}; + dmp.diff_edit_cost = 4; + { + // Null case + var diffs = DiffList{}; + try dmp.diffCleanupEfficiency(allocator, &diffs); + try testing.expectEqualDeep(DiffList{}, diffs); + } +} From 7de639091f61b05d1186901cccced210d5e0c7c6 Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Mon, 8 Jul 2024 13:26:50 -0400 Subject: [PATCH 42/60] Finish port of diffMatchEfficiency --- DiffMatchPatch.zig | 113 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 108 insertions(+), 5 deletions(-) diff --git a/DiffMatchPatch.zig b/DiffMatchPatch.zig index 6e673a2..abdddfb 100644 --- a/DiffMatchPatch.zig +++ b/DiffMatchPatch.zig @@ -2735,12 +2735,12 @@ test diffCleanupSemantic { }}); } -fn testDiffCleanupEfficienct( +fn testDiffCleanupEfficiency( allocator: Allocator, dmp: DiffMatchPatch, params: struct { - input: []Diff, - expected: []Diff, + input: []const Diff, + expected: []const Diff, }, ) !void { var diffs = try DiffList.initCapacity(allocator, params.input.len); @@ -2757,10 +2757,113 @@ test "diffCleanupEfficiency" { const allocator = testing.allocator; var dmp = DiffMatchPatch{}; dmp.diff_edit_cost = 4; - { - // Null case + { // Null case. var diffs = DiffList{}; try dmp.diffCleanupEfficiency(allocator, &diffs); try testing.expectEqualDeep(DiffList{}, diffs); } + { // No elimination. + const dslice: []const Diff = &.{ + .{ .operation = .delete, .text = "ab" }, + .{ .operation = .insert, .text = "12" }, + .{ .operation = .equal, .text = "wxyz" }, + .{ .operation = .delete, .text = "cd" }, + .{ .operation = .insert, .text = "34" }, + }; + try testing.checkAllAllocationFailures( + testing.allocator, + testDiffCleanupEfficiency, + .{ + dmp, + .{ .input = dslice, .expected = dslice }, + }, + ); + } + { // Four-edit elimination. + const dslice: []const Diff = &.{ + .{ .operation = .delete, .text = "ab" }, + .{ .operation = .insert, .text = "12" }, + .{ .operation = .equal, .text = "xyz" }, + .{ .operation = .delete, .text = "cd" }, + .{ .operation = .insert, .text = "34" }, + }; + const d_after: []const Diff = &.{ + .{ .operation = .delete, .text = "abxyzcd" }, + .{ .operation = .insert, .text = "12xyz34" }, + }; + try testing.checkAllAllocationFailures( + testing.allocator, + testDiffCleanupEfficiency, + .{ + dmp, + .{ .input = dslice, .expected = d_after }, + }, + ); + } + { // Three-edit elimination. + const dslice: []const Diff = &.{ + .{ .operation = .insert, .text = "12" }, + .{ .operation = .equal, .text = "x" }, + .{ .operation = .delete, .text = "cd" }, + .{ .operation = .insert, .text = "34" }, + }; + const d_after: []const Diff = &.{ + .{ .operation = .delete, .text = "xcd" }, + .{ .operation = .insert, .text = "12x34" }, + }; + try testing.checkAllAllocationFailures( + testing.allocator, + testDiffCleanupEfficiency, + .{ + dmp, + .{ .input = dslice, .expected = d_after }, + }, + ); + } + { // Backpass elimination. + const dslice: []const Diff = &.{ + .{ .operation = .delete, .text = "ab" }, + .{ .operation = .insert, .text = "12" }, + .{ .operation = .equal, .text = "xy" }, + .{ .operation = .insert, .text = "34" }, + .{ .operation = .equal, .text = "z" }, + .{ .operation = .delete, .text = "cd" }, + .{ .operation = .insert, .text = "56" }, + }; + const d_after: []const Diff = &.{ + .{ .operation = .delete, .text = "abxyzcd" }, + .{ .operation = .insert, .text = "12xy34z56" }, + }; + try testing.checkAllAllocationFailures( + testing.allocator, + testDiffCleanupEfficiency, + .{ + dmp, + .{ .input = dslice, .expected = d_after }, + }, + ); + } + { // High cost elimination. + dmp.diff_edit_cost = 5; + const dslice: []const Diff = &.{ + .{ .operation = .delete, .text = "ab" }, + .{ .operation = .insert, .text = "12" }, + .{ .operation = .equal, .text = "wxyz" }, + .{ .operation = .delete, .text = "cd" }, + .{ .operation = .insert, .text = "34" }, + }; + const d_after: []const Diff = &.{ + .{ .operation = .delete, .text = "abwxyzcd" }, + .{ .operation = .insert, .text = "12wxyz34" }, + }; + try testing.checkAllAllocationFailures( + testing.allocator, + testDiffCleanupEfficiency, + .{ + dmp, + .{ .input = dslice, .expected = d_after }, + }, + ); + dmp.diff_edit_cost = 4; + } } From e564009c0e1008d593d676b0fc8f5d3d346d081f Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Mon, 8 Jul 2024 13:36:32 -0400 Subject: [PATCH 43/60] Add kcov coverage step --- build.zig | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/build.zig b/build.zig index dedeff9..c9d8047 100644 --- a/build.zig +++ b/build.zig @@ -20,4 +20,19 @@ pub fn build(b: *std.Build) void { const step_tests = b.addRunArtifact(tests); b.step("test", "Run diffz tests").dependOn(&step_tests.step); + + // Adds a step to generate code coverage + const cov_step = b.step("cov", "Generate coverage (kcov must be installed)"); + + const cov_run = b.addSystemCommand(&.{ + "kcov", + "--clean", + "--include-pattern=DiffMatchPatch.zig", + "--exclude-line=unreachable,expect(false)", + "kcov-output", + }); + cov_run.addArtifactArg(tests); + cov_step.dependOn(&cov_run.step); + _ = cov_run.captureStdOut(); + _ = cov_run.captureStdErr(); } From 0264b16ec7d209688ff7a1bb10dac2164bcc91c0 Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Mon, 8 Jul 2024 13:37:24 -0400 Subject: [PATCH 44/60] Remove unnecessary errdefer --- .gitignore | 1 + DiffMatchPatch.zig | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 8c9d17e..8571fdf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .zig-cache zig-cache zig-out +kcov-output \ No newline at end of file diff --git a/DiffMatchPatch.zig b/DiffMatchPatch.zig index abdddfb..2bf574f 100644 --- a/DiffMatchPatch.zig +++ b/DiffMatchPatch.zig @@ -476,7 +476,6 @@ fn diffHalfMatchInternal( const prefix_after = try allocator.dupe(u8, best_short_text_a); errdefer allocator.free(prefix_after); const suffix_after = try allocator.dupe(u8, best_short_text_b); - errdefer allocator.free(suffix_after); const best_common_text = try best_common.toOwnedSlice(allocator); errdefer allocator.free(best_common_text); return .{ From 046fa0deba3334023703565cfa30ea4919933e06 Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Tue, 9 Jul 2024 12:56:16 -0400 Subject: [PATCH 45/60] Fix half_match code pathway to handle allocation fails --- DiffMatchPatch.zig | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/DiffMatchPatch.zig b/DiffMatchPatch.zig index 2bf574f..dfec56c 100644 --- a/DiffMatchPatch.zig +++ b/DiffMatchPatch.zig @@ -222,11 +222,10 @@ fn diffCompute( check_lines: bool, deadline: u64, ) DiffError!DiffList { - var diffs = DiffList{}; - errdefer deinitDiffList(allocator, &diffs); - if (before.len == 0) { // Just add some text (speedup). + var diffs = DiffList{}; + errdefer deinitDiffList(allocator, &diffs); try diffs.ensureUnusedCapacity(allocator, 1); diffs.appendAssumeCapacity(Diff.init( .insert, @@ -237,6 +236,8 @@ fn diffCompute( if (after.len == 0) { // Just delete some text (speedup). + var diffs = DiffList{}; + errdefer deinitDiffList(allocator, &diffs); try diffs.ensureUnusedCapacity(allocator, 1); diffs.appendAssumeCapacity(Diff.init( .delete, @@ -249,6 +250,8 @@ fn diffCompute( const short_text = if (before.len > after.len) after else before; if (std.mem.indexOf(u8, long_text, short_text)) |index| { + var diffs = DiffList{}; + errdefer deinitDiffList(allocator, &diffs); // Shorter text is inside the longer text (speedup). const op: Diff.Operation = if (before.len > after.len) .delete @@ -273,6 +276,8 @@ fn diffCompute( if (short_text.len == 1) { // Single character string. // After the previous speedup, the character can't be an equality. + var diffs = DiffList{}; + errdefer deinitDiffList(allocator, &diffs); try diffs.ensureUnusedCapacity(allocator, 2); diffs.appendAssumeCapacity(Diff.init( .delete, @@ -290,13 +295,14 @@ fn diffCompute( // A half-match was found, sort out the return data. defer half_match.deinit(allocator); // Send both pairs off for separate processing. - const diffs_a = try dmp.diffInternal( + var diffs = try dmp.diffInternal( allocator, half_match.prefix_before, half_match.prefix_after, check_lines, deadline, ); + errdefer deinitDiffList(allocator, &diffs); var diffs_b = try dmp.diffInternal( allocator, half_match.suffix_before, @@ -314,7 +320,6 @@ fn diffCompute( } // Merge the results. - diffs = diffs_a; try diffs.ensureUnusedCapacity(allocator, 1); diffs.appendAssumeCapacity( Diff.init(.equal, try allocator.dupe( @@ -477,7 +482,6 @@ fn diffHalfMatchInternal( errdefer allocator.free(prefix_after); const suffix_after = try allocator.dupe(u8, best_short_text_b); const best_common_text = try best_common.toOwnedSlice(allocator); - errdefer allocator.free(best_common_text); return .{ .prefix_before = prefix_before, .suffix_before = suffix_before, @@ -913,10 +917,10 @@ fn diffCleanupMerge(allocator: std.mem.Allocator, diffs: *DiffList) DiffError!vo const ii = pointer - count_delete - count_insert - 1; var nt = try allocator.alloc(u8, diffs.items[ii].text.len + common_length); const ot = diffs.items[ii].text; - defer allocator.free(ot); @memcpy(nt[0..ot.len], ot); @memcpy(nt[ot.len..], text_insert.items[0..common_length]); diffs.items[ii].text = nt; + allocator.free(ot); } else { try diffs.ensureUnusedCapacity(allocator, 1); const text = try allocator.dupe(u8, text_insert.items[0..common_length]); @@ -935,7 +939,7 @@ fn diffCleanupMerge(allocator: std.mem.Allocator, diffs: *DiffList) DiffError!vo text_insert.items[text_insert.items.len - common_length ..], old_text, }); - defer allocator.free(old_text); + allocator.free(old_text); text_insert.items.len -= common_length; text_delete.items.len -= common_length; } @@ -2274,6 +2278,18 @@ test diffBisect { }}); } +fn diffHalfMatchLeak(allocator: Allocator) !void { + const dmp = DiffMatchPatch{}; + const text1 = "The quick brown fox jumps over the lazy dog."; + const text2 = "That quick brown fox jumped over a lazy dog."; + var diffs = try dmp.diff(allocator, text2, text1, true); + deinitDiffList(allocator, &diffs); +} + +test "diffHalfMatch leak regression test" { + try testing.checkAllAllocationFailures(testing.allocator, diffHalfMatchLeak, .{}); +} + fn testDiff( allocator: std.mem.Allocator, params: struct { From 353474a29ae45c9acb39b97688b7f0c370902f81 Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Tue, 9 Jul 2024 09:11:31 -1000 Subject: [PATCH 46/60] Update DiffMatchPatch.zig Co-authored-by: Techatrix --- DiffMatchPatch.zig | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/DiffMatchPatch.zig b/DiffMatchPatch.zig index dfec56c..7293f11 100644 --- a/DiffMatchPatch.zig +++ b/DiffMatchPatch.zig @@ -21,8 +21,7 @@ fn freeRangeDiffList( start: usize, len: usize, ) void { - const after_range = start + len; - const range = diffs.items[start..after_range]; + const range = diffs.items[start..][0..len]; for (range) |d| { allocator.free(d.text); } From 69796636b4fbc7e130d04ef0ca6b4a58fef64c32 Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Tue, 9 Jul 2024 15:24:13 -0400 Subject: [PATCH 47/60] Put line var inside while loop --- DiffMatchPatch.zig | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/DiffMatchPatch.zig b/DiffMatchPatch.zig index dfec56c..0fba9ba 100644 --- a/DiffMatchPatch.zig +++ b/DiffMatchPatch.zig @@ -824,7 +824,6 @@ fn diffLinesToCharsMunge( ) DiffError![]const u8 { var line_start: isize = 0; var line_end: isize = -1; - var line: []const u8 = undefined; var chars = ArrayListUnmanaged(u8){}; defer chars.deinit(allocator); // Walk the text, pulling out a Substring for each line. @@ -834,7 +833,7 @@ fn diffLinesToCharsMunge( break :b @as(isize, @intCast(std.mem.indexOf(u8, text[@intCast(line_start)..], "\n") orelse break :b @intCast(text.len - 1))) + line_start; }; - line = text[@intCast(line_start) .. @as(usize, @intCast(line_start)) + @as(usize, @intCast(line_end + 1 - line_start))]; + var line = text[@intCast(line_start) .. @as(usize, @intCast(line_start)) + @as(usize, @intCast(line_end + 1 - line_start))]; if (line_hash.get(line)) |value| { try chars.append(allocator, @intCast(value)); From c662afb0309d189ed21bceeafd58a771b3a5eaa6 Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Tue, 9 Jul 2024 15:30:19 -0400 Subject: [PATCH 48/60] Moves DiffList and functions --- DiffMatchPatch.zig | 48 ++++++++++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/DiffMatchPatch.zig b/DiffMatchPatch.zig index 72bda84..9aa4e2c 100644 --- a/DiffMatchPatch.zig +++ b/DiffMatchPatch.zig @@ -4,28 +4,6 @@ const std = @import("std"); const testing = std.testing; const Allocator = std.mem.Allocator; const ArrayListUnmanaged = std.ArrayListUnmanaged; -const DiffList = ArrayListUnmanaged(Diff); - -/// Deinit an `ArrayListUnmanaged(Diff)` and the allocated slices of -/// text in each `Diff`. -pub fn deinitDiffList(allocator: Allocator, diffs: *DiffList) void { - defer diffs.deinit(allocator); - for (diffs.items) |d| { - allocator.free(d.text); - } -} - -fn freeRangeDiffList( - allocator: Allocator, - diffs: *DiffList, - start: usize, - len: usize, -) void { - const range = diffs.items[start..][0..len]; - for (range) |d| { - allocator.free(d.text); - } -} /// DMP with default configuration options pub const default = DiffMatchPatch{}; @@ -95,7 +73,8 @@ patch_margin: u16 = 4, pub const DiffError = error{OutOfMemory}; -/// Find the differences between two texts. +/// Find the differences between two texts. The return value +/// must be freed with `deinitDiffList(allocator, &diffs)`. /// @param before Old string to be diffed. /// @param after New string to be diffed. /// @param checklines Speedup flag. If false, then don't run a @@ -119,6 +98,29 @@ pub fn diff( return dmp.diffInternal(allocator, before, after, check_lines, deadline); } +const DiffList = ArrayListUnmanaged(Diff); + +/// Deinit an `ArrayListUnmanaged(Diff)` and the allocated slices of +/// text in each `Diff`. +pub fn deinitDiffList(allocator: Allocator, diffs: *DiffList) void { + defer diffs.deinit(allocator); + for (diffs.items) |d| { + allocator.free(d.text); + } +} + +fn freeRangeDiffList( + allocator: Allocator, + diffs: *DiffList, + start: usize, + len: usize, +) void { + const range = diffs.items[start..][0..len]; + for (range) |d| { + allocator.free(d.text); + } +} + fn diffInternal( dmp: DiffMatchPatch, allocator: std.mem.Allocator, From 4eea3ae6ea9ce5c41b705ac0b538479263753e30 Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Tue, 9 Jul 2024 09:35:33 -1000 Subject: [PATCH 49/60] Replace errdefer on best_common_text Co-authored-by: Techatrix --- DiffMatchPatch.zig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/DiffMatchPatch.zig b/DiffMatchPatch.zig index 9aa4e2c..0f591d4 100644 --- a/DiffMatchPatch.zig +++ b/DiffMatchPatch.zig @@ -482,7 +482,9 @@ fn diffHalfMatchInternal( const prefix_after = try allocator.dupe(u8, best_short_text_a); errdefer allocator.free(prefix_after); const suffix_after = try allocator.dupe(u8, best_short_text_b); + errdefer allocator.free(suffix_after); const best_common_text = try best_common.toOwnedSlice(allocator); + errdefer allocator.free(best_common_text ); return .{ .prefix_before = prefix_before, .suffix_before = suffix_before, From 71b80af0bbfeaac2c43e6fc548353aaaad2034e9 Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Tue, 9 Jul 2024 15:45:15 -0400 Subject: [PATCH 50/60] Use assumeCapacity variant for empty ranges --- DiffMatchPatch.zig | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/DiffMatchPatch.zig b/DiffMatchPatch.zig index 0f591d4..4c970cc 100644 --- a/DiffMatchPatch.zig +++ b/DiffMatchPatch.zig @@ -484,7 +484,7 @@ fn diffHalfMatchInternal( const suffix_after = try allocator.dupe(u8, best_short_text_b); errdefer allocator.free(suffix_after); const best_common_text = try best_common.toOwnedSlice(allocator); - errdefer allocator.free(best_common_text ); + errdefer allocator.free(best_common_text); return .{ .prefix_before = prefix_before, .suffix_before = suffix_before, @@ -745,8 +745,7 @@ fn diffLineMode( pointer - count_delete - count_insert, count_delete + count_insert, ); - try diffs.replaceRange( - allocator, + diffs.replaceRangeAssumeCapacity( pointer - count_delete - count_insert, count_delete + count_insert, &.{}, @@ -929,8 +928,8 @@ fn diffCleanupMerge(allocator: std.mem.Allocator, diffs: *DiffList) DiffError!vo diffs.insertAssumeCapacity(0, Diff.init(.equal, text)); pointer += 1; } - try text_insert.replaceRange(allocator, 0, common_length, &.{}); - try text_delete.replaceRange(allocator, 0, common_length, &.{}); + text_insert.replaceRangeAssumeCapacity(0, common_length, &.{}); + text_delete.replaceRangeAssumeCapacity(0, common_length, &.{}); } // Factor out any common suffixies. // @ZigPort this seems very wrong @@ -950,7 +949,7 @@ fn diffCleanupMerge(allocator: std.mem.Allocator, diffs: *DiffList) DiffError!vo pointer -= count_delete + count_insert; if (count_delete + count_insert > 0) { freeRangeDiffList(allocator, diffs, pointer, count_delete + count_insert); - try diffs.replaceRange(allocator, pointer, count_delete + count_insert, &.{}); + diffs.replaceRangeAssumeCapacity(pointer, count_delete + count_insert, &.{}); } if (text_delete.items.len != 0) { @@ -1024,7 +1023,7 @@ fn diffCleanupMerge(allocator: std.mem.Allocator, diffs: *DiffList) DiffError!vo allocator.free(old_pt1t); diffs.items[pointer + 1].text = p1t; freeRangeDiffList(allocator, diffs, pointer - 1, 1); - try diffs.replaceRange(allocator, pointer - 1, 1, &.{}); + diffs.replaceRangeAssumeCapacity(pointer - 1, 1, &.{}); changes = true; } else if (std.mem.startsWith(u8, diffs.items[pointer].text, diffs.items[pointer + 1].text)) { const old_ptm1 = diffs.items[pointer - 1].text; @@ -1042,7 +1041,7 @@ fn diffCleanupMerge(allocator: std.mem.Allocator, diffs: *DiffList) DiffError!vo allocator.free(old_pt); diffs.items[pointer].text = pt; freeRangeDiffList(allocator, diffs, pointer + 1, 1); - try diffs.replaceRange(allocator, pointer + 1, 1, &.{}); + diffs.replaceRangeAssumeCapacity(pointer + 1, 1, &.{}); changes = true; } } From be49143fc4b63f7ce1b4aa970b0ca4f7ebdbb51b Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Tue, 9 Jul 2024 19:50:45 -0400 Subject: [PATCH 51/60] Change boolInt --- DiffMatchPatch.zig | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/DiffMatchPatch.zig b/DiffMatchPatch.zig index 4c970cc..85c1010 100644 --- a/DiffMatchPatch.zig +++ b/DiffMatchPatch.zig @@ -1365,11 +1365,8 @@ fn diffCleanupSemanticScore(one: []const u8, two: []const u8) usize { return 0; } -inline fn boolInt(b: bool) usize { - if (b) - return 1 - else - return 0; +inline fn boolInt(b: bool) u8 { + return @intFromBool(b); } /// Reduce the number of edits by eliminating operationally trivial From a86b90c42124511e69bc45112681378aa46ec108 Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Thu, 11 Jul 2024 08:27:14 -1000 Subject: [PATCH 52/60] Move coverage directory to zig-out Co-authored-by: Techatrix --- build.zig | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/build.zig b/build.zig index c9d8047..39db8fb 100644 --- a/build.zig +++ b/build.zig @@ -21,18 +21,27 @@ pub fn build(b: *std.Build) void { b.step("test", "Run diffz tests").dependOn(&step_tests.step); - // Adds a step to generate code coverage - const cov_step = b.step("cov", "Generate coverage (kcov must be installed)"); + const addOutputDirectoryArg = comptime if (@import("builtin").zig_version.order(.{ .major = 0, .minor = 13, .patch = 0 }) == .lt) + std.Build.Step.Run.addOutputFileArg + else + std.Build.Step.Run.addOutputDirectoryArg; - const cov_run = b.addSystemCommand(&.{ + const run_kcov = b.addSystemCommand(&.{ "kcov", "--clean", - "--include-pattern=DiffMatchPatch.zig", "--exclude-line=unreachable,expect(false)", - "kcov-output", }); - cov_run.addArtifactArg(tests); - cov_step.dependOn(&cov_run.step); - _ = cov_run.captureStdOut(); - _ = cov_run.captureStdErr(); + run_kcov.addPrefixedDirectoryArg("--include-pattern=", b.path(".")); + const coverage_output = addOutputDirectoryArg(run_kcov, "."); + run_kcov.addArtifactArg(tests); + run_kcov.enableTestRunnerMode(); + + const install_coverage = b.addInstallDirectory(.{ + .source_dir = coverage_output, + .install_dir = .{ .custom = "coverage" }, + .install_subdir = "", + }); + + const coverage_step = b.step("coverage", "Generate coverage (kcov must be installed)"); + coverage_step.dependOn(&install_coverage.step); } From 77d3eae45ea8852843b00933675942d92024705d Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Thu, 11 Jul 2024 14:32:01 -0400 Subject: [PATCH 53/60] Remove kcov directory from .gitignore --- .gitignore | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 8571fdf..5e48e54 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ .zig-cache zig-cache -zig-out -kcov-output \ No newline at end of file +zig-out \ No newline at end of file From aba5ffae01baf91866977e55bb8a9e65113c2c2f Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Sat, 13 Jul 2024 20:06:24 -0400 Subject: [PATCH 54/60] Fix for diffLineMode memory leaks --- DiffMatchPatch.zig | 64 +++++++++++++++++++++++++++++++++++++--------- out.txt | 43 +++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 12 deletions(-) create mode 100644 out.txt diff --git a/DiffMatchPatch.zig b/DiffMatchPatch.zig index 85c1010..62c1c94 100644 --- a/DiffMatchPatch.zig +++ b/DiffMatchPatch.zig @@ -703,12 +703,15 @@ fn diffLineMode( const text1 = a.chars_1; const text2 = a.chars_2; const line_array = a.line_array; - - var diffs: DiffList = try dmp.diffInternal(allocator, text1, text2, false, deadline); - errdefer diffs.deinit(allocator); - // Convert the diff back to original text. - try diffCharsToLines(allocator, diffs.items, line_array.items); - // Eliminate freak matches (e.g. blank lines) + var diffs: DiffList = undefined; + { + var char_diffs: DiffList = try dmp.diffInternal(allocator, text1, text2, false, deadline); + defer deinitDiffList(allocator, &char_diffs); + // Convert the diff back to original text. + diffs = try diffCharsToLines(allocator, &char_diffs, line_array.items); + // Eliminate freak matches (e.g. blank lines) + } + errdefer deinitDiffList(allocator, &diffs); try diffCleanupSemantic(allocator, &diffs); // Rediff any replacement blocks, this time character-by-character. @@ -752,8 +755,13 @@ fn diffLineMode( ); pointer = pointer - count_delete - count_insert; var sub_diff = try dmp.diffInternal(allocator, text_delete.items, text_insert.items, false, deadline); + { + errdefer deinitDiffList(allocator, &sub_diff); + try diffs.ensureUnusedCapacity(allocator, sub_diff.items.len); + } defer sub_diff.deinit(allocator); - try diffs.insertSlice(allocator, pointer, sub_diff.items); + const new_diff = diffs.addManyAtAssumeCapacity(pointer, sub_diff.items.len); + @memcpy(new_diff, sub_diff.items); pointer = pointer + sub_diff.items.len; } count_insert = 0; @@ -806,6 +814,7 @@ fn diffLinesToChars( // Allocate 2/3rds of the space for text1, the rest for text2. const chars1 = try diffLinesToCharsMunge(allocator, text1, &line_array, &line_hash, 170); + errdefer allocator.free(chars1); const chars2 = try diffLinesToCharsMunge(allocator, text2, &line_array, &line_hash, 255); return .{ .chars_1 = chars1, .chars_2 = chars2, .line_array = line_array }; } @@ -860,20 +869,23 @@ fn diffLinesToCharsMunge( /// @param lineArray List of unique strings. fn diffCharsToLines( allocator: std.mem.Allocator, - diffs: []Diff, + char_diffs: *DiffList, line_array: []const []const u8, -) DiffError!void { +) DiffError!DiffList { + var diffs = DiffList{}; + errdefer deinitDiffList(allocator, &diffs); + try diffs.ensureTotalCapacity(allocator, char_diffs.items.len); var text = ArrayListUnmanaged(u8){}; defer text.deinit(allocator); - for (diffs) |*d| { + for (char_diffs.items) |*d| { var j: usize = 0; while (j < d.text.len) : (j += 1) { try text.appendSlice(allocator, line_array[d.text[j]]); } - allocator.free(d.text); - d.text = try text.toOwnedSlice(allocator); + diffs.appendAssumeCapacity(Diff.init(d.operation, try text.toOwnedSlice(allocator))); } + return diffs; } /// Reorder and merge like edit sections. Merge equalities. @@ -2570,6 +2582,34 @@ test diff { } } +fn testDiffLineMode( + allocator: Allocator, + dmp: DiffMatchPatch, + before: []const u8, + after: []const u8, +) !void { + var diff_checked = try dmp.diff(allocator, before, after, true); + defer deinitDiffList(allocator, &diff_checked); + + var diff_unchecked = try dmp.diff(allocator, before, after, false); + defer deinitDiffList(allocator, &diff_unchecked); + + try testing.expectEqualDeep(diff_checked.items, diff_unchecked.items); // diff: Simple line-mode. +} + +test "diffLineMode" { + const dmp: DiffMatchPatch = .{ .diff_timeout = 0 }; + try testing.checkAllAllocationFailures( + testing.allocator, + testDiffLineMode, + + .{ + dmp, "1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n", + "abcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\n", + }, + ); +} + fn testDiffCleanupSemantic( allocator: std.mem.Allocator, params: struct { diff --git a/out.txt b/out.txt new file mode 100644 index 0000000..b267770 --- /dev/null +++ b/out.txt @@ -0,0 +1,43 @@ +1/1 DiffMatchPatch.test.diffLineMode...FAIL (OutOfMemory) +/opt/homebrew/Cellar/zig/0.13.0/lib/zig/std/math.zig:557:21: 0x10053dd17 in mul__anon_4753 (test) + if (ov[1] != 0) return error.Overflow; + ^ +/opt/homebrew/Cellar/zig/0.13.0/lib/zig/std/mem/Allocator.zig:210:55: 0x100581e4f in allocWithSizeAndAlignment__anon_8842 (test) + const byte_count = math.mul(usize, size, n) catch return Error.OutOfMemory; + ^ +/opt/homebrew/Cellar/zig/0.13.0/lib/zig/std/mem/Allocator.zig:193:5: 0x1005c9d9f in alignedAlloc__anon_9677 (test) + return self.allocAdvancedWithRetAddr(T, alignment, n, @returnAddress()); + ^ +/opt/homebrew/Cellar/zig/0.13.0/lib/zig/std/array_list.zig:1081:36: 0x1005aa103 in ensureTotalCapacityPrecise (test) + const new_memory = try allocator.alignedAlloc(T, alignment, new_capacity); + ^ +/opt/homebrew/Cellar/zig/0.13.0/lib/zig/std/array_list.zig:1058:13: 0x100575cbb in ensureTotalCapacity (test) + return self.ensureTotalCapacityPrecise(allocator, better_capacity); + ^ +/Users/atman/code/opp/ziglibs/diffz/DiffMatchPatch.zig:877:5: 0x1005ac737 in diffCharsToLines (test) + try diffs.ensureTotalCapacity(allocator, char_diffs.items.len); + ^ +/Users/atman/code/opp/ziglibs/diffz/DiffMatchPatch.zig:711:17: 0x100576c07 in diffLineMode (test) + diffs = try diffCharsToLines(allocator, &diffs, line_array.items); + ^ +/Users/atman/code/opp/ziglibs/diffz/DiffMatchPatch.zig:336:9: 0x100547497 in diffCompute (test) + return dmp.diffLineMode(allocator, before, after, deadline); + ^ +/Users/atman/code/opp/ziglibs/diffz/DiffMatchPatch.zig:159:17: 0x10053b7a7 in diffInternal (test) + var diffs = try dmp.diffCompute(allocator, trimmed_before, trimmed_after, check_lines, deadline); + ^ +/Users/atman/code/opp/ziglibs/diffz/DiffMatchPatch.zig:98:5: 0x100534b67 in diff (test) + return dmp.diffInternal(allocator, before, after, check_lines, deadline); + ^ +/Users/atman/code/opp/ziglibs/diffz/DiffMatchPatch.zig:2591:24: 0x100534803 in testDiffLineMode (test) + var diff_checked = try dmp.diff(allocator, before, after, true); + ^ +/opt/homebrew/Cellar/zig/0.13.0/lib/zig/std/testing.zig:1073:9: 0x10053522b in checkAllAllocationFailures__anon_2717 (test) + try @call(.auto, test_fn, args); + ^ +/Users/atman/code/opp/ziglibs/diffz/DiffMatchPatch.zig:2602:5: 0x100535863 in test.diffLineMode (test) + try testing.checkAllAllocationFailures( + ^ +0 passed; 0 skipped; 1 failed. +error: the following test command failed with exit code 1: +/Users/atman/code/opp/ziglibs/diffz/.zig-cache/o/580927a6ec9942a55d66505d04a4eca8/test From 07df57b7fe930e330452bd70447695e6a2626a40 Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Sat, 13 Jul 2024 20:14:49 -0400 Subject: [PATCH 55/60] remove spurious file --- out.txt | 43 ------------------------------------------- 1 file changed, 43 deletions(-) delete mode 100644 out.txt diff --git a/out.txt b/out.txt deleted file mode 100644 index b267770..0000000 --- a/out.txt +++ /dev/null @@ -1,43 +0,0 @@ -1/1 DiffMatchPatch.test.diffLineMode...FAIL (OutOfMemory) -/opt/homebrew/Cellar/zig/0.13.0/lib/zig/std/math.zig:557:21: 0x10053dd17 in mul__anon_4753 (test) - if (ov[1] != 0) return error.Overflow; - ^ -/opt/homebrew/Cellar/zig/0.13.0/lib/zig/std/mem/Allocator.zig:210:55: 0x100581e4f in allocWithSizeAndAlignment__anon_8842 (test) - const byte_count = math.mul(usize, size, n) catch return Error.OutOfMemory; - ^ -/opt/homebrew/Cellar/zig/0.13.0/lib/zig/std/mem/Allocator.zig:193:5: 0x1005c9d9f in alignedAlloc__anon_9677 (test) - return self.allocAdvancedWithRetAddr(T, alignment, n, @returnAddress()); - ^ -/opt/homebrew/Cellar/zig/0.13.0/lib/zig/std/array_list.zig:1081:36: 0x1005aa103 in ensureTotalCapacityPrecise (test) - const new_memory = try allocator.alignedAlloc(T, alignment, new_capacity); - ^ -/opt/homebrew/Cellar/zig/0.13.0/lib/zig/std/array_list.zig:1058:13: 0x100575cbb in ensureTotalCapacity (test) - return self.ensureTotalCapacityPrecise(allocator, better_capacity); - ^ -/Users/atman/code/opp/ziglibs/diffz/DiffMatchPatch.zig:877:5: 0x1005ac737 in diffCharsToLines (test) - try diffs.ensureTotalCapacity(allocator, char_diffs.items.len); - ^ -/Users/atman/code/opp/ziglibs/diffz/DiffMatchPatch.zig:711:17: 0x100576c07 in diffLineMode (test) - diffs = try diffCharsToLines(allocator, &diffs, line_array.items); - ^ -/Users/atman/code/opp/ziglibs/diffz/DiffMatchPatch.zig:336:9: 0x100547497 in diffCompute (test) - return dmp.diffLineMode(allocator, before, after, deadline); - ^ -/Users/atman/code/opp/ziglibs/diffz/DiffMatchPatch.zig:159:17: 0x10053b7a7 in diffInternal (test) - var diffs = try dmp.diffCompute(allocator, trimmed_before, trimmed_after, check_lines, deadline); - ^ -/Users/atman/code/opp/ziglibs/diffz/DiffMatchPatch.zig:98:5: 0x100534b67 in diff (test) - return dmp.diffInternal(allocator, before, after, check_lines, deadline); - ^ -/Users/atman/code/opp/ziglibs/diffz/DiffMatchPatch.zig:2591:24: 0x100534803 in testDiffLineMode (test) - var diff_checked = try dmp.diff(allocator, before, after, true); - ^ -/opt/homebrew/Cellar/zig/0.13.0/lib/zig/std/testing.zig:1073:9: 0x10053522b in checkAllAllocationFailures__anon_2717 (test) - try @call(.auto, test_fn, args); - ^ -/Users/atman/code/opp/ziglibs/diffz/DiffMatchPatch.zig:2602:5: 0x100535863 in test.diffLineMode (test) - try testing.checkAllAllocationFailures( - ^ -0 passed; 0 skipped; 1 failed. -error: the following test command failed with exit code 1: -/Users/atman/code/opp/ziglibs/diffz/.zig-cache/o/580927a6ec9942a55d66505d04a4eca8/test From 01fe8cbef8562af1b128e7034da2c13b9836f427 Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Sat, 13 Jul 2024 20:38:37 -0400 Subject: [PATCH 56/60] Fix type errors in line mode tests --- DiffMatchPatch.zig | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/DiffMatchPatch.zig b/DiffMatchPatch.zig index 62c1c94..d9927ff 100644 --- a/DiffMatchPatch.zig +++ b/DiffMatchPatch.zig @@ -1788,14 +1788,15 @@ fn testDiffCharsToLines( expected: []const Diff, }, ) !void { - var diffs = try DiffList.initCapacity(allocator, params.diffs.len); - defer deinitDiffList(allocator, &diffs); + var char_diffs = try DiffList.initCapacity(allocator, params.diffs.len); + defer deinitDiffList(allocator, &char_diffs); for (params.diffs) |item| { - diffs.appendAssumeCapacity(.{ .operation = item.operation, .text = try allocator.dupe(u8, item.text) }); + char_diffs.appendAssumeCapacity(.{ .operation = item.operation, .text = try allocator.dupe(u8, item.text) }); } - try diffCharsToLines(allocator, diffs.items, params.line_array); + var diffs = try diffCharsToLines(allocator, &char_diffs, params.line_array); + defer deinitDiffList(allocator, &diffs); try testing.expectEqualDeep(params.expected, diffs.items); } From 1483c3ef36d9f5c30ec5148981d505108e6deed4 Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Sun, 14 Jul 2024 14:17:09 -0400 Subject: [PATCH 57/60] Make check_lines threshold configurable This lets the test set it to a low value. That in turn allows the full allocation failure check to complete in a reasonable amount of time. --- DiffMatchPatch.zig | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/DiffMatchPatch.zig b/DiffMatchPatch.zig index d9927ff..c271580 100644 --- a/DiffMatchPatch.zig +++ b/DiffMatchPatch.zig @@ -53,6 +53,8 @@ pub const Diff = struct { diff_timeout: u64 = 1000, /// Cost of an empty edit operation in terms of edit characters. diff_edit_cost: u16 = 4, +/// Number of bytes in each string needed to trigger a line-based diff +diff_check_lines_over: u64 = 100, /// At what point is no match declared (0.0 = perfection, 1.0 = very loose). match_threshold: f32 = 0.5, @@ -331,8 +333,7 @@ fn diffCompute( try diffs.appendSlice(allocator, diffs_b.items); return diffs; } - - if (check_lines and before.len > 100 and after.len > 100) { + if (check_lines and before.len > dmp.diff_check_lines_over and after.len > dmp.diff_check_lines_over) { return dmp.diffLineMode(allocator, before, after, deadline); } @@ -2585,10 +2586,11 @@ test diff { fn testDiffLineMode( allocator: Allocator, - dmp: DiffMatchPatch, + dmp: *DiffMatchPatch, before: []const u8, after: []const u8, ) !void { + dmp.diff_check_lines_over = 20; var diff_checked = try dmp.diff(allocator, before, after, true); defer deinitDiffList(allocator, &diff_checked); @@ -2596,17 +2598,19 @@ fn testDiffLineMode( defer deinitDiffList(allocator, &diff_unchecked); try testing.expectEqualDeep(diff_checked.items, diff_unchecked.items); // diff: Simple line-mode. + dmp.diff_check_lines_over = 100; } test "diffLineMode" { - const dmp: DiffMatchPatch = .{ .diff_timeout = 0 }; + var dmp: DiffMatchPatch = .{ .diff_timeout = 0 }; try testing.checkAllAllocationFailures( testing.allocator, testDiffLineMode, .{ - dmp, "1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n", - "abcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\n", + &dmp, + "1234567890\n1234567890\n1234567890\n", + "abcdefghij\nabcdefghij\nabcdefghij\n", }, ); } From bff1f1de16c1df2cc5fe3d40c0a4d6e5966205f0 Mon Sep 17 00:00:00 2001 From: Sam Atman Date: Sun, 14 Jul 2024 16:12:21 -0400 Subject: [PATCH 58/60] Make diffCleanupSemantic public It is in the source text, and should be in diffz. --- DiffMatchPatch.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DiffMatchPatch.zig b/DiffMatchPatch.zig index c271580..b35235f 100644 --- a/DiffMatchPatch.zig +++ b/DiffMatchPatch.zig @@ -1069,7 +1069,7 @@ fn diffCleanupMerge(allocator: std.mem.Allocator, diffs: *DiffList) DiffError!vo /// Reduce the number of edits by eliminating semantically trivial /// equalities. /// @param diffs List of Diff objects. -fn diffCleanupSemantic(allocator: std.mem.Allocator, diffs: *DiffList) DiffError!void { +pub fn diffCleanupSemantic(allocator: std.mem.Allocator, diffs: *DiffList) DiffError!void { var changes = false; // Stack of indices where equalities are found. var equalities = ArrayListUnmanaged(isize){}; From 15c66430bc7c1642f044df7ad1b6af2448dc88f0 Mon Sep 17 00:00:00 2001 From: Techatrix Date: Sat, 31 Aug 2024 18:26:09 +0200 Subject: [PATCH 59/60] resolve review comments --- .gitignore | 2 +- DiffMatchPatch.zig | 8 +------- build.zig | 6 +++--- build.zig.zon | 3 +-- 4 files changed, 6 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index 5e48e54..8c9d17e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ .zig-cache zig-cache -zig-out \ No newline at end of file +zig-out diff --git a/DiffMatchPatch.zig b/DiffMatchPatch.zig index b35235f..fcea578 100644 --- a/DiffMatchPatch.zig +++ b/DiffMatchPatch.zig @@ -347,8 +347,6 @@ const HalfMatchResult = struct { suffix_after: []const u8, common_middle: []const u8, - // TODO maybe check for empty slice here for fewer copies, - // as in, maybe we can transfer ownership and replace with "". pub fn deinit(hmr: HalfMatchResult, alloc: Allocator) void { alloc.free(hmr.prefix_before); alloc.free(hmr.suffix_before); @@ -1378,10 +1376,6 @@ fn diffCleanupSemanticScore(one: []const u8, two: []const u8) usize { return 0; } -inline fn boolInt(b: bool) u8 { - return @intFromBool(b); -} - /// Reduce the number of edits by eliminating operationally trivial /// equalities. pub fn diffCleanupEfficiency( @@ -1435,7 +1429,7 @@ pub fn diffCleanupEfficiency( if ((last_equality.len != 0) and ((pre_ins and pre_del and post_ins and post_del) or ((last_equality.len < dmp.diff_edit_cost / 2) and - (boolInt(pre_ins) + boolInt(pre_del) + boolInt(post_ins) + boolInt(post_del) == 3)))) + (@as(u8, @intFromBool(pre_ins)) + @as(u8, @intFromBool(pre_del)) + @as(u8, @intFromBool(post_ins)) + @as(u8, @intFromBool(post_del)) == 3)))) { // Duplicate record. try diffs.ensureUnusedCapacity(allocator, 1); diff --git a/build.zig b/build.zig index 39db8fb..26a659f 100644 --- a/build.zig +++ b/build.zig @@ -10,16 +10,16 @@ pub fn build(b: *std.Build) void { .optimize = optimize, }); - // Run tests const tests = b.addTest(.{ .name = "tests", .root_source_file = b.path("DiffMatchPatch.zig"), .target = target, .optimize = optimize, }); - const step_tests = b.addRunArtifact(tests); + const run_tests = b.addRunArtifact(tests); - b.step("test", "Run diffz tests").dependOn(&step_tests.step); + const test_step = b.step("test", "Run all the tests"); + test_step.dependOn(&run_tests.step); const addOutputDirectoryArg = comptime if (@import("builtin").zig_version.order(.{ .major = 0, .minor = 13, .patch = 0 }) == .lt) std.Build.Step.Run.addOutputFileArg diff --git a/build.zig.zon b/build.zig.zon index b4e0b4d..0fe996d 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -1,10 +1,9 @@ .{ .name = "diffz", .version = "0.0.1", + .minimum_zig_version = "0.12.0", .paths = .{ "DiffMatchPatch.zig", - ".gitattributes", - ".gitignore", "LICENSE", "README.md", "build.zig.zon", From 3fec21afc04892937bcf0a74f506bb739a9d6f09 Mon Sep 17 00:00:00 2001 From: Techatrix Date: Sat, 31 Aug 2024 18:30:40 +0200 Subject: [PATCH 60/60] add GitHub workflow --- .github/workflows/main.yml | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..a833621 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,38 @@ +name: CI + +on: + push: + branches: + - main + pull_request: + branches: + - main + workflow_dispatch: + +jobs: + build: + strategy: + fail-fast: false + matrix: + zig-version: [master] + os: [ubuntu-latest, macos-latest, windows-latest] + include: + - zig-version: "0.12.1" + os: ubuntu-latest + - zig-version: "0.13.0" + os: ubuntu-latest + runs-on: ${{ matrix.os }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Zig + uses: mlugg/setup-zig@v1 + with: + version: ${{ matrix.zig-version }} + + - name: Check Formatting + run: zig fmt --ast-check --check src + + - name: Run Tests + run: zig build test --summary all