change id-tables so iterators not invalidated by mutation of existing keys
id-table fixes test for id-table
This commit is contained in:
parent
abb616e6d6
commit
510d3937a7
|
@ -5,7 +5,146 @@
|
||||||
|
|
||||||
;; No-contract version.
|
;; No-contract version.
|
||||||
|
|
||||||
(define-struct id-table-iter (a b))
|
(define-struct id-table (hash phase))
|
||||||
|
;; where hash maps symbol => (listof (cons identifier value))
|
||||||
|
;; phase is a phase-level (integer or #f)
|
||||||
|
|
||||||
|
(define (make-id-table/constructor who init-dict phase make identifier->symbol identifier=?)
|
||||||
|
(let ([t (make (make-hasheq) phase)])
|
||||||
|
(for ([(k v) (in-dict init-dict)])
|
||||||
|
(unless (identifier? k)
|
||||||
|
(raise-type-error who "dictionary with identifier keys" init-dict))
|
||||||
|
(id-table-set! who t k v identifier->symbol identifier=?))
|
||||||
|
t))
|
||||||
|
|
||||||
|
(define (make-immutable-id-table/constructor who init-dict phase make identifier->symbol identifier=?)
|
||||||
|
(for/fold ([t (make '#hasheq() phase)])
|
||||||
|
([(k v) (in-dict init-dict)])
|
||||||
|
(unless (identifier? k)
|
||||||
|
(raise-type-error who "dictionary with identifier keys" init-dict))
|
||||||
|
(id-table-set/constructor who t k v make identifier->symbol identifier=?)))
|
||||||
|
|
||||||
|
(define (id-table-ref who d id default identifier->symbol identifier=?)
|
||||||
|
(let ([phase (id-table-phase d)])
|
||||||
|
(let ([i (for/first ([i (in-list (hash-ref (id-table-hash d)
|
||||||
|
(identifier->symbol id phase)
|
||||||
|
null))]
|
||||||
|
#:when (identifier=? (car i) id phase))
|
||||||
|
i)])
|
||||||
|
(if i
|
||||||
|
(cdr i)
|
||||||
|
(cond [(eq? default not-given)
|
||||||
|
(error who "no mapping for ~e" id)]
|
||||||
|
[(procedure? default) (default)]
|
||||||
|
[else default])))))
|
||||||
|
|
||||||
|
(define (id-table-set! who d id v identifier->symbol identifier=?)
|
||||||
|
(let* ([phase (id-table-phase d)]
|
||||||
|
[sym (identifier->symbol id phase)]
|
||||||
|
[l (hash-ref (id-table-hash d) sym null)]
|
||||||
|
[new-l (alist-set identifier=? phase l id v)])
|
||||||
|
(hash-set! (id-table-hash d) sym new-l)))
|
||||||
|
|
||||||
|
(define (id-table-remove! who d id identifier->symbol identifier=?)
|
||||||
|
(let* ([phase (id-table-phase d)]
|
||||||
|
[sym (identifier->symbol id phase)]
|
||||||
|
[l (hash-ref (id-table-hash d) sym null)]
|
||||||
|
[newl (alist-remove identifier=? phase l id)])
|
||||||
|
(if (pair? newl)
|
||||||
|
(hash-set! (id-table-hash d) sym newl)
|
||||||
|
(hash-remove! (id-table-hash d) sym))))
|
||||||
|
|
||||||
|
(define (id-table-set/constructor who d id v constructor identifier->symbol identifier=?)
|
||||||
|
(let* ([phase (id-table-phase d)]
|
||||||
|
[sym (identifier->symbol id phase)]
|
||||||
|
[l (hash-ref (id-table-hash d) sym null)]
|
||||||
|
[new-l (alist-set identifier=? phase l id v)])
|
||||||
|
(constructor (hash-set (id-table-hash d) sym new-l)
|
||||||
|
phase)))
|
||||||
|
|
||||||
|
(define (id-table-remove/constructor who d id constructor identifier->symbol identifier=?)
|
||||||
|
(let* ([phase (id-table-phase d)]
|
||||||
|
[sym (identifier->symbol id phase)]
|
||||||
|
[l (hash-ref (id-table-hash d) sym null)]
|
||||||
|
[newl (alist-remove identifier=? phase l id)])
|
||||||
|
(constructor
|
||||||
|
(if (pair? newl)
|
||||||
|
(hash-set (id-table-hash d) sym newl)
|
||||||
|
(hash-remove (id-table-hash d) sym))
|
||||||
|
phase)))
|
||||||
|
|
||||||
|
(define (id-table-count d)
|
||||||
|
(apply + (hash-map (id-table-hash d) (lambda (k v) (length v)))))
|
||||||
|
|
||||||
|
(define (id-table-for-each d p)
|
||||||
|
(define (pp i) (p (car i) (cdr i)))
|
||||||
|
(hash-for-each (id-table-hash d) (lambda (k v) (for-each pp v))))
|
||||||
|
|
||||||
|
(define (id-table-map d f)
|
||||||
|
(define (fp i) (f (car i) (cdr i)))
|
||||||
|
(apply append (hash-map (id-table-hash d) (lambda (k v) (map fp v)))))
|
||||||
|
|
||||||
|
|
||||||
|
(define-struct id-table-iter (d a br b))
|
||||||
|
;; where d is an id-table
|
||||||
|
;; a is hash iter
|
||||||
|
;; br is non-empty-alist "root"
|
||||||
|
;; b is non-empty-alist current position, b = (list-tail br N) for some N
|
||||||
|
|
||||||
|
#|
|
||||||
|
For mutable table, the alist iter can be invalidated because we replace entire
|
||||||
|
alist on update. So, we store alist root and if root has been replaced, we
|
||||||
|
try to re-find the previous position in the new alist (see rebase-iter).
|
||||||
|
Not an issue for immutable tables.
|
||||||
|
|
||||||
|
Notes (FIXME?):
|
||||||
|
- we don't (can't?) check that hash iter is okay (would require hash revision?)
|
||||||
|
so internal errors raised by hash-iterate-{next,value} can slip out
|
||||||
|
|#
|
||||||
|
|
||||||
|
(define (id-table-iterate-first d)
|
||||||
|
(let* ([h (id-table-hash d)]
|
||||||
|
[a (hash-iterate-first h)])
|
||||||
|
(and a
|
||||||
|
(let ([b (hash-iterate-value h a)])
|
||||||
|
(make-id-table-iter d a b b)))))
|
||||||
|
|
||||||
|
(define (id-table-iterate-next who d pos)
|
||||||
|
(let-values ([(h a br b) (rebase-iter who d pos)])
|
||||||
|
(let ([b2 (cdr b)])
|
||||||
|
(if (pair? b2)
|
||||||
|
(make-id-table-iter d a br b2)
|
||||||
|
(let ([a2 (hash-iterate-next h a)])
|
||||||
|
(and a2
|
||||||
|
(let ([b2 (hash-iterate-value h a2)])
|
||||||
|
(make-id-table-iter d a2 b2 b2))))))))
|
||||||
|
|
||||||
|
(define (id-table-iterate-key who d pos)
|
||||||
|
(let-values ([(h a br b) (rebase-iter who d pos)])
|
||||||
|
(caar b)))
|
||||||
|
|
||||||
|
(define (id-table-iterate-value who d pos)
|
||||||
|
(let-values ([(h a br b) (rebase-iter who d pos)])
|
||||||
|
(cdar b)))
|
||||||
|
|
||||||
|
(define (rebase-iter who d pos)
|
||||||
|
(unless (eq? d (id-table-iter-d pos))
|
||||||
|
(error who "invalid iteration position for identifier table"))
|
||||||
|
(let* ([h (id-table-hash d)]
|
||||||
|
[a (id-table-iter-a pos)]
|
||||||
|
[br (id-table-iter-br pos)]
|
||||||
|
[b (id-table-iter-b pos)]
|
||||||
|
[v (hash-iterate-value h a)]) ;; FIXME: may expose internal error
|
||||||
|
(if (eq? br v)
|
||||||
|
(values h a br b)
|
||||||
|
;; hash entry has changed from br to v, so find (caar b) in v
|
||||||
|
(let ([id (caar b)])
|
||||||
|
(let loop ([v* v])
|
||||||
|
(cond [(null? v*)
|
||||||
|
(error who "invalid iteration position for identifier table")]
|
||||||
|
[(eq? (caar v*) id) ;; relies on id staying same; see alist-set
|
||||||
|
(values h a v v*)]
|
||||||
|
[else (loop (cdr v*))]))))))
|
||||||
|
|
||||||
;; ========
|
;; ========
|
||||||
|
|
||||||
|
@ -20,7 +159,9 @@
|
||||||
[(identifier=? (caar l) id phase)
|
[(identifier=? (caar l) id phase)
|
||||||
(if (eq? v (cdar l)) ;; idempotent; just leave it alone
|
(if (eq? v (cdar l)) ;; idempotent; just leave it alone
|
||||||
l
|
l
|
||||||
(cons (cons id v) (cdr l)))]
|
;; Note: use (caar l) instead of id so that rebase-iter
|
||||||
|
;; above can find entry by stxobj identity.
|
||||||
|
(cons (cons (caar l) v) (cdr l)))]
|
||||||
[else
|
[else
|
||||||
(let ([rest* (loop (cdr l))])
|
(let ([rest* (loop (cdr l))])
|
||||||
(if (eq? (cdr l) rest*)
|
(if (eq? (cdr l) rest*)
|
||||||
|
@ -68,8 +209,6 @@
|
||||||
(format-id #'idtbl "immutable-~a?" (syntax-e #'idtbl))])
|
(format-id #'idtbl "immutable-~a?" (syntax-e #'idtbl))])
|
||||||
(define (s x) (format-id #'idtbl "~a~a" (syntax-e #'idtbl) x))
|
(define (s x) (format-id #'idtbl "~a~a" (syntax-e #'idtbl) x))
|
||||||
(with-syntax ([idtbl? (s '?)]
|
(with-syntax ([idtbl? (s '?)]
|
||||||
[idtbl-hash (s '-hash)]
|
|
||||||
[idtbl-phase (s '-phase)]
|
|
||||||
[idtbl-ref (s '-ref)]
|
[idtbl-ref (s '-ref)]
|
||||||
[idtbl-set! (s '-set!)]
|
[idtbl-set! (s '-set!)]
|
||||||
[idtbl-set (s '-set)]
|
[idtbl-set (s '-set)]
|
||||||
|
@ -92,123 +231,42 @@
|
||||||
|
|
||||||
(define (make-idtbl [init-dict null]
|
(define (make-idtbl [init-dict null]
|
||||||
#:phase [phase (syntax-local-phase-level)])
|
#:phase [phase (syntax-local-phase-level)])
|
||||||
(let ([t (mutable-idtbl (make-hasheq) phase)])
|
(make-id-table/constructor 'make-idtbl init-dict phase mutable-idtbl
|
||||||
(for ([(k v) (in-dict init-dict)])
|
identifier->symbol identifier=?))
|
||||||
(unless (identifier? k)
|
|
||||||
(raise-type-error 'make-idtbl
|
|
||||||
"dictionary with identifier keys" init-dict))
|
|
||||||
(idtbl-set! t k v))
|
|
||||||
t))
|
|
||||||
(define (make-immutable-idtbl [init-dict null]
|
(define (make-immutable-idtbl [init-dict null]
|
||||||
#:phase [phase (syntax-local-phase-level)])
|
#:phase [phase (syntax-local-phase-level)])
|
||||||
(for/fold ([t (immutable-idtbl '#hasheq() phase)])
|
(make-immutable-id-table/constructor 'make-immutable-idtbl init-dict phase immutable-idtbl
|
||||||
([(k v) (in-dict init-dict)])
|
identifier->symbol identifier=?))
|
||||||
(unless (identifier? k)
|
|
||||||
(raise-type-error 'make-immutable-idtbl
|
|
||||||
"dictionary with identifier keys" init-dict))
|
|
||||||
(idtbl-set t k v)))
|
|
||||||
|
|
||||||
(define (idtbl-ref d id [default not-given])
|
(define (idtbl-ref d id [default not-given])
|
||||||
(let ([phase (idtbl-phase d)])
|
(id-table-ref 'idtbl-ref d id default identifier->symbol identifier=?))
|
||||||
(let ([i (ormap (lambda (i) (and (identifier=? (car i) id phase) i))
|
|
||||||
(hash-ref (idtbl-hash d)
|
|
||||||
(identifier->symbol id phase)
|
|
||||||
null))])
|
|
||||||
(if i
|
|
||||||
(cdr i)
|
|
||||||
(cond [(eq? default not-given)
|
|
||||||
(error 'idtbl-ref "no mapping for ~e" id)]
|
|
||||||
[(procedure? default) (default)]
|
|
||||||
[else default])))))
|
|
||||||
|
|
||||||
(define (idtbl-set! d id v)
|
(define (idtbl-set! d id v)
|
||||||
(let* ([phase (idtbl-phase d)]
|
(id-table-set! 'idtbl-set! d id v identifier->symbol identifier=?))
|
||||||
[sym (identifier->symbol id phase)]
|
|
||||||
[l (hash-ref (idtbl-hash d) sym null)])
|
|
||||||
(hash-set! (idtbl-hash d)
|
|
||||||
sym
|
|
||||||
(alist-set identifier=? phase l id v))))
|
|
||||||
|
|
||||||
(define (idtbl-set/constructor d id v constructor)
|
(define (idtbl-set/constructor d id v constructor)
|
||||||
(let* ([phase (idtbl-phase d)]
|
(id-table-set/constructor 'idtbl-set d id v constructor identifier->symbol identifier=?))
|
||||||
[sym (identifier->symbol id phase)]
|
|
||||||
[l (hash-ref (idtbl-hash d) sym null)])
|
|
||||||
(constructor
|
|
||||||
(hash-set (idtbl-hash d)
|
|
||||||
sym
|
|
||||||
(alist-set identifier=? phase l id v))
|
|
||||||
phase)))
|
|
||||||
(define (idtbl-set d id v)
|
(define (idtbl-set d id v)
|
||||||
(idtbl-set/constructor d id v immutable-idtbl))
|
(idtbl-set/constructor d id v immutable-idtbl))
|
||||||
|
|
||||||
(define (idtbl-remove! d id)
|
(define (idtbl-remove! d id)
|
||||||
(let* ([phase (idtbl-phase d)]
|
(id-table-remove! 'idtbl-remove! d id identifier->symbol identifier=?))
|
||||||
[sym (identifier->symbol id phase)]
|
|
||||||
[l (hash-ref (idtbl-hash d) sym null)]
|
|
||||||
[newl (alist-remove identifier=? phase l id)])
|
|
||||||
(if (pair? newl)
|
|
||||||
(hash-set! (idtbl-hash d) sym newl)
|
|
||||||
(hash-remove! (idtbl-hash d) sym))))
|
|
||||||
|
|
||||||
(define (idtbl-remove/constructor d id constructor)
|
(define (idtbl-remove/constructor d id constructor)
|
||||||
(let* ([phase (idtbl-phase d)]
|
(id-table-remove/constructor 'idtbl-remove d id constructor identifier->symbol identifier=?))
|
||||||
[sym (identifier->symbol id phase)]
|
|
||||||
[l (hash-ref (idtbl-hash d) sym null)]
|
|
||||||
[newl (alist-remove identifier=? phase l id)])
|
|
||||||
(constructor
|
|
||||||
(if (pair? newl)
|
|
||||||
(hash-set (idtbl-hash d) sym newl)
|
|
||||||
(hash-remove (idtbl-hash d) sym))
|
|
||||||
phase)))
|
|
||||||
(define (idtbl-remove d id)
|
(define (idtbl-remove d id)
|
||||||
(idtbl-remove/constructor d id immutable-idtbl))
|
(idtbl-remove/constructor d id immutable-idtbl))
|
||||||
|
|
||||||
(define (idtbl-count d)
|
(define (idtbl-count d)
|
||||||
(apply + (hash-map (idtbl-hash d) (lambda (k v) (length v)))))
|
(id-table-count d))
|
||||||
|
|
||||||
(define (idtbl-for-each d p)
|
(define (idtbl-for-each d p)
|
||||||
(define (pp i) (p (car i) (cdr i)))
|
(id-table-for-each d p))
|
||||||
(hash-for-each (idtbl-hash d)
|
|
||||||
(lambda (k v) (for-each pp v))))
|
|
||||||
|
|
||||||
(define (idtbl-map d f)
|
(define (idtbl-map d f)
|
||||||
(define (fp i) (f (car i) (cdr i)))
|
(id-table-map d f))
|
||||||
(apply append
|
|
||||||
(hash-map (idtbl-hash d)
|
|
||||||
(lambda (k v) (map fp v)))))
|
|
||||||
|
|
||||||
(define (idtbl-iterate-first d)
|
(define (idtbl-iterate-first d)
|
||||||
(let ([h (idtbl-hash d)])
|
(id-table-iterate-first d))
|
||||||
(let ([a (dict-iterate-first h)])
|
|
||||||
(and a
|
|
||||||
(let ([b (dict-iterate-first (dict-iterate-value h a))])
|
|
||||||
(and b (make-id-table-iter a b)))))))
|
|
||||||
|
|
||||||
(define (idtbl-iterate-next d pos)
|
(define (idtbl-iterate-next d pos)
|
||||||
(let ([h (idtbl-hash d)]
|
(id-table-iterate-next 'idtbl-iterate-next d pos))
|
||||||
[a (id-table-iter-a pos)]
|
|
||||||
[b (id-table-iter-b pos)])
|
|
||||||
(let ([v (dict-iterate-value h a)])
|
|
||||||
(let ([b2 (dict-iterate-next v b)])
|
|
||||||
(if b2
|
|
||||||
(make-id-table-iter a b2)
|
|
||||||
(let ([a2 (dict-iterate-next h a)])
|
|
||||||
(and a2
|
|
||||||
(let ([b2 (dict-iterate-first
|
|
||||||
(dict-iterate-value h a2))])
|
|
||||||
(and b2 (make-id-table-iter a2 b2))))))))))
|
|
||||||
|
|
||||||
(define (idtbl-iterate-key d pos)
|
(define (idtbl-iterate-key d pos)
|
||||||
(let ([h (idtbl-hash d)]
|
(id-table-iterate-key 'idtbl-iterate-key d pos))
|
||||||
[a (id-table-iter-a pos)]
|
|
||||||
[b (id-table-iter-b pos)])
|
|
||||||
(dict-iterate-key (dict-iterate-value h a) b)))
|
|
||||||
|
|
||||||
(define (idtbl-iterate-value d pos)
|
(define (idtbl-iterate-value d pos)
|
||||||
(let ([h (idtbl-hash d)]
|
(id-table-iterate-value 'idtbl-iterate-value d pos))
|
||||||
[a (id-table-iter-a pos)]
|
|
||||||
[b (id-table-iter-b pos)])
|
|
||||||
(dict-iterate-value (dict-iterate-value h a) b)))
|
|
||||||
|
|
||||||
(define idtbl-mutable-methods
|
(define idtbl-mutable-methods
|
||||||
(vector-immutable idtbl-ref
|
(vector-immutable idtbl-ref
|
||||||
|
@ -216,7 +274,7 @@
|
||||||
#f
|
#f
|
||||||
idtbl-remove!
|
idtbl-remove!
|
||||||
#f
|
#f
|
||||||
idtbl-count
|
id-table-count
|
||||||
idtbl-iterate-first
|
idtbl-iterate-first
|
||||||
idtbl-iterate-next
|
idtbl-iterate-next
|
||||||
idtbl-iterate-key
|
idtbl-iterate-key
|
||||||
|
@ -228,16 +286,15 @@
|
||||||
idtbl-set
|
idtbl-set
|
||||||
#f
|
#f
|
||||||
idtbl-remove
|
idtbl-remove
|
||||||
idtbl-count
|
id-table-count
|
||||||
idtbl-iterate-first
|
idtbl-iterate-first
|
||||||
idtbl-iterate-next
|
idtbl-iterate-next
|
||||||
idtbl-iterate-key
|
idtbl-iterate-key
|
||||||
idtbl-iterate-value))
|
idtbl-iterate-value))
|
||||||
|
|
||||||
(struct idtbl (hash phase))
|
(struct idtbl id-table ())
|
||||||
(struct mutable-idtbl idtbl ()
|
(struct mutable-idtbl idtbl ()
|
||||||
#:property prop:dict idtbl-mutable-methods)
|
#:property prop:dict idtbl-mutable-methods)
|
||||||
|
|
||||||
(struct immutable-idtbl idtbl ()
|
(struct immutable-idtbl idtbl ()
|
||||||
#:property prop:dict idtbl-immutable-methods)
|
#:property prop:dict idtbl-immutable-methods)
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
(load-relative "loadtest.rktl")
|
(load-relative "loadtest.rktl")
|
||||||
|
|
||||||
(require syntax/id-table
|
(require syntax/id-table
|
||||||
scheme/dict)
|
racket/dict)
|
||||||
|
|
||||||
(Section 'id-table)
|
(Section 'id-table)
|
||||||
|
|
||||||
|
@ -11,6 +11,38 @@
|
||||||
(test #t mutable-bound-id-table? (make-bound-id-table))
|
(test #t mutable-bound-id-table? (make-bound-id-table))
|
||||||
(test #t immutable-bound-id-table? (make-immutable-bound-id-table))
|
(test #t immutable-bound-id-table? (make-immutable-bound-id-table))
|
||||||
|
|
||||||
|
(let ()
|
||||||
|
(define a #'a)
|
||||||
|
(define b #'b)
|
||||||
|
(define b2 (let ([b 0]) #'b))
|
||||||
|
(define b3 ((make-syntax-introducer) #'b)) ;; free=? to b
|
||||||
|
(define alist (list (cons a 1) (cons b 2) (cons b2 3) (cons b3 4)))
|
||||||
|
(test 4 bound-id-table-count (make-bound-id-table alist))
|
||||||
|
(test 4 bound-id-table-count (make-immutable-bound-id-table alist))
|
||||||
|
(test 3 free-id-table-count (make-free-id-table alist))
|
||||||
|
(test 3 free-id-table-count (make-immutable-free-id-table alist))
|
||||||
|
|
||||||
|
(let ()
|
||||||
|
;; Test in-dict, iteration methods for immutable id-tables
|
||||||
|
(define d1 (make-immutable-bound-id-table alist))
|
||||||
|
(test (+ 1 2 3 4) (lambda () (for/sum ([(id v) (in-dict d1)]) v)))
|
||||||
|
(define d2 (for/fold ([d (make-immutable-bound-id-table)])
|
||||||
|
([(id v) (in-dict d1)])
|
||||||
|
(dict-set d id (add1 v))))
|
||||||
|
(test 2 bound-id-table-ref d2 a)
|
||||||
|
(test 3 bound-id-table-ref d2 b)
|
||||||
|
(test 4 bound-id-table-ref d2 b2)
|
||||||
|
(test 5 bound-id-table-ref d2 b3))
|
||||||
|
|
||||||
|
(let ()
|
||||||
|
;; Test in-dict, iteration methods for mutable id-tables
|
||||||
|
;; In particular, test that -set! of *existing* key does not disrupt iter.
|
||||||
|
(define d1 (make-bound-id-table alist))
|
||||||
|
(test (+ 1 2 3 4) (lambda () (for/sum ([(id v) (in-dict d1)]) v)))
|
||||||
|
(for ([(id v) (in-dict d1)])
|
||||||
|
(bound-id-table-set! d1 id (add1 v)))
|
||||||
|
(test (+ 2 3 4 5) (lambda () (for/sum ([(id v) (in-dict d1)]) v)))))
|
||||||
|
|
||||||
(let ()
|
(let ()
|
||||||
;; contains-same? : (listof x) (listof x) -> boolean
|
;; contains-same? : (listof x) (listof x) -> boolean
|
||||||
(define (contains-same? l1 l2)
|
(define (contains-same? l1 l2)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user