Spaces:
Sleeping
Sleeping
;; Copyright (C) 2010 Free Software Foundation, Inc. | |
;; Copyright (C) Ray Dillinger 2003. All Rights Reserved. | |
;; | |
;; Contains code based upon Alex Shinn's public-domain implementation of | |
;; `read-with-shared-structure' found in Chicken's SRFI 38 egg. | |
;; Permission is hereby granted, free of charge, to any person obtaining | |
;; a copy of this software and associated documentation files (the | |
;; "Software"), to deal in the Software without restriction, including | |
;; without limitation the rights to use, copy, modify, merge, publish, | |
;; distribute, sublicense, and/or sell copies of the Software, and to | |
;; permit persons to whom the Software is furnished to do so, subject to | |
;; the following conditions: | |
;; The above copyright notice and this permission notice shall be | |
;; included in all copies or substantial portions of the Software. | |
;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS | |
;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN | |
;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | |
;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
;; SOFTWARE. | |
(define-module (srfi srfi-38) | |
#:export (write-with-shared-structure | |
read-with-shared-structure) | |
#:use-module (rnrs bytevectors) | |
#:use-module (srfi srfi-8) | |
#:use-module (srfi srfi-69) | |
#:use-module (system vm trap-state)) | |
(cond-expand-provide (current-module) '(srfi-38)) | |
;; A printer that shows all sharing of substructures. Uses the Common | |
;; Lisp print-circle notation: #n# refers to a previous substructure | |
;; labeled with #n=. Takes O(n^2) time. | |
;; Code attributed to Al Petrofsky, modified by Ray Dillinger. | |
;; Modified in 2010 by Andreas Rottmann to use SRFI 69 hashtables, | |
;; making the time O(n), and adding some of Guile's data types to the | |
;; `interesting' objects. | |
(define* (write-with-shared-structure obj | |
#:optional | |
(outport (current-output-port)) | |
(optarg #f)) | |
;; We only track duplicates of pairs, vectors, strings, bytevectors, | |
;; structs (which subsume R6RS and SRFI-9 records), ports and (native) | |
;; hash-tables. We ignore zero-length vectors and strings because | |
;; r5rs doesn't guarantee that eq? treats them sanely (and they aren't | |
;; very interesting anyway). | |
(define (interesting? obj) | |
(or (pair? obj) | |
(and (vector? obj) (not (zero? (vector-length obj)))) | |
(and (string? obj) (not (zero? (string-length obj)))) | |
(bytevector? obj) | |
(struct? obj) | |
(port? obj) | |
(hash-table? obj))) | |
;; (write-obj OBJ STATE): | |
;; | |
;; STATE is a hashtable which has an entry for each interesting part | |
;; of OBJ. The associated value will be: | |
;; | |
;; -- a number if the part has been given one, | |
;; -- #t if the part will need to be assigned a number but has not been yet, | |
;; -- #f if the part will not need a number. | |
;; The entry `counter' in STATE should be the most recently | |
;; assigned number. | |
;; | |
;; Mutates STATE for any parts that had numbers assigned. | |
(define (write-obj obj state) | |
(define (write-interesting) | |
(cond ((pair? obj) | |
(display "(" outport) | |
(write-obj (car obj) state) | |
(let write-cdr ((obj (cdr obj))) | |
(cond ((and (pair? obj) (not (hash-table-ref state obj))) | |
(display " " outport) | |
(write-obj (car obj) state) | |
(write-cdr (cdr obj))) | |
((null? obj) | |
(display ")" outport)) | |
(else | |
(display " . " outport) | |
(write-obj obj state) | |
(display ")" outport))))) | |
((vector? obj) | |
(display "#(" outport) | |
(let ((len (vector-length obj))) | |
(write-obj (vector-ref obj 0) state) | |
(let write-vec ((i 1)) | |
(cond ((= i len) (display ")" outport)) | |
(else (display " " outport) | |
(write-obj (vector-ref obj i) state) | |
(write-vec (+ i 1))))))) | |
;; else it's a string | |
(else (write obj outport)))) | |
(cond ((interesting? obj) | |
(let ((val (hash-table-ref state obj))) | |
(cond ((not val) (write-interesting)) | |
((number? val) | |
(begin (display "#" outport) | |
(write val outport) | |
(display "#" outport))) | |
(else | |
(let ((n (+ 1 (hash-table-ref state 'counter)))) | |
(display "#" outport) | |
(write n outport) | |
(display "=" outport) | |
(hash-table-set! state 'counter n) | |
(hash-table-set! state obj n) | |
(write-interesting)))))) | |
(else | |
(write obj outport)))) | |
;; Scan computes the initial value of the hash table, which maps each | |
;; interesting part of the object to #t if it occurs multiple times, | |
;; #f if only once. | |
(define (scan obj state) | |
(cond ((not (interesting? obj))) | |
((hash-table-exists? state obj) | |
(hash-table-set! state obj #t)) | |
(else | |
(hash-table-set! state obj #f) | |
(cond ((pair? obj) | |
(scan (car obj) state) | |
(scan (cdr obj) state)) | |
((vector? obj) | |
(let ((len (vector-length obj))) | |
(do ((i 0 (+ 1 i))) | |
((= i len)) | |
(scan (vector-ref obj i) state)))))))) | |
(let ((state (make-hash-table eq?))) | |
(scan obj state) | |
(hash-table-set! state 'counter 0) | |
(write-obj obj state))) | |
;; A reader that understands the output of the above writer. This has | |
;; been written by Andreas Rottmann to re-use Guile's built-in reader, | |
;; with inspiration from Alex Shinn's public-domain implementation of | |
;; `read-with-shared-structure' found in Chicken's SRFI 38 egg. | |
(define* (read-with-shared-structure #:optional (port (current-input-port))) | |
(let ((parts-table (make-hash-table eqv?))) | |
;; reads chars that match PRED and returns them as a string. | |
(define (read-some-chars pred initial) | |
(let iter ((chars initial)) | |
(let ((c (peek-char port))) | |
(if (or (eof-object? c) (not (pred c))) | |
(list->string (reverse chars)) | |
(iter (cons (read-char port) chars)))))) | |
(define (read-hash c port) | |
(let* ((n (string->number (read-some-chars char-numeric? (list c)))) | |
(c (read-char port)) | |
(thunk (hash-table-ref/default parts-table n #f))) | |
(case c | |
((#\=) | |
(if thunk | |
(error "Double declaration of part " n)) | |
(let* ((cell (list #f)) | |
(thunk (lambda () (car cell)))) | |
(hash-table-set! parts-table n thunk) | |
(let ((obj (read port))) | |
(set-car! cell obj) | |
obj))) | |
((#\#) | |
(or thunk | |
(error "Use of undeclared part " n))) | |
(else | |
(error "Malformed shared part specifier"))))) | |
(with-fluid* %read-hash-procedures (fluid-ref %read-hash-procedures) | |
(lambda () | |
(for-each (lambda (digit) | |
(read-hash-extend digit read-hash)) | |
'(#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9)) | |
(let ((result (read port))) | |
(if (< 0 (hash-table-size parts-table)) | |
(patch! result)) | |
result))))) | |
(define (hole? x) (procedure? x)) | |
(define (fill-hole x) (if (hole? x) (fill-hole (x)) x)) | |
(define (patch! x) | |
(cond | |
((pair? x) | |
(if (hole? (car x)) (set-car! x (fill-hole (car x))) (patch! (car x))) | |
(if (hole? (cdr x)) (set-cdr! x (fill-hole (cdr x))) (patch! (cdr x)))) | |
((vector? x) | |
(do ((i (- (vector-length x) 1) (- i 1))) | |
((< i 0)) | |
(let ((elt (vector-ref x i))) | |
(if (hole? elt) | |
(vector-set! x i (fill-hole elt)) | |
(patch! elt))))))) | |