srfi-38.scm 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. ;; Copyright (C) 2010 Free Software Foundation, Inc.
  2. ;; Copyright (C) Ray Dillinger 2003. All Rights Reserved.
  3. ;;
  4. ;; Contains code based upon Alex Shinn's public-domain implementation of
  5. ;; `read-with-shared-structure' found in Chicken's SRFI 38 egg.
  6. ;; Permission is hereby granted, free of charge, to any person obtaining
  7. ;; a copy of this software and associated documentation files (the
  8. ;; "Software"), to deal in the Software without restriction, including
  9. ;; without limitation the rights to use, copy, modify, merge, publish,
  10. ;; distribute, sublicense, and/or sell copies of the Software, and to
  11. ;; permit persons to whom the Software is furnished to do so, subject to
  12. ;; the following conditions:
  13. ;; The above copyright notice and this permission notice shall be
  14. ;; included in all copies or substantial portions of the Software.
  15. ;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  16. ;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  17. ;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  18. ;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
  19. ;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
  20. ;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  21. ;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  22. ;; SOFTWARE.
  23. (define-module (srfi srfi-38)
  24. #:export (write-with-shared-structure
  25. read-with-shared-structure)
  26. #:use-module (rnrs bytevectors)
  27. #:use-module (srfi srfi-8)
  28. #:use-module (srfi srfi-69)
  29. #:use-module (system vm trap-state))
  30. (cond-expand-provide (current-module) '(srfi-38))
  31. ;; A printer that shows all sharing of substructures. Uses the Common
  32. ;; Lisp print-circle notation: #n# refers to a previous substructure
  33. ;; labeled with #n=. Takes O(n^2) time.
  34. ;; Code attributed to Al Petrofsky, modified by Ray Dillinger.
  35. ;; Modified in 2010 by Andreas Rottmann to use SRFI 69 hashtables,
  36. ;; making the time O(n), and adding some of Guile's data types to the
  37. ;; `interesting' objects.
  38. (define* (write-with-shared-structure obj
  39. #:optional
  40. (outport (current-output-port))
  41. (optarg #f))
  42. ;; We only track duplicates of pairs, vectors, strings, bytevectors,
  43. ;; structs (which subsume R6RS and SRFI-9 records), ports and (native)
  44. ;; hash-tables. We ignore zero-length vectors and strings because
  45. ;; r5rs doesn't guarantee that eq? treats them sanely (and they aren't
  46. ;; very interesting anyway).
  47. (define (interesting? obj)
  48. (or (pair? obj)
  49. (and (vector? obj) (not (zero? (vector-length obj))))
  50. (and (string? obj) (not (zero? (string-length obj))))
  51. (bytevector? obj)
  52. (struct? obj)
  53. (port? obj)
  54. (hash-table? obj)))
  55. ;; (write-obj OBJ STATE):
  56. ;;
  57. ;; STATE is a hashtable which has an entry for each interesting part
  58. ;; of OBJ. The associated value will be:
  59. ;;
  60. ;; -- a number if the part has been given one,
  61. ;; -- #t if the part will need to be assigned a number but has not been yet,
  62. ;; -- #f if the part will not need a number.
  63. ;; The entry `counter' in STATE should be the most recently
  64. ;; assigned number.
  65. ;;
  66. ;; Mutates STATE for any parts that had numbers assigned.
  67. (define (write-obj obj state)
  68. (define (write-interesting)
  69. (cond ((pair? obj)
  70. (display "(" outport)
  71. (write-obj (car obj) state)
  72. (let write-cdr ((obj (cdr obj)))
  73. (cond ((and (pair? obj) (not (hash-table-ref state obj)))
  74. (display " " outport)
  75. (write-obj (car obj) state)
  76. (write-cdr (cdr obj)))
  77. ((null? obj)
  78. (display ")" outport))
  79. (else
  80. (display " . " outport)
  81. (write-obj obj state)
  82. (display ")" outport)))))
  83. ((vector? obj)
  84. (display "#(" outport)
  85. (let ((len (vector-length obj)))
  86. (write-obj (vector-ref obj 0) state)
  87. (let write-vec ((i 1))
  88. (cond ((= i len) (display ")" outport))
  89. (else (display " " outport)
  90. (write-obj (vector-ref obj i) state)
  91. (write-vec (+ i 1)))))))
  92. ;; else it's a string
  93. (else (write obj outport))))
  94. (cond ((interesting? obj)
  95. (let ((val (hash-table-ref state obj)))
  96. (cond ((not val) (write-interesting))
  97. ((number? val)
  98. (begin (display "#" outport)
  99. (write val outport)
  100. (display "#" outport)))
  101. (else
  102. (let ((n (+ 1 (hash-table-ref state 'counter))))
  103. (display "#" outport)
  104. (write n outport)
  105. (display "=" outport)
  106. (hash-table-set! state 'counter n)
  107. (hash-table-set! state obj n)
  108. (write-interesting))))))
  109. (else
  110. (write obj outport))))
  111. ;; Scan computes the initial value of the hash table, which maps each
  112. ;; interesting part of the object to #t if it occurs multiple times,
  113. ;; #f if only once.
  114. (define (scan obj state)
  115. (cond ((not (interesting? obj)))
  116. ((hash-table-exists? state obj)
  117. (hash-table-set! state obj #t))
  118. (else
  119. (hash-table-set! state obj #f)
  120. (cond ((pair? obj)
  121. (scan (car obj) state)
  122. (scan (cdr obj) state))
  123. ((vector? obj)
  124. (let ((len (vector-length obj)))
  125. (do ((i 0 (+ 1 i)))
  126. ((= i len))
  127. (scan (vector-ref obj i) state))))))))
  128. (let ((state (make-hash-table eq?)))
  129. (scan obj state)
  130. (hash-table-set! state 'counter 0)
  131. (write-obj obj state)))
  132. ;; A reader that understands the output of the above writer. This has
  133. ;; been written by Andreas Rottmann to re-use Guile's built-in reader,
  134. ;; with inspiration from Alex Shinn's public-domain implementation of
  135. ;; `read-with-shared-structure' found in Chicken's SRFI 38 egg.
  136. (define* (read-with-shared-structure #:optional (port (current-input-port)))
  137. (let ((parts-table (make-hash-table eqv?)))
  138. ;; reads chars that match PRED and returns them as a string.
  139. (define (read-some-chars pred initial)
  140. (let iter ((chars initial))
  141. (let ((c (peek-char port)))
  142. (if (or (eof-object? c) (not (pred c)))
  143. (list->string (reverse chars))
  144. (iter (cons (read-char port) chars))))))
  145. (define (read-hash c port)
  146. (let* ((n (string->number (read-some-chars char-numeric? (list c))))
  147. (c (read-char port))
  148. (thunk (hash-table-ref/default parts-table n #f)))
  149. (case c
  150. ((#\=)
  151. (if thunk
  152. (error "Double declaration of part " n))
  153. (let* ((cell (list #f))
  154. (thunk (lambda () (car cell))))
  155. (hash-table-set! parts-table n thunk)
  156. (let ((obj (read port)))
  157. (set-car! cell obj)
  158. obj)))
  159. ((#\#)
  160. (or thunk
  161. (error "Use of undeclared part " n)))
  162. (else
  163. (error "Malformed shared part specifier")))))
  164. (with-fluid* %read-hash-procedures (fluid-ref %read-hash-procedures)
  165. (lambda ()
  166. (for-each (lambda (digit)
  167. (read-hash-extend digit read-hash))
  168. '(#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9))
  169. (let ((result (read port)))
  170. (if (< 0 (hash-table-size parts-table))
  171. (patch! result))
  172. result)))))
  173. (define (hole? x) (procedure? x))
  174. (define (fill-hole x) (if (hole? x) (fill-hole (x)) x))
  175. (define (patch! x)
  176. (cond
  177. ((pair? x)
  178. (if (hole? (car x)) (set-car! x (fill-hole (car x))) (patch! (car x)))
  179. (if (hole? (cdr x)) (set-cdr! x (fill-hole (cdr x))) (patch! (cdr x))))
  180. ((vector? x)
  181. (do ((i (- (vector-length x) 1) (- i 1)))
  182. ((< i 0))
  183. (let ((elt (vector-ref x i)))
  184. (if (hole? elt)
  185. (vector-set! x i (fill-hole elt))
  186. (patch! elt)))))))