PageRenderTime 114ms CodeModel.GetById 32ms RepoModel.GetById 1ms app.codeStats 0ms

/diff.ss

http://github.com/yinwang0/ydiff
Scheme | 827 lines | 565 code | 186 blank | 76 comment | 0 complexity | 09d399bf598d0a8aeb18ca3dcf6523a9 MD5 | raw file
Possible License(s): GPL-3.0
  1. ;; ydiff - a language-aware tool for comparing programs
  2. ;; Copyright (C) 2011 Yin Wang (yinwang0@gmail.com)
  3. ;; This program is free software: you can redistribute it and/or modify
  4. ;; it under the terms of the GNU General Public License as published by
  5. ;; the Free Software Foundation, either version 3 of the License, or
  6. ;; (at your option) any later version.
  7. ;; This program is distributed in the hope that it will be useful,
  8. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. ;; GNU General Public License for more details.
  11. ;; You should have received a copy of the GNU General Public License
  12. ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
  13. (load "utils.ss")
  14. ;-------------------------------------------------------------
  15. ; parameters
  16. ;-------------------------------------------------------------
  17. ;; The ratio of cost/size that we consider two nodes to be
  18. ;; "similar", so as to perform a heuristic move (that will
  19. ;; cut running time by a lot.) But this number should be
  20. ;; small enough otherwise everything will be considered to
  21. ;; be moves! Set to a small number for accuracy.
  22. (define *move-ratio* 0)
  23. ;; The minimum size of a node to be considered for moves.
  24. ;; Shouldn't be too small, otherwise small deletec names
  25. ;; will appear in a very distant place!
  26. (define *move-size* 5)
  27. ;; How long must a string be in order for us to use string-dist
  28. ;; function, which is costly when used on long strings but the most
  29. ;; accurate method to use. Currently this parameter is set to 0,
  30. ;; effective disables all LCS string comparison. This improves
  31. ;; performance while not sacrificing accuracy because the algorithm is
  32. ;; AST based.
  33. (define *max-string-len* 0)
  34. ;; Only memoize the diff of nodes of size larger than this number.
  35. ;; This effectively reduces memory usage.
  36. (define *memo-node-size* 2)
  37. ;-------------------------------------------------------------
  38. ; data types
  39. ;-------------------------------------------------------------
  40. (struct Change (orig cur cost type) #:transparent)
  41. (struct Tag (tag idx start) #:transparent)
  42. (define ins?
  43. (lambda (c)
  44. (eq? 'ins (Change-type c))))
  45. (define del?
  46. (lambda (c)
  47. (eq? 'del (Change-type c))))
  48. (define mod?
  49. (lambda (c)
  50. (eq? 'mod (Change-type c))))
  51. ;----------------- utils for creating changes ----------------
  52. (define ins
  53. (lambda (node)
  54. (let ([size (node-size node)])
  55. (list (Change #f node size 'ins)))))
  56. (define del
  57. (lambda (node)
  58. (let ([size (node-size node)])
  59. (list (Change node #f size 'del)))))
  60. (define mod
  61. (lambda (node1 node2 cost)
  62. (list (Change node1 node2 cost 'mod))))
  63. (define mov
  64. (lambda (node1 node2 cost)
  65. (list (Change node1 node2 cost 'mov))))
  66. ;; create a "total change". (delete node1 and insert node2)
  67. (define total
  68. (lambda (node1 node2)
  69. (let ([size1 (node-size node1)]
  70. [size2 (node-size node2)])
  71. (values (append (del node1) (ins node2))
  72. (+ size1 size2)))))
  73. (define mod->mov
  74. (lambda (c)
  75. (match c
  76. [(Change node1 node2 cost 'mod)
  77. (Change node1 node2 cost 'mov)]
  78. [other other])))
  79. ;;------------------ frames utils --------------------
  80. (define deframe
  81. (lambda (node)
  82. (match node
  83. [(Expr _ _ 'frame elts)
  84. (apply append (map deframe elts))]
  85. [else (list node)])))
  86. (define deframe-change
  87. (lambda (change)
  88. (cond
  89. [(ins? change)
  90. (apply append
  91. (map ins (deframe (Change-cur change))))]
  92. [(del? change)
  93. (apply append
  94. (map del (deframe (Change-orig change))))]
  95. [else (list change)])))
  96. (define extract-frame
  97. (lambda (node1 node2 type)
  98. (match node1
  99. [(Expr start1 end1 type1 elts1)
  100. (let ([frame-elts (filter (lambda (x)
  101. (not (eq? x node2)))
  102. elts1)])
  103. (type (Expr start1 start1 'frame frame-elts)))]
  104. [_ fatal 'extract-frame "I only accept Expr"])))
  105. ;; (define n1 (Token "ok" 0 1))
  106. ;; (define n2 (Expr 'ok 0 2 (list n1 (Token "bar" 1 2))))
  107. ;; (map deframe-change (extract-frame n2 n1 ins))
  108. ;------------------ operations on nodes ---------------------
  109. ;; "virtual function" - get definition name
  110. ;; can be overridden by individual languages
  111. (define get-name (lambda (node) #f))
  112. ;; "virtual function" - get node type
  113. ;; can be overridden by individual languages
  114. (define get-type
  115. (lambda (node)
  116. (cond
  117. [(Expr? node) (Expr-type node)]
  118. [(Token? node) 'token]
  119. [(Comment? node) 'comment]
  120. [(Str? node) 'str]
  121. [(Char? node) 'char])))
  122. ;; same-def? and different-def? only depend on get-name, so they need
  123. ;; not be overridden by individual languages.
  124. (define same-def?
  125. (lambda (e1 e2)
  126. (cond
  127. [(not (eq? (get-type e1) (get-type e2)))
  128. #f]
  129. [else
  130. (let ([name1 (get-name e1)]
  131. [name2 (get-name e2)])
  132. (and name1 name2 (equal? name1 name2)))])))
  133. (define different-def?
  134. (lambda (e1 e2)
  135. (cond
  136. [(not (eq? (get-type e1) (get-type e2)))
  137. #f]
  138. [else
  139. (let ([name1 (get-name e1)]
  140. [name2 (get-name e2)])
  141. (and name1 name2 (not (equal? name1 name2))))])))
  142. ;; whether two nodes are similar given the cost
  143. (define similar?
  144. (lambda (node1 node2 c)
  145. (<= c (* *move-ratio* (+ (node-size node1)
  146. (node-size node2))))))
  147. ;----------- node size function ------------
  148. (define *node-size-hash* (make-hasheq))
  149. (define node-size
  150. (lambda (node)
  151. (define memo
  152. (lambda (v)
  153. (if (> v 1)
  154. (hash-set! *node-size-hash* node v)
  155. (void))
  156. v))
  157. (cond
  158. [(pair? node)
  159. (apply + (map node-size node))]
  160. [(or (Token? node) (Str? node) (Char? node)) 1]
  161. [(Expr? node)
  162. (cond
  163. [(hash-has-key? *node-size-hash* node)
  164. (hash-ref *node-size-hash* node)]
  165. [else
  166. (memo (node-size (Expr-elts node)))])]
  167. [else 0])))
  168. (define node-depth
  169. (lambda (node)
  170. (cond
  171. [(null? node) 0]
  172. [(pair? node)
  173. (apply max (map node-depth node))]
  174. [(Expr? node)
  175. (add1 (node-depth (Expr-elts node)))]
  176. [else 0])))
  177. ; (node-depth (parse-scheme "(lambda (x (x (y)) (y)) x)"))
  178. (define uid
  179. (let ([count 1]
  180. [table (box '())])
  181. (lambda (node)
  182. (let ([p (assq node (unbox table))])
  183. (cond
  184. [(not p)
  185. (let ([id count])
  186. (set! count (add1 count))
  187. (set-box! table (cons `(,node . ,id) (unbox table)))
  188. id)]
  189. [else
  190. (cdr p)])))))
  191. ;; similarity string from a change
  192. (define similarity
  193. (lambda (change)
  194. (let ([total (+ (node-size (Change-orig change))
  195. (node-size (Change-cur change)))])
  196. (cond
  197. [(or (= 0 total) (= 0 (Change-cost change)))
  198. "100%"]
  199. [else
  200. (string-append
  201. (real->decimal-string
  202. (* 100 (- 1.0 (/ (Change-cost change) total))) 1)
  203. "%")]))))
  204. ;-------------------------------------------------------------
  205. ; diff proper
  206. ;-------------------------------------------------------------
  207. ; 2-D memoization table
  208. (define make-table
  209. (lambda (dim1 dim2)
  210. (let ([vec (make-vector (add1 dim1))])
  211. (let loop ([n 0])
  212. (cond
  213. [(= n (vector-length vec)) vec]
  214. [else
  215. (vector-set! vec n (make-vector (add1 dim2) #f))
  216. (loop (add1 n))])))))
  217. (define table-ref
  218. (lambda (t x y)
  219. (let ([row (vector-ref t x)])
  220. (vector-ref row y))))
  221. (define table-set!
  222. (lambda (t x y v)
  223. (let ([row (vector-ref t x)])
  224. (vector-set! row y v))))
  225. ;---------------- string distance function -----------------
  226. ;; string distance is no longer used because string=? saffice to
  227. ;; compare strings in ASTs. Retain it here for possible later uses.
  228. (define string-dist
  229. (lambda (s1 s2)
  230. (let* ([len1 (string-length s1)]
  231. [len2 (string-length s2)]
  232. [t (make-table len1 len2)]
  233. [char-dist (dist1 t s1 0 s2 0)])
  234. (cond
  235. [(= 0 (+ len1 len2)) 0]
  236. [else
  237. (/ (* 2.0 char-dist) (+ len1 len2))]))))
  238. (define dist1
  239. (lambda (table s1 start1 s2 start2)
  240. (define memo
  241. (lambda (value)
  242. (table-set! table start1 start2 value)
  243. value))
  244. (cond
  245. [(table-ref table start1 start2)
  246. => (lambda (cached) cached)]
  247. [(= start1 (string-length s1))
  248. (memo (- (string-length s2) start2))]
  249. [(= start2 (string-length s2))
  250. (memo (- (string-length s1) start1))]
  251. [else
  252. (let* ([c1 (string-ref s1 start1)]
  253. [c2 (string-ref s2 start2)]
  254. [d0 (cond
  255. [(char=? c1 c2) 0]
  256. [(char=? (char-downcase c1)
  257. (char-downcase c2)) 1]
  258. [else 2])]
  259. [d1 (+ d0 (dist1 table s1 (add1 start1) s2 (add1 start2)))]
  260. [d2 (+ 1 (dist1 table s1 (add1 start1) s2 start2))]
  261. [d3 (+ 1 (dist1 table s1 start1 s2 (add1 start2)))])
  262. (memo (min d1 d2 d3)))])))
  263. ;--------------------- the primary diff function -------------------
  264. (define diff-node
  265. (lambda (node1 node2 move?)
  266. (define memo
  267. (lambda (v1 v2)
  268. (and (> (node-size node1) *memo-node-size*)
  269. (> (node-size node2) *memo-node-size*)
  270. (hash-put! *diff-hash* node1 node2 (cons v1 v2)))
  271. (values v1 v2)))
  272. (define try-extract
  273. (lambda (changes cost)
  274. (cond
  275. [(or (not move?)
  276. (similar? node1 node2 cost))
  277. (memo changes cost)]
  278. [else
  279. (letv ([(m c) (diff-extract node1 node2 move?)])
  280. (cond
  281. [(not m)
  282. (memo changes cost)]
  283. [else
  284. (memo m c)]))])))
  285. (diff-progress 1) ;; progress bar
  286. (cond
  287. [(hash-get *diff-hash* node1 node2)
  288. => (lambda (cached)
  289. (values (car cached) (cdr cached)))]
  290. [(and (Char? node1) (Char? node2))
  291. (diff-string (char->string (Char-text node1))
  292. (char->string (Char-text node2))
  293. node1 node2)]
  294. [(and (Str? node1) (Str? node2))
  295. (diff-string (Str-text node1) (Str-text node2) node1 node2)]
  296. [(and (Comment? node1) (Comment? node2))
  297. (diff-string (Comment-text node1) (Comment-text node2) node1 node2)]
  298. [(and (Token? node1) (Token? node2))
  299. (diff-string (Token-text node1) (Token-text node2) node1 node2)]
  300. [(and (Expr? node1) (Expr? node2)
  301. (eq? (get-type node1) (get-type node2)))
  302. (letv ([(m c) (diff-list (Expr-elts node1) (Expr-elts node2) move?)])
  303. (try-extract m c))]
  304. [(and (pair? node1) (not (pair? node2)))
  305. (diff-list node1 (list node2) move?)]
  306. [(and (not (pair? node1)) (pair? node2))
  307. (diff-list (list node1) node2 move?)]
  308. [(and (pair? node1) (pair? node2))
  309. (diff-list node1 node2 move?)]
  310. [else
  311. (letv ([(m c) (total node1 node2)])
  312. (try-extract m c))])))
  313. ;; helper for nodes with string contents (Str, Comment, Token etc.)
  314. (define diff-string
  315. (lambda (string1 string2 node1 node2)
  316. (cond
  317. [(or (> (string-length string1) *max-string-len*)
  318. (> (string-length string2) *max-string-len*))
  319. (cond
  320. [(string=? string1 string2)
  321. (values (mod node1 node2 0) 0)]
  322. [else
  323. (total node1 node2)])]
  324. [else
  325. (let ([cost (string-dist string1 string2)])
  326. (values (mod node1 node2 cost) cost))])))
  327. ;; global 2D hash for storing known diffs
  328. (define *diff-hash* (make-hasheq))
  329. (define diff-list
  330. (lambda (ls1 ls2 move?)
  331. (let ([ls1 (sort ls1 node-sort-fn)]
  332. [ls2 (sort ls2 node-sort-fn)])
  333. (diff-list1 (make-hasheq) ls1 ls2 move?))))
  334. (define diff-list1
  335. (lambda (table ls1 ls2 move?)
  336. (define memo
  337. (lambda (v1 v2)
  338. (hash-put! table ls1 ls2 (cons v1 v2))
  339. (values v1 v2)))
  340. (define guess
  341. (lambda (ls1 ls2)
  342. (letv ([(m0 c0) (diff-node (car ls1) (car ls2) move?)]
  343. [(m1 c1) (diff-list1 table (cdr ls1) (cdr ls2) move?)]
  344. [cost1 (+ c0 c1)])
  345. (cond
  346. [(or (same-def? (car ls1) (car ls2))
  347. (and (not (different-def? (car ls1) (car ls2)))
  348. (similar? (car ls1) (car ls2) c0)))
  349. (memo (append m0 m1) cost1)]
  350. [else
  351. (letv ([(m2 c2) (diff-list1 table (cdr ls1) ls2 move?)]
  352. [(m3 c3) (diff-list1 table ls1 (cdr ls2) move?)]
  353. [cost2 (+ c2 (node-size (car ls1)))]
  354. [cost3 (+ c3 (node-size (car ls2)))])
  355. (cond
  356. [(<= cost2 cost3)
  357. (memo (append (del (car ls1)) m2) cost2)]
  358. [else
  359. (memo (append (ins (car ls2)) m3) cost3)]))]))))
  360. (cond
  361. [(hash-get table ls1 ls2)
  362. => (lambda (cached)
  363. (values (car cached) (cdr cached)))]
  364. [(and (null? ls1) (null? ls2))
  365. (values '() 0)]
  366. [(null? ls1)
  367. (let ([changes (apply append (map ins ls2))])
  368. (values changes (node-size ls2)))]
  369. [(null? ls2)
  370. (let ([changes (apply append (map del ls1))])
  371. (values changes (node-size ls1)))]
  372. [else
  373. (guess ls1 ls2)])))
  374. ;; structure extraction
  375. (define diff-extract
  376. (lambda (node1 node2 move?)
  377. (cond
  378. [(or (< (node-size node1) *move-size*)
  379. (< (node-size node2) *move-size*))
  380. (values #f #f)]
  381. [(and (Expr? node1) (Expr? node2))
  382. (cond
  383. [(<= (node-size node1) (node-size node2))
  384. (let loop ([elts2 (Expr-elts node2)])
  385. (cond
  386. [(null? elts2) (values #f #f)]
  387. [else
  388. (letv ([(m0 c0) (diff-node node1 (car elts2) move?)])
  389. (cond
  390. [(or (same-def? node1 (car elts2))
  391. (similar? node1 (car elts2) c0))
  392. (let ([frame (extract-frame node2 (car elts2) ins)])
  393. (values (append m0 frame) c0))]
  394. [else
  395. (loop (cdr elts2))]))]))]
  396. [else
  397. (let loop ([elts1 (Expr-elts node1)])
  398. (cond
  399. [(null? elts1) (values #f #f)]
  400. [else
  401. (letv ([(m0 c0) (diff-node (car elts1) node2 move?)])
  402. (cond
  403. [(or (same-def? (car elts1) node2)
  404. (similar? (car elts1) node2 c0))
  405. (let ([frame (extract-frame node1 (car elts1) del)])
  406. (values (append m0 frame) c0))]
  407. [else
  408. (loop (cdr elts1))]))]))])]
  409. [else (values #f #f)])))
  410. ;-------------------------------------------------------------
  411. ; finding moves
  412. ;-------------------------------------------------------------
  413. (define big-node?
  414. (lambda (node)
  415. (>= (node-size node) *move-size*)))
  416. (define big-change?
  417. (lambda (c)
  418. (cond
  419. [(ins? c)
  420. (big-node? (Change-cur c))]
  421. [(del? c)
  422. (big-node? (Change-orig c))]
  423. [(mod? c)
  424. (or (big-node? (Change-orig c))
  425. (big-node? (Change-cur c)))])))
  426. (define node-sort-fn
  427. (lambda (x y)
  428. (let ([name1 (get-name x)]
  429. [name2 (get-name y)])
  430. (cond
  431. [(and name1 name2)
  432. (string<? (symbol->string name1)
  433. (symbol->string name2))]
  434. [(and name1 (not name2)) #t]
  435. [(and (not name1) name2) #f]
  436. [else
  437. (< (get-start x) (get-start y))]))))
  438. ;; iterate diff-list on the list of changes
  439. (define closure
  440. (lambda (changes)
  441. (set! *diff-hash* (make-hasheq))
  442. (let loop ([changes changes] [closed '()] [count 1])
  443. (printf "~n[closure loop #~a] " count)
  444. (letv ([dels (filter (predand del? big-change?) changes)]
  445. [adds (filter (predand ins? big-change?) changes)]
  446. [rest (set- changes (append dels adds))]
  447. [ls1 (sort (map Change-orig dels) node-sort-fn)]
  448. [ls2 (sort (map Change-cur adds) node-sort-fn)]
  449. [(m c) (diff-list ls1 ls2 #t)]
  450. [new-moves (map mod->mov (filter mod? m))])
  451. (printf "~n~a new moves found" (length new-moves))
  452. (cond
  453. [(null? new-moves)
  454. (let ([all-changes (append m rest closed)])
  455. (apply append (map deframe-change all-changes)))]
  456. [else
  457. (let ([new-changes (filter (negate mod?) m)])
  458. (loop new-changes
  459. (append new-moves rest closed)
  460. (add1 count)))])))))
  461. ;-------------------------------------------------------------
  462. ; HTML generation
  463. ;-------------------------------------------------------------
  464. ;----------------- utils ----------------
  465. (define qs
  466. (lambda (x)
  467. (let ([x (cond
  468. [(symbol? x) (symbol->string x)]
  469. [(number? x) (number->string x)]
  470. [(string? x) x])])
  471. (string-append "'" x "'"))))
  472. (define line
  473. (lambda (port . s)
  474. (display (string-append (apply string-append s) "\n") port)))
  475. (define change-tags
  476. (lambda (changes side)
  477. (let loop ([cs changes] [tags '()])
  478. (cond
  479. [(null? cs) tags]
  480. [else
  481. (let ([key (if (eq? side 'left)
  482. (Change-orig (car cs))
  483. (Change-cur (car cs)))])
  484. (cond
  485. [(or (not key)
  486. (= (get-start key) (get-end key)))
  487. (loop (cdr cs) tags)]
  488. [(and (Change-orig (car cs)) (Change-cur (car cs)))
  489. (let ([startTag (Tag (link-start (car cs) side)
  490. (get-start key) -1)]
  491. [endTag (Tag "</a>" (get-end key) (get-start key))])
  492. (loop (cdr cs) (cons endTag (cons startTag tags))))]
  493. [else
  494. (let ([startTag (Tag (span-start (car cs) side)
  495. (get-start key) -1)]
  496. [endTag (Tag "</span>" (get-end key) (get-start key))])
  497. (loop (cdr cs) (cons endTag (cons startTag tags))))]))]))))
  498. (define apply-tags
  499. (lambda (s tags)
  500. (let ([tags (sort tags tag-sort-fn)])
  501. (let loop ([tags tags] [curr 0] [out '()])
  502. (cond
  503. [(null? tags)
  504. (cond
  505. [(< curr (string-length s))
  506. (loop tags (add1 curr) (cons (escape (string-ref s curr)) out))]
  507. [else
  508. (apply string-append (reverse out))])]
  509. [else
  510. (cond
  511. [(< curr (Tag-idx (car tags)))
  512. (loop tags (add1 curr) (cons (escape (string-ref s curr)) out))]
  513. [else
  514. (loop (cdr tags) curr (cons (Tag-tag (car tags)) out))])])))))
  515. ;; get the CSS class for the change
  516. (define change-class
  517. (lambda (change)
  518. (cond
  519. [(and (eq? (Change-type change) 'mov)
  520. (> (Change-cost change) 0))
  521. "mc"] ; move-change
  522. [(eq? (Change-type change) 'mov) "m"] ; move
  523. [(> (Change-cost change) 0) "c"] ; change
  524. [else "u"]))) ; unchanged
  525. (define link-start
  526. (lambda (change side)
  527. (let ([cls (change-class change)]
  528. [me (if (eq? side 'left)
  529. (Change-orig change)
  530. (Change-cur change))]
  531. [other (if (eq? side 'left)
  532. (Change-cur change)
  533. (Change-orig change))])
  534. (string-append
  535. "<a id=" (qs (uid me))
  536. " tid=" (qs (uid other))
  537. " class=" (qs cls)
  538. ">"))))
  539. (define span-start
  540. (lambda (change side)
  541. (let ([cls (if (eq? 'del (Change-type change)) "d" "i")]) ; del or ins
  542. (string-append "<span class=" (qs cls) ">"))))
  543. (define tag-sort-fn
  544. (lambda (t1 t2)
  545. (cond
  546. [(= (Tag-idx t1) (Tag-idx t2))
  547. (> (Tag-start t1) (Tag-start t2))]
  548. [else
  549. (< (Tag-idx t1) (Tag-idx t2))])))
  550. (define *escape-table*
  551. '((#\" . "&quot;")
  552. (#\' . "&#39;")
  553. (#\< . "&lt;")
  554. (#\> . "&gt;")))
  555. (define escape
  556. (lambda (c)
  557. (cond
  558. [(assq c *escape-table*) => cdr]
  559. [else (char->string c)])))
  560. ; getting the base name of a path/file name
  561. ; (base-name "mk/mk-c.scm") => mk-c
  562. (define base-name
  563. (lambda (fn)
  564. (let loop ([i (- (string-length fn) 1)]
  565. [start -1]
  566. [end (- (string-length fn) 1)])
  567. (cond
  568. [(= i 0)
  569. (substring fn i end)]
  570. [(eq? (string-ref fn i) #\.)
  571. (loop (sub1 i) start i)]
  572. [(eq? (string-ref fn i) #\/)
  573. (substring fn (add1 i) end)]
  574. [else
  575. (loop (sub1 i) start end)]))))
  576. (define html-header
  577. (lambda (port)
  578. (line port "<html>")
  579. (line port "<head>")
  580. (line port "<META http-equiv=\"Content-Type\""
  581. " content=\"text/html; charset=utf-8\">")
  582. (line port "<LINK href=\"diff-s.css\""
  583. " rel=\"stylesheet\" type=\"text/css\">")
  584. (line port "<script type=\"text/javascript\""
  585. " src=\"nav-div.js\"></script>")
  586. (line port "</head>")
  587. (line port "<body>")))
  588. (define html-footer
  589. (lambda (port)
  590. (line port "</body>")
  591. (line port "</html>")))
  592. (define write-html
  593. (lambda (port text side)
  594. (line port (string-append "<div id=\"" side "\" class=\"src\">"))
  595. (line port "<pre>")
  596. (if (string=? side "left")
  597. (line port "<a id='leftstart' tid='rightstart'></a>")
  598. (line port "<a id='rightstart' tid='leftstart'></a>"))
  599. (line port text)
  600. (line port "</pre>")
  601. (line port "</div>")))
  602. ;; poor man's progress bar
  603. (define diff-progress
  604. (new-progress 10000))
  605. (define cleanup
  606. (lambda ()
  607. (set! *node-size-hash* (make-hasheq))
  608. (set! *diff-hash* (make-hasheq))))
  609. ;; main command
  610. ;; takes two file names and a parser
  611. (define diff
  612. (lambda (file1 file2 parse)
  613. (cleanup)
  614. (letv ([start (current-seconds)]
  615. [s1 (read-file file1)]
  616. [s2 (read-file file2)]
  617. [node1 (parse s1)]
  618. [node2 (parse s2)]
  619. [_ (diff-progress "\nDone parsing")]
  620. [(changes cost) (diff-node node1 node2 #f)]
  621. [_ (diff-progress "\nDone diffing")]
  622. [changes (closure changes)]
  623. [_ (diff-progress "\nDone moving")]
  624. [_ (set! *diff-hash* (make-hasheq))]
  625. [ctags1 (change-tags changes 'left)]
  626. [ctags2 (change-tags changes 'right)]
  627. [tagged1 (apply-tags s1 ctags1)]
  628. [tagged2 (apply-tags s2 ctags2)]
  629. [frame-file (string-append (base-name file1) "-"
  630. (base-name file2) ".html")]
  631. [port (open-output-file frame-file
  632. #:mode 'text
  633. #:exists 'replace)]
  634. [end (current-seconds)])
  635. (printf "finished in ~a seconds~n" (- end start))
  636. (html-header port)
  637. (write-html port tagged1 "left")
  638. (write-html port tagged2 "right")
  639. (html-footer port)
  640. (close-output-port port)
  641. (cleanup))))