Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 137 additions & 1 deletion diff.carp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@

(defn string-diff [old new] (diff &(String.words old) &(String.words new)))

(doc line-diff "Diffs two strings line by line, splitting on newlines.")
(defn line-diff [old new]
(diff &(String.split-by old &[\newline]) &(String.split-by new &[\newline])))

(doc char-diff "Diffs two strings character by character.")
(defn char-diff [old new] (diff &(String.chars old) &(String.chars new)))

(defn eq? [d] (match @d (Eq _) true _ false))

(defn inserted? [d] (match @d (Insertion _) true _ false))
Expand All @@ -58,4 +65,133 @@

(defn eq [d] (Array.copy-filter &eq? d))
(defn insertions [d] (Array.copy-filter &inserted? d))
(defn deletions [d] (Array.copy-filter &deleted? d)))
(defn deletions [d] (Array.copy-filter &deleted? d))

(defn- emit-hunk [output old-start old-count new-start new-count hunk-lines]
(let [header (String.concat
&[@"@@ -"
(Int.str old-start)
@","
(Int.str old-count)
@" +"
(Int.str new-start)
@","
(Int.str new-count)
@" @@"])
body (String.join "
" hunk-lines)]
(String.concat &[output header @"
" body @"
"])))

(doc unified "Renders a line diff in unified diff format with hunk headers.
Expects the output of `line-diff` or a diff of string arrays.
Uses 3 lines of context around changes.")
(defn unified [d]
(let-do [ctx 3
old-pos 1
new-pos 1
hunk-lines (the (Array String) [])
hunk-old-start 1
hunk-new-start 1
hunk-old-count 0
hunk-new-count 0
in-hunk false
lead-lines (the (Array String) [])
lead-old-start 1
lead-new-start 1
lead-count 0
result @""]
(for [i 0 (Array.length d)]
(match-ref (Array.unsafe-nth d i)
(Eq lines)
(let-do [n (Array.length lines)]
(if in-hunk
(if (<= n (* 2 ctx))
(for [j 0 n]
(let-do [line (Array.unsafe-nth lines j)]
(Array.push-back! &hunk-lines
(String.concat &[@" " @line]))
(set! hunk-old-count (+ hunk-old-count 1))
(set! hunk-new-count (+ hunk-new-count 1))))
(let-do [trailing (Int.min ctx n)]
(for [j 0 trailing]
(let-do [line (Array.unsafe-nth lines j)]
(Array.push-back! &hunk-lines
(String.concat &[@" " @line]))
(set! hunk-old-count (+ hunk-old-count 1))
(set! hunk-new-count (+ hunk-new-count 1))))
(set! result
(emit-hunk result
hunk-old-start
hunk-old-count
hunk-new-start
hunk-new-count
&hunk-lines))
(set! in-hunk false)
(set! hunk-lines [])
(let-do [leading (Int.min ctx (- n trailing))
lead-start (- n leading)]
(set! lead-lines [])
(set! lead-count 0)
(set! lead-old-start (+ old-pos lead-start))
(set! lead-new-start (+ new-pos lead-start))
(for [j lead-start n]
(let-do [line (Array.unsafe-nth lines j)]
(Array.push-back! &lead-lines
(String.concat &[@" " @line]))
(set! lead-count (+ lead-count 1)))))))
(let-do [leading (Int.min ctx n)
lead-start (- n leading)]
(set! lead-lines [])
(set! lead-count 0)
(set! lead-old-start (+ old-pos lead-start))
(set! lead-new-start (+ new-pos lead-start))
(for [j lead-start n]
(let-do [line (Array.unsafe-nth lines j)]
(Array.push-back! &lead-lines
(String.concat &[@" " @line]))
(set! lead-count (+ lead-count 1))))))
(set! old-pos (+ old-pos n))
(set! new-pos (+ new-pos n)))
(Deletion lines)
(let-do [n (Array.length lines)]
(when-do (not in-hunk)
(set! in-hunk true)
(set! hunk-lines (Array.copy &lead-lines))
(set! hunk-old-start lead-old-start)
(set! hunk-new-start lead-new-start)
(set! hunk-old-count lead-count)
(set! hunk-new-count lead-count)
(set! lead-lines [])
(set! lead-count 0))
(for [j 0 n]
(let-do [line (Array.unsafe-nth lines j)]
(Array.push-back! &hunk-lines (String.concat &[@"-" @line]))
(set! hunk-old-count (+ hunk-old-count 1))))
(set! old-pos (+ old-pos n)))
(Insertion lines)
(let-do [n (Array.length lines)]
(when-do (not in-hunk)
(set! in-hunk true)
(set! hunk-lines (Array.copy &lead-lines))
(set! hunk-old-start lead-old-start)
(set! hunk-new-start lead-new-start)
(set! hunk-old-count lead-count)
(set! hunk-new-count lead-count)
(set! lead-lines [])
(set! lead-count 0))
(for [j 0 n]
(let-do [line (Array.unsafe-nth lines j)]
(Array.push-back! &hunk-lines (String.concat &[@"+" @line]))
(set! hunk-new-count (+ hunk-new-count 1))))
(set! new-pos (+ new-pos n)))))
(when in-hunk
(set! result
(emit-hunk result
hunk-old-start
hunk-old-count
hunk-new-start
hunk-new-count
&hunk-lines)))
result)))
100 changes: 99 additions & 1 deletion tests/diff.carp
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,102 @@
(assert-equal test
&[(Deletion [1 2]) (Eq [3 4])]
&(diff &[1 2 3 4] &[3 4])
"new is a suffix of old")))
"new is a suffix of old")

; line-diff tests
(assert-equal test
&[(Eq [@"a"]) (Insertion [@"new"]) (Deletion [@"old"]) (Eq [@"b"])]
&(line-diff "a
old
b" "a
new
b")
"line-diff splits on newlines and diffs")
(assert-equal test &[(Eq [@"same" @"lines"])] &(Diff.eq &(line-diff "same
lines" "same
lines")) "line-diff of identical strings is all equal")

; char-diff tests
(assert-equal test
&[(Eq [\a]) (Insertion [\x]) (Deletion [\b]) (Eq [\c])]
&(char-diff "abc" "axc")
"char-diff diffs individual characters")
(assert-equal test
&[(Eq [\h \e \l \l \o])]
&(Diff.eq &(char-diff "hello" "hello"))
"char-diff of identical strings is all equal")

; unified tests
(assert-equal test &(String.concat &[@"@@ -1,3 +1,3 @@
" @" line1
" @"+changed
" @"-line2
" @" line3
"]) &(unified &(line-diff "line1
line2
line3" "line1
changed
line3")) "unified renders single-hunk diff with correct header")
(assert-equal test "" &(unified &(line-diff "same
text" "same
text")) "unified of identical strings is empty")
(assert-equal test &(String.concat &[@"@@ -1,4 +1,4 @@
" @"+new
" @"-old
" @" a
" @" b
" @" c
"]) &(unified &(line-diff "old
a
b
c" "new
a
b
c")) "unified handles change at start")
(assert-equal test &(String.concat &[@"@@ -1,4 +1,4 @@
" @" a
" @" b
" @" c
" @"+new
" @"-old
"]) &(unified &(line-diff "a
b
c
old" "a
b
c
new")) "unified handles change at end")
(assert-equal test &(String.concat &[@"@@ -1,5 +1,5 @@
" @" a
" @"+BB
" @"-b
" @" c
" @" d
" @" e
" @"@@ -8,4 +8,4 @@
" @" h
" @" i
" @" j
" @"+KK
" @"-k
"]) &(unified &(line-diff "a
b
c
d
e
f
g
h
i
j
k" "a
BB
c
d
e
f
g
h
i
j
KK")) "unified generates multiple hunks for distant changes")))
Loading