aboutsummaryrefslogtreecommitdiffstats
path: root/csc/compare.csc
diff options
context:
space:
mode:
Diffstat (limited to 'csc/compare.csc')
-rw-r--r--csc/compare.csc219
1 files changed, 219 insertions, 0 deletions
diff --git a/csc/compare.csc b/csc/compare.csc
new file mode 100644
index 0000000..c9716bf
--- /dev/null
+++ b/csc/compare.csc
@@ -0,0 +1,219 @@
+(define-library (csc compare)
+ (export diff)
+ (import (scheme base)
+ (only (csc format) fprintf)
+ (only (csc loop)
+ loop
+ return)
+ (only (csc match) match)
+ (only (csc sort) sort)
+ (only (csc strings)
+ contains
+ split)
+ (only (csc vec)
+ vec
+ vec-append))
+ (begin
+ ; compare is inspired by Go's cmp.Diff.
+
+
+ (define (apply-transformer t x)
+ (if (list? t)
+ (loop for transformer in t
+ do (set! x (apply-transformer transformer x))
+ finally (return x))
+ (if ((car t) x)
+ ((cdr t) x)
+ x)))
+
+
+ (define (alist? l)
+ (match l
+ (((key . _) . _)
+ (symbol? key))
+ (_ #f)))
+
+
+ (define (transform x transformers)
+ (define x* (apply-transformer transformers x))
+ (cond
+ ((alist? x*)
+ (sort (lambda (a b) (string<? (symbol->string (car a)) (symbol->string (car b))))
+ (map (lambda (elem)
+ (cons (car elem) (transform (cdr elem) transformers)))
+ x*)))
+ ((list? x*)
+ (map (lambda (elem) (transform elem transformers))
+ x*))
+ (else x*)))
+
+
+ ; As always, I copied the algorithm from Wikipedia
+ (define (lcs x y cmp)
+ (define x-vals (list->vector x))
+ (define y-vals (list->vector y))
+ (define table (make-vector (* (+ 1 (vector-length x-vals)) (+ 1 (vector-length y-vals))) 0))
+ (define (index i j)
+ (+ j (* i (+ 1 (vector-length y-vals)))))
+ (loop for i from 1 to (vector-length x-vals)
+ do (loop for j from 1 to (vector-length y-vals)
+ if (cmp (vector-ref x-vals (- i 1)) (vector-ref y-vals (- j 1)))
+ do (vector-set! table (index i j) (+ 1 (vector-ref table (index (- i 1) (- j 1)))))
+ else
+ do (vector-set! table (index i j) (max (vector-ref table (index (- i 1) j))
+ (vector-ref table (index i (- j 1)))))))
+ (let loop ((i (vector-length x-vals))
+ (j (vector-length y-vals))
+ (lcs '()))
+ (cond
+ ((or (= 0 i) (= 0 j)) lcs)
+ ((cmp (vector-ref x-vals (- i 1)) (vector-ref y-vals (- j 1)))
+ (loop (- i 1) (- j 1) (cons (vector-ref x-vals (- i 1)) lcs)))
+ ((> (vector-ref table (index i (- j 1))) (vector-ref table (index (- i 1) j)))
+ (loop i (- j 1) lcs))
+ (else (loop (- i 1) j lcs)))))
+
+
+ (define (pretty w x indent)
+ (cond
+ ((list? x)
+ (fprintf w "{}(\n" indent)
+ (loop with new-indent = (string-append indent " ")
+ for v in x
+ do (pretty w v new-indent))
+ (fprintf w "{})\n" indent))
+ ((and (pair? x)
+ (symbol? (car x)))
+ (fprintf w "{}({} .\n" indent (car x))
+ (pretty w (cdr x) (string-append indent " "))
+ (fprintf w "{})\n" indent))
+ (else (fprintf w "{}{}\n" indent x))))
+
+
+ (define (diff* w x y indent)
+ (cond
+ ((or (and (boolean? x) (boolean? y))
+ (and (char? x) (char? y))
+ (and (symbol? x) (symbol? y))
+ (and (number? x) (number? y))
+ (and (string? x) (string? y)))
+ (if (equal? x y)
+ (begin
+ (fprintf w "{} {}\n" indent x)
+ #t)
+ (begin
+ (fprintf w "{}-{}\n{}+{}\n" indent x indent y)
+ #f)))
+ ((and (alist? x) (alist? y))
+ (fprintf w "{} (\n" indent)
+ (let ((key-lcs (lcs (map car x) (map car y) symbol=?))
+ (indent* (string-append indent " "))
+ (equal #t))
+ (loop if (null? key-lcs)
+ if (null? x)
+ if (null? y)
+ do (return)
+ else
+ do (pretty w (car y) (string-append indent* "+"))
+ (set! y (cdr y))
+ (set! equal #f)
+ else
+ do (pretty w (car x) (string-append indent* "-"))
+ (set! x (cdr x))
+ (set! equal #f)
+ else
+ if (symbol=? (caar x) (caar y) (car key-lcs))
+ do (fprintf w "{} ({} .\n" indent* (car key-lcs))
+ (set! equal (and (diff* w (cdar x) (cdar y) (string-append indent* " "))
+ equal))
+ (fprintf w "{} )\n" indent*)
+ (set! x (cdr x))
+ (set! y (cdr y))
+ (set! key-lcs (cdr key-lcs))
+ else if (symbol=? (caar x) (car key-lcs))
+ do (pretty w (car y) (string-append indent* "+"))
+ (set! y (cdr y))
+ (set! equal #f)
+ else
+ do (pretty w (car x) (string-append indent* "-"))
+ (set! x (cdr x))
+ (set! equal #f))
+ (fprintf w "{} )\n" indent)
+ equal))
+ ((and (list? x) (list? y))
+ (fprintf w "{} (\n" indent)
+ (let* ((common (lcs x y equal?))
+ (indent* (string-append indent " "))
+ (equal #t))
+ (loop if (null? common)
+ if (null? x)
+ if (null? y)
+ do (return)
+ else
+ do (pretty w (car y) (string-append indent* "+"))
+ (set! y (cdr y))
+ (set! equal #f)
+ else if (null? y)
+ do (pretty w (car x) (string-append indent* "-"))
+ (set! x (cdr x))
+ (set! equal #f)
+ else
+ do (diff* w (car x) (car y) indent*)
+ (set! x (cdr x))
+ (set! y (cdr y))
+ (set! equal #f)
+ else
+ if (equal? (car x) (car y))
+ do (pretty w (car x) (string-append indent* " "))
+ (set! x (cdr x))
+ (set! y (cdr y))
+ (set! common (cdr common))
+ else if (equal? (car x) (car common))
+ do (pretty w (car y) (string-append indent* "+"))
+ (set! y (cdr y))
+ (set! equal #f)
+ else if (equal? (car y) (car common))
+ do (pretty w (car x) (string-append indent* "-"))
+ (set! x (cdr x))
+ (set! equal #f)
+ else
+ do (diff* w (car x) (car y) indent*)
+ (set! x (cdr x))
+ (set! y (cdr y))
+ (set! equal #f))
+ (fprintf w "{} )\n" indent)
+ equal))))
+
+
+ (define default-transformers
+ (list
+ (cons (lambda (s)
+ (and (string? s)
+ (contains s "\n")))
+ (lambda (s)
+ (list (cons 'type 'string)
+ (cons 'value (split s "\n")))))
+ (cons vector? (lambda (v)
+ (list (cons 'type 'vector)
+ (cons 'value (vector->list v)))))
+ (cons bytevector? (lambda (b)
+ (list (cons 'type 'bytevector)
+ (cons 'value (loop for i below (bytevector-length b)
+ collect (string-append "0x" (number->string (bytevector-u8-ref b i))))))))))
+
+
+ ; Returns a string diff between x and y. Diff knows how to compare lists,
+ ; alists and other primitive types. Any user-defined type needs a
+ ; transformer. Each transformer is a pair of type predicate that the
+ ; transformer matches on, and a function that transforms a value of that
+ ; type into an alist.
+ ;
+ ; Potential future enhancements:
+ ; - support vector and bytevector
+ ; - split strings into lines
+ (define (diff x y . transformers)
+ (define d (open-output-string))
+ (define t* (cons default-transformers transformers))
+ (if (diff* d (transform x t*) (transform y t*) "")
+ ""
+ (get-output-string d)))))