From 698e038aebdaa87ba77375654604af33ddfee5fe Mon Sep 17 00:00:00 2001 From: "carpentry-heartbeat[bot]" Date: Sun, 7 Jun 2026 17:44:56 +0200 Subject: [PATCH 1/2] Add Diff.apply and Diff.revert; doc annotations for all public functions --- diff.carp | 31 +++++++++++++++++++++++++++++ tests/diff.carp | 52 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) diff --git a/diff.carp b/diff.carp index f0f8685..08f2f96 100644 --- a/diff.carp +++ b/diff.carp @@ -11,6 +11,8 @@ (Deletion x) (match @b (Deletion y) (= &x &y) _ false))) (implements = Diff.=) + (doc diff + "Computes the diff between two arrays, returning a sequence of Eq, Insertion, and Deletion hunks.") (defn diff [old new] (let-do [oidxm {} overlap {} @@ -48,6 +50,7 @@ [(Eq (Array.slice new ssn (+ ssn sl)))]]) (diff &(Array.suffix old (+ sso sl)) &(Array.suffix new (+ ssn sl)))])))) + (doc string-diff "Diffs two strings word by word, splitting on whitespace.") (defn string-diff [old new] (diff &(String.words old) &(String.words new))) (doc line-diff "Diffs two strings line by line, splitting on newlines.") @@ -57,16 +60,44 @@ (doc char-diff "Diffs two strings character by character.") (defn char-diff [old new] (diff &(String.chars old) &(String.chars new))) + (doc eq? "Returns true if the diff hunk is an Eq.") (defn eq? [d] (match @d (Eq _) true _ false)) + (doc inserted? "Returns true if the diff hunk is an Insertion.") (defn inserted? [d] (match @d (Insertion _) true _ false)) + (doc deleted? "Returns true if the diff hunk is a Deletion.") (defn deleted? [d] (match @d (Deletion _) true _ false)) + (doc eq "Filters a diff to only the Eq hunks.") (defn eq [d] (Array.copy-filter &eq? d)) + (doc insertions "Filters a diff to only the Insertion hunks.") (defn insertions [d] (Array.copy-filter &inserted? d)) + (doc deletions "Filters a diff to only the Deletion hunks.") (defn deletions [d] (Array.copy-filter &deleted? d)) + (doc apply + "Reconstructs the 'new' array from a diff by collecting Eq and Insertion elements.") + (defn apply [d] + (let-do [res []] + (for [i 0 (Array.length d)] + (match-ref (Array.unsafe-nth d i) + (Eq xs) (set! res (Array.concat &[res (Array.copy xs)])) + (Insertion xs) (set! res (Array.concat &[res (Array.copy xs)])) + (Deletion _) ())) + res)) + + (doc revert + "Reconstructs the 'old' array from a diff by collecting Eq and Deletion elements.") + (defn revert [d] + (let-do [res []] + (for [i 0 (Array.length d)] + (match-ref (Array.unsafe-nth d i) + (Eq xs) (set! res (Array.concat &[res (Array.copy xs)])) + (Insertion _) () + (Deletion xs) (set! res (Array.concat &[res (Array.copy xs)])))) + res)) + (defn- emit-hunk [output old-start old-count new-start new-count hunk-lines] (let [header (String.concat &[@"@@ -" diff --git a/tests/diff.carp b/tests/diff.carp index 872137e..e89dc34 100644 --- a/tests/diff.carp +++ b/tests/diff.carp @@ -59,6 +59,58 @@ &(diff &[1 2 3 4] &[3 4]) "new is a suffix of old") + ; apply tests + (assert-equal test + &[1 2 3] + &(Diff.apply &(diff &[1 2 3] &[1 2 3])) + "apply on equal arrays returns the same array") + (assert-equal test + &[1 2 3] + &(Diff.apply &(diff &[1 4 3] &[1 2 3])) + "apply reconstructs the new array") + (assert-equal test + &(the (Array Int) []) + &(Diff.apply &(diff &[1 2 3] &[])) + "apply on all-deletion diff returns empty") + (assert-equal test + &[1 2 3] + &(Diff.apply &(diff &[] &[1 2 3])) + "apply on all-insertion diff returns the inserted elements") + (assert-equal test + &(the (Array Int) []) + &(Diff.apply &(diff &[] &(the (Array Int) []))) + "apply on empty diff returns empty") + (assert-equal test + &[1 2 3 4] + &(Diff.apply &(diff &[1 2] &[1 2 3 4])) + "apply with appended elements") + + ; revert tests + (assert-equal test + &[1 2 3] + &(Diff.revert &(diff &[1 2 3] &[1 2 3])) + "revert on equal arrays returns the same array") + (assert-equal test + &[1 4 3] + &(Diff.revert &(diff &[1 4 3] &[1 2 3])) + "revert reconstructs the old array") + (assert-equal test + &[1 2 3] + &(Diff.revert &(diff &[1 2 3] &[])) + "revert on all-deletion diff returns the deleted elements") + (assert-equal test + &(the (Array Int) []) + &(Diff.revert &(diff &[] &[1 2 3])) + "revert on all-insertion diff returns empty") + (assert-equal test + &(the (Array Int) []) + &(Diff.revert &(diff &[] &(the (Array Int) []))) + "revert on empty diff returns empty") + (assert-equal test + &[1 2 3 4] + &(Diff.revert &(diff &[1 2 3 4] &[1 2])) + "revert with removed suffix") + ; line-diff tests (assert-equal test &[(Eq [@"a"]) (Insertion [@"new"]) (Deletion [@"old"]) (Eq [@"b"])] From 2d9622cc193f6c39a8a80e402f2901a5bf4c833a Mon Sep 17 00:00:00 2001 From: "carpentry-heartbeat[bot]" Date: Mon, 8 Jun 2026 08:43:30 +0200 Subject: [PATCH 2/2] add hand-built mixed diff tests for apply/revert Construct diffs directly (not via diff) to show apply/revert work on the diff structure alone. Int and String variants with interleaved Eq/Insertion/Deletion hunks demonstrate that the two functions produce meaningfully different results. --- tests/diff.carp | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/diff.carp b/tests/diff.carp index e89dc34..187bdee 100644 --- a/tests/diff.carp +++ b/tests/diff.carp @@ -85,6 +85,45 @@ &(Diff.apply &(diff &[1 2] &[1 2 3 4])) "apply with appended elements") + ; apply/revert on hand-built mixed diffs — demonstrates + ; reconstructing either side from the diff structure alone + (assert-equal test + &[1 2 10 20 4 30] + &(Diff.apply + &[(Eq [1 2]) + (Insertion [10 20]) + (Deletion [3]) + (Eq [4]) + (Deletion [5 6]) + (Insertion [30])]) + "apply on mixed diff collects Eq+Insertion, skips Deletion") + (assert-equal test + &[1 2 3 4 5 6] + &(Diff.revert + &[(Eq [1 2]) + (Insertion [10 20]) + (Deletion [3]) + (Eq [4]) + (Deletion [5 6]) + (Insertion [30])]) + "revert on mixed diff collects Eq+Deletion, skips Insertion") + (assert-equal test + &[@"hello" @"brave" @"new" @"world"] + &(Diff.apply + &[(Eq [@"hello"]) + (Deletion [@"cruel" @"old"]) + (Insertion [@"brave" @"new"]) + (Eq [@"world"])]) + "apply on string mixed diff reconstructs the new side") + (assert-equal test + &[@"hello" @"cruel" @"old" @"world"] + &(Diff.revert + &[(Eq [@"hello"]) + (Deletion [@"cruel" @"old"]) + (Insertion [@"brave" @"new"]) + (Eq [@"world"])]) + "revert on string mixed diff reconstructs the old side") + ; revert tests (assert-equal test &[1 2 3]