#lang scheme/base (require scheme/gui/base scheme/contract mrlib/graph scheme/pretty scheme/class framework) (provide show-differences find-differences) (define (all-but-last l) (let loop ([l l]) (cond [(null? (cdr l)) null] [else (cons (car l) (loop (cdr l)))]))) (define (record-differences sexp1 sexp2) (let ([ht (make-hasheq)]) ;; loop's result indicates if the sexps are different (let loop ([sexp1 sexp1] [sexp2 sexp2]) (cond [(eq? sexp1 sexp2) #f] [(and (pair? sexp1) (pair? sexp2) (equal? (d-length sexp1) (d-length sexp2))) (let ([subs-same (map/d loop sexp1 sexp2)]) (if (and (andmap values subs-same) (not (= 1 (d-length sexp1)))) (begin (hash-set! ht sexp1 #t) (hash-set! ht sexp2 #t) #t) #f))] [(equal? sexp1 sexp2) #f] [else (hash-set! ht sexp1 #t) (hash-set! ht sexp2 #t) #t])) ht)) (define (unwrap s) (cond [(pair? s) (cons (unwrap (car s)) (unwrap (cdr s)))] [(wrap? s) (wrap-content s)] [else s])) (define (unkink s) (let loop ([s s]) (cond [(pair? s) (cons (loop (car s)) (loop (cdr s)))] [(vector? s) (list->vector (map loop (vector->list s)))] [(box? s) (box (loop (unbox s)))] [(syntax? s) (datum->syntax s (unkink (loop (syntax-e s))) s)] [(number? s) (make-wrap s)] [(symbol? s) (make-wrap s)] [else s]))) (define-struct wrap (content) #:inspector (make-inspector)) (define (show-differences orig-s1 orig-s2 columns) (let-values ([(to-color-s1 to-color-s2) (find-differences orig-s1 orig-s2 columns columns)]) (define f (new frame% [label ""] [width 600] [height 500])) (define hp (new horizontal-panel% [parent f])) (define t1 (new text:basic%)) (define t2 (new text:basic%)) (define c1 (new editor-canvas% [parent hp] [editor t1])) (define c2 (new editor-canvas% [parent hp] [editor t2])) (render-sexp/colors orig-s1 to-color-s1 t1 columns) (render-sexp/colors orig-s2 to-color-s2 t2 columns) (send f show #t))) (define (find-differences orig-s1 orig-s2 columns1 columns2) (let ([s1 (unkink orig-s1)] [s2 (unkink orig-s2)]) (define diff-ht (record-differences s1 s2)) (values (find-coloring s1 diff-ht columns1) (find-coloring s2 diff-ht columns2)))) ;; render-sexp/colors : sexp ht text -> void (define (render-sexp/colors sexp to-color text columns) (let ([start '()]) (parameterize ([pretty-print-columns columns]) (pretty-print sexp (open-output-text-editor text))) (for-each (λ (p) (send text highlight-range (car p) (cdr p) (send the-color-database find-color "NavajoWhite"))) to-color) (send text change-style (make-object style-delta% 'change-family 'modern) 0 (send text last-position)))) (define (find-coloring sexp diff-ht columns) (let* ([start '()] [to-color '()] [pending-bytes (bytes)] [position 0] [counting-port (make-output-port 'counting-port always-evt (λ (bs start end can-block? breaks?) (cond [(= 0 (bytes-length bs)) 0] [else (set! pending-bytes (bytes-append pending-bytes (bytes (bytes-ref bs start)))) (let ([str (with-handlers ([exn:fail:contract? (λ (x) #f)]) (bytes->string/utf-8 pending-bytes))]) (when str (set! position (+ position (string-length str))) (set! pending-bytes (bytes)))) 1])) void)]) (parameterize ([pretty-print-columns columns] [pretty-print-remap-stylable (λ (val) (and (wrap? val) (symbol? (wrap-content val)) (wrap-content val)))] [pretty-print-size-hook (λ (val dsp? port) (if (wrap? val) (string-length (format "~s" (wrap-content val))) #f))] [pretty-print-print-hook (λ (val dsp? port) (write (wrap-content val) port))] [pretty-print-pre-print-hook (λ (obj port) (when (hash-ref diff-ht obj #f) (flush-output port) (set! start (cons position start))))] [pretty-print-post-print-hook (λ (obj port) (when (hash-ref diff-ht obj #f) (flush-output port) (set! to-color (cons (cons (car start) position) to-color)) (set! start (cdr start))))]) (pretty-print sexp counting-port)) to-color)) ;; does a map-like operation, but if the list is dotted, flattens the results into an actual list. (define (map/d f l1 l2) (let loop ([l1 l1] [l2 l2]) (cond [(pair? l1) (cons (f (car l1) (car l2)) (loop (cdr l1) (cdr l2)))] [(null? l1) null] [else (list (f l1 l2))]))) (define (d-length l1) (let loop ([l1 l1] [n 0]) (cond [(pair? l1) (loop (cdr l1) (+ n 1))] [(null? l1) n] [else (cons 'dotted (+ n 1))])))