srfi-38.scm 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  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. ;; A printer that shows all sharing of substructures. Uses the Common
  31. ;; Lisp print-circle notation: #n# refers to a previous substructure
  32. ;; labeled with #n=. Takes O(n^2) time.
  33. ;; Code attributed to Al Petrofsky, modified by Ray Dillinger.
  34. ;; Modified in 2010 by Andreas Rottmann to use SRFI 69 hashtables,
  35. ;; making the time O(n), and adding some of Guile's data types to the
  36. ;; `interesting' objects.
  37. (define* (write-with-shared-structure obj
  38. #:optional
  39. (outport (current-output-port))
  40. (optarg #f))
  41. ;; We only track duplicates of pairs, vectors, strings, bytevectors,
  42. ;; structs (which subsume R6RS and SRFI-9 records), ports and (native)
  43. ;; hash-tables. We ignore zero-length vectors and strings because
  44. ;; r5rs doesn't guarantee that eq? treats them sanely (and they aren't
  45. ;; very interesting anyway).
  46. (define (interesting? obj)
  47. (or (pair? obj)
  48. (and (vector? obj) (not (zero? (vector-length obj))))
  49. (and (string? obj) (not (zero? (string-length obj))))
  50. (bytevector? obj)
  51. (struct? obj)
  52. (port? obj)
  53. (hash-table? obj)))
  54. ;; (write-obj OBJ STATE):
  55. ;;
  56. ;; STATE is a hashtable which has an entry for each interesting part
  57. ;; of OBJ. The associated value will be:
  58. ;;
  59. ;; -- a number if the part has been given one,
  60. ;; -- #t if the part will need to be assigned a number but has not been yet,
  61. ;; -- #f if the part will not need a number.
  62. ;; The entry `counter' in STATE should be the most recently
  63. ;; assigned number.
  64. ;;
  65. ;; Mutates STATE for any parts that had numbers assigned.
  66. (define (write-obj obj state)
  67. (define (write-interesting)
  68. (cond ((pair? obj)
  69. (display "(" outport)
  70. (write-obj (car obj) state)
  71. (let write-cdr ((obj (cdr obj)))
  72. (cond ((and (pair? obj) (not (hash-table-ref state obj)))
  73. (display " " outport)
  74. (write-obj (car obj) state)
  75. (write-cdr (cdr obj)))
  76. ((null? obj)
  77. (display ")" outport))
  78. (else
  79. (display " . " outport)
  80. (write-obj obj state)
  81. (display ")" outport)))))
  82. ((vector? obj)
  83. (display "#(" outport)
  84. (let ((len (vector-length obj)))
  85. (write-obj (vector-ref obj 0) state)
  86. (let write-vec ((i 1))
  87. (cond ((= i len) (display ")" outport))
  88. (else (display " " outport)
  89. (write-obj (vector-ref obj i) state)
  90. (write-vec (+ i 1)))))))
  91. ;; else it's a string
  92. (else (write obj outport))))
  93. (cond ((interesting? obj)
  94. (let ((val (hash-table-ref state obj)))
  95. (cond ((not val) (write-interesting))
  96. ((number? val)
  97. (begin (display "#" outport)
  98. (write val outport)
  99. (display "#" outport)))
  100. (else
  101. (let ((n (+ 1 (hash-table-ref state 'counter))))
  102. (display "#" outport)
  103. (write n outport)
  104. (display "=" outport)
  105. (hash-table-set! state 'counter n)
  106. (hash-table-set! state obj n)
  107. (write-interesting))))))
  108. (else
  109. (write obj outport))))
  110. ;; Scan computes the initial value of the hash table, which maps each
  111. ;; interesting part of the object to #t if it occurs multiple times,
  112. ;; #f if only once.
  113. (define (scan obj state)
  114. (cond ((not (interesting? obj)))
  115. ((hash-table-exists? state obj)
  116. (hash-table-set! state obj #t))
  117. (else
  118. (hash-table-set! state obj #f)
  119. (cond ((pair? obj)
  120. (scan (car obj) state)
  121. (scan (cdr obj) state))
  122. ((vector? obj)
  123. (let ((len (vector-length obj)))
  124. (do ((i 0 (+ 1 i)))
  125. ((= i len))
  126. (scan (vector-ref obj i) state))))))))
  127. (let ((state (make-hash-table eq?)))
  128. (scan obj state)
  129. (hash-table-set! state 'counter 0)
  130. (write-obj obj state)))
  131. ;; A reader that understands the output of the above writer. This has
  132. ;; been written by Andreas Rottmann to re-use Guile's built-in reader,
  133. ;; with inspiration from Alex Shinn's public-domain implementation of
  134. ;; `read-with-shared-structure' found in Chicken's SRFI 38 egg.
  135. (define* (read-with-shared-structure #:optional (port (current-input-port)))
  136. (let ((parts-table (make-hash-table eqv?)))
  137. ;; reads chars that match PRED and returns them as a string.
  138. (define (read-some-chars pred initial)
  139. (let iter ((chars initial))
  140. (let ((c (peek-char port)))
  141. (if (or (eof-object? c) (not (pred c)))
  142. (list->string (reverse chars))
  143. (iter (cons (read-char port) chars))))))
  144. (define (read-hash c port)
  145. (let* ((n (string->number (read-some-chars char-numeric? (list c))))
  146. (c (read-char port))
  147. (thunk (hash-table-ref/default parts-table n #f)))
  148. (case c
  149. ((#\=)
  150. (if thunk
  151. (error "Double declaration of part " n))
  152. (let* ((cell (list #f))
  153. (thunk (lambda () (car cell))))
  154. (hash-table-set! parts-table n thunk)
  155. (let ((obj (read port)))
  156. (set-car! cell obj)
  157. obj)))
  158. ((#\#)
  159. (or thunk
  160. (error "Use of undeclared part " n)))
  161. (else
  162. (error "Malformed shared part specifier")))))
  163. (with-fluid* %read-hash-procedures (fluid-ref %read-hash-procedures)
  164. (lambda ()
  165. (for-each (lambda (digit)
  166. (read-hash-extend digit read-hash))
  167. '(#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9))
  168. (let ((result (read port)))
  169. (if (< 0 (hash-table-size parts-table))
  170. (patch! result))
  171. result)))))
  172. (define (hole? x) (procedure? x))
  173. (define (fill-hole x) (if (hole? x) (fill-hole (x)) x))
  174. (define (patch! x)
  175. (cond
  176. ((pair? x)
  177. (if (hole? (car x)) (set-car! x (fill-hole (car x))) (patch! (car x)))
  178. (if (hole? (cdr x)) (set-cdr! x (fill-hole (cdr x))) (patch! (cdr x))))
  179. ((vector? x)
  180. (do ((i (- (vector-length x) 1) (- i 1)))
  181. ((< i 0))
  182. (let ((elt (vector-ref x i)))
  183. (if (hole? elt)
  184. (vector-set! x i (fill-hole elt))
  185. (patch! elt)))))))