Skip to content

Commit 9c84bef

Browse files
committed
Merge pull request #5 from eschulte/master
diff operations directly over lists, and support for diff and patch application
2 parents c877b2f + 1eeae57 commit 9c84bef

File tree

3 files changed

+93
-1
lines changed

3 files changed

+93
-1
lines changed

diff.lisp

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,17 @@
8989
(let ((code (intern-string interner line)))
9090
(push code interned-file)))
9191
(push (coerce (nreverse interned-file) 'simple-vector) interned-files)))))
92+
93+
(defun intern-seqs (&rest seqs)
94+
(let ((interner (make-interner))
95+
(interned-seqs nil))
96+
(dolist (seq seqs (values interner (nreverse interned-seqs)))
97+
(let ((interned-seq nil))
98+
(loop :for line :in seq :do
99+
(let ((code (intern-string interner line)))
100+
(push code interned-seq)))
101+
(push (coerce (nreverse interned-seq) 'simple-vector) interned-seqs)))))
102+
92103

93104
;;; Computing longest common subsequences between two sequences whose
94105
;;; elements compare equal via EQL. The algorithm used here is based
@@ -244,6 +255,12 @@
244255
(defun compute-raw-diff (origin modified)
245256
(convert-lcs-to-diff (compute-lcs origin modified)))
246257

258+
(defun compute-raw-seq-diff (original-seq modified-seq)
259+
(multiple-value-bind (interner interned-seqs)
260+
(intern-seqs original-seq modified-seq)
261+
(declare (ignorable interner))
262+
(convert-lcs-to-diff (apply #'compute-lcs interned-seqs))))
263+
247264

248265
;;; producing diffs in "unified diff" format
249266

@@ -298,6 +315,50 @@ of context; the default of three should be good enough for most situations.")
298315
(window-chunks :initform nil
299316
:accessor window-chunks)))
300317

318+
(defun apply-seq-window (original-seq window &key (offset 0))
319+
"Apply the edits encoded in WINDOW to the ORIGINAL-SEQ."
320+
(multiple-value-bind (interner interned-seqs)
321+
(apply #'intern-seqs original-seq
322+
(mapcar #'chunk-lines (window-chunks window)))
323+
(let ((index (original-start-line window))
324+
(result (coerce (first interned-seqs) 'list)))
325+
(flet ((ind () (+ index offset))
326+
(back (line) (interned-object interner line)))
327+
(loop
328+
for chunk in (window-chunks window)
329+
for lines in (mapcar (lambda (l) (coerce l 'list)) (cdr interned-seqs))
330+
do (case (chunk-kind chunk)
331+
(:common
332+
(mapc (lambda (line)
333+
(assert (eql line (nth (ind) result))
334+
(line result index)
335+
"window does not apply at ~d, ~s!=~s "
336+
(ind) (back line) (back (nth (ind) result)))
337+
(incf index))
338+
lines))
339+
((:replace :delete)
340+
(setf result
341+
(append (subseq result 0 (ind))
342+
(subseq result (+ (ind) (length lines)))))
343+
(incf index (length lines))
344+
(decf offset (length lines)))
345+
((:insert :create)
346+
(setf result (append (subseq result 0 (ind))
347+
lines
348+
(subseq result (ind))))
349+
(incf offset (length lines)))))
350+
(values (mapcar #'back result) offset)))))
351+
352+
(defun apply-seq-diff (original-seq diff)
353+
"Apply DIFF to the sequence ORIGINAL-SEQ."
354+
(apply #'values
355+
(reduce
356+
(lambda (accumulator window)
357+
(destructuring-bind (seq offset) accumulator
358+
(multiple-value-call #'list
359+
(apply-seq-window seq window :offset offset))))
360+
(diff-windows diff) :initial-value (list original-seq 0))))
361+
301362
(deftype chunk-kind () '(member :common :delete :replace :insert :create))
302363

303364
(defclass chunk ()
@@ -488,6 +549,19 @@ DIFF:UNIFIED-DIFF or DIFF:CONTEXT-DIFF."
488549
modified-pathname modified)))
489550
(walk-diff-regions context diff-regions)))))
490551

552+
(defun generate-seq-diff (diff-kind original-seq modified-seq)
553+
"Compute a diff between ORIGINAL-PATHNAME and MODIFIED-PATHNAME."
554+
(multiple-value-bind (interner interned-seqs)
555+
(intern-seqs original-seq modified-seq)
556+
(let* ((original (first interned-seqs))
557+
(modified (second interned-seqs))
558+
(lcs (compute-lcs original modified)))
559+
(let ((diff-regions (convert-lcs-to-diff lcs))
560+
(context (create-diff-generator diff-kind interner
561+
"original" original
562+
"modified" modified)))
563+
(walk-diff-regions context diff-regions)))))
564+
491565
(defun format-diff (diff-kind original-pathname modified-pathname &optional (stream *standard-output*))
492566
(render-diff (generate-diff diff-kind
493567
original-pathname

package.lisp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,14 @@
44
(:use :cl)
55
(:export #:*diff-context-lines*
66
#:generate-diff
7+
#:generate-seq-diff
78
#:unified-diff #:context-diff
8-
9+
10+
#:apply-seq-window
11+
#:apply-seq-diff
12+
#:apply-seq-patch
13+
#:apply-patch
14+
915
#:render-diff
1016
#:render-diff-window
1117
#:format-diff
@@ -28,6 +34,7 @@
2834
#:chunk-lines
2935

3036
#:compute-raw-diff
37+
#:compute-raw-seq-diff
3138
#:common-diff-region
3239
#:modified-diff-region
3340
#:original-start

patch.lisp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,3 +341,14 @@
341341
:index-file (and index (pathname index))
342342
:prereq-string prereq)
343343
patches)))))))
344+
345+
(defun apply-seq-patch (original-seq patch)
346+
"Apply PATCH to the sequence ORIGINAL-SEQ."
347+
(apply-seq-diff original-seq (diff patch)))
348+
349+
(defun apply-patch (patch &aux original)
350+
"Apply PATCH."
351+
(do-file-lines (line (original-pathname (diff patch))) (push line original))
352+
(with-open-file (out (original-pathname (diff patch))
353+
:direction :output :if-exists :supersede)
354+
(format out "~{~a~^~%~}~%" (apply-seq-patch (nreverse original) patch))))

0 commit comments

Comments
 (0)