;;; Functional name maps
;;; Copyright (C) 2014, 2015, 2017 Free Software Foundation, Inc.
;;;
;;; This library is free software: you can redistribute it and/or modify
;;; it under the terms of the GNU Lesser General Public License as
;;; published by the Free Software Foundation, either version 3 of the
;;; License, or (at your option) any later version.
;;;
;;; This library is distributed in the hope that it will be useful, but
;;; WITHOUT ANY WARRANTY; without even the implied warranty of
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
;;; Lesser General Public License for more details.
;;;
;;; You should have received a copy of the GNU Lesser General Public
;;; License along with this program. If not, see
;;; .
;;; Commentary:
;;;
;;; A persistent, functional data structure representing a set of
;;; integers as a tree whose branches are vectors and whose leaves are
;;; fixnums. Intsets are careful to preserve sub-structure, in the
;;; sense of eq?, whereever possible.
;;;
;;; Code:
(define-module (language cps intset)
#:use-module (rnrs bytevectors)
#:use-module (srfi srfi-9)
#:use-module (srfi srfi-9 gnu)
#:use-module (ice-9 match)
#:use-module ((ice-9 threads) #:select (current-thread))
#:export (empty-intset
intset?
transient-intset?
persistent-intset
transient-intset
intset
intset-add
intset-add!
intset-remove
intset-ref
intset-next
intset-prev
intset-fold
intset-fold-right
intset-union
intset-intersect
intset-subtract
bitvector->intset))
(define-syntax-rule (define-inline name val)
(define-syntax name (identifier-syntax val)))
(eval-when (expand)
(use-modules (system base target))
(define-syntax compile-time-cond
(lambda (x)
(syntax-case x (else)
((_ (test body ...) rest ...)
(if (primitive-eval (syntax->datum #'test))
#'(begin body ...)
#'(begin (compile-time-cond rest ...))))
((_ (else body ...))
#'(begin body ...))
((_)
(error "no compile-time-cond expression matched"))))))
(compile-time-cond
((eqv? (target-word-size) 4)
(define-inline *leaf-bits* 4))
((eqv? (target-word-size) 8)
(define-inline *leaf-bits* 5)))
;; FIXME: This should make an actual atomic reference.
(define-inlinable (make-atomic-reference value)
(list value))
(define-inlinable (get-atomic-reference reference)
(car reference))
(define-inlinable (set-atomic-reference! reference value)
(set-car! reference value))
(define-inline *leaf-size* (ash 1 *leaf-bits*))
(define-inline *leaf-mask* (1- *leaf-size*))
(define-inline *branch-bits* 3)
(define-inline *branch-size* (ash 1 *branch-bits*))
(define-inline *branch-size-with-edit* (1+ *branch-size*))
(define-inline *edit-index* *branch-size*)
(define-inline *branch-mask* (1- *branch-size*))
(define-record-type
(make-intset min shift root)
intset?
(min intset-min)
(shift intset-shift)
(root intset-root))
(define-record-type
(make-transient-intset min shift root edit)
transient-intset?
(min transient-intset-min set-transient-intset-min!)
(shift transient-intset-shift set-transient-intset-shift!)
(root transient-intset-root set-transient-intset-root!)
(edit transient-intset-edit set-transient-intset-edit!))
(define-inlinable (clone-leaf-and-set leaf i val)
(if val
(if leaf
(logior leaf (ash 1 i))
(ash 1 i))
(if leaf
(logand leaf (lognot (ash 1 i)))
#f)))
(define (leaf-empty? leaf)
(zero? leaf))
(define-inlinable (new-branch edit)
(let ((vec (make-vector *branch-size-with-edit* #f)))
(when edit (vector-set! vec *edit-index* edit))
vec))
(define-inlinable (clone-branch-and-set branch i elt)
(let ((new (new-branch #f)))
(when branch
(let lp ((n 0))
(when (< n *branch-size*)
(vector-set! new n (vector-ref branch n))
(lp (1+ n)))))
(vector-set! new i elt)
new))
(define-inlinable (assert-readable! root-edit)
(unless (eq? (get-atomic-reference root-edit) (current-thread))
(error "Transient intset owned by another thread" root-edit)))
(define-inlinable (writable-branch branch root-edit)
(let ((edit (vector-ref branch *edit-index*)))
(if (eq? root-edit edit)
branch
(clone-branch-and-set branch *edit-index* root-edit))))
(define (branch-empty? branch)
(let lp ((i 0))
(or (= i *branch-size*)
(and (not (vector-ref branch i))
(lp (1+ i))))))
(define-inlinable (round-down min shift)
(logand min (lognot (1- (ash 1 shift)))))
(define empty-intset (make-intset 0 *leaf-bits* #f))
(define (add-level min shift root)
(let* ((shift* (+ shift *branch-bits*))
(min* (round-down min shift*))
(idx (logand (ash (- min min*) (- shift)) *branch-mask*)))
(make-intset min* shift* (clone-branch-and-set #f idx root))))
(define (make-intset/prune min shift root)
(cond
((not root)
empty-intset)
((= shift *leaf-bits*)
(make-intset min shift root))
(else
(let lp ((i 0) (elt #f))
(cond
((< i *branch-size*)
(if (vector-ref root i)
(if elt
(make-intset min shift root)
(lp (1+ i) i))
(lp (1+ i) elt)))
(elt
(let ((shift (- shift *branch-bits*)))
(make-intset/prune (+ min (ash elt shift))
shift
(vector-ref root elt))))
;; Shouldn't be reached...
(else empty-intset))))))
(define* (transient-intset #:optional (source empty-intset))
(match source
(($ min shift root edit)
(assert-readable! edit)
source)
(($ min shift root)
(let ((edit (make-atomic-reference (current-thread))))
(make-transient-intset min shift root edit)))))
(define* (persistent-intset #:optional (source empty-intset))
(match source
(($ min shift root edit)
(assert-readable! edit)
;; Make a fresh reference, causing any further operations on this
;; transient to clone its root afresh.
(set-transient-intset-edit! source
(make-atomic-reference (current-thread)))
;; Clear the reference to the current thread, causing our edited
;; data structures to be persistent again.
(set-atomic-reference! edit #f)
(if min
(make-intset min shift root)
empty-intset))
(($ )
source)))
(define (intset-add! bs i)
(define (adjoin-leaf i root)
(clone-leaf-and-set root (logand i *leaf-mask*) #t))
(define (ensure-branch! root idx)
(let ((edit (vector-ref root *edit-index*)))
(match (vector-ref root idx)
(#f (let ((v (new-branch edit)))
(vector-set! root idx v)
v))
(v (let ((v* (writable-branch v edit)))
(unless (eq? v v*)
(vector-set! root idx v*))
v*)))))
(define (adjoin-branch! i shift root)
(let* ((shift (- shift *branch-bits*))
(idx (logand (ash i (- shift)) *branch-mask*)))
(cond
((= shift *leaf-bits*)
(vector-set! root idx (adjoin-leaf i (vector-ref root idx))))
(else
(adjoin-branch! i shift (ensure-branch! root idx))))))
(match bs
(($ min shift root edit)
(assert-readable! edit)
(cond
((< i 0)
;; The power-of-two spanning trick doesn't work across 0.
(error "Intsets can only hold non-negative integers." i))
((not root)
;; Add first element.
(let ((min (round-down i shift)))
(set-transient-intset-min! bs min)
(set-transient-intset-shift! bs *leaf-bits*)
(set-transient-intset-root! bs (adjoin-leaf (- i min) root))))
((and (<= min i) (< i (+ min (ash 1 shift))))
;; Add element to set; level will not change.
(if (= shift *leaf-bits*)
(set-transient-intset-root! bs (adjoin-leaf (- i min) root))
(let ((root* (writable-branch root edit)))
(unless (eq? root root*)
(set-transient-intset-root! bs root*))
(adjoin-branch! (- i min) shift root*))))
(else
(let lp ((min min)
(shift shift)
(root (if (eqv? shift *leaf-bits*)
root
(writable-branch root edit))))
(let* ((shift* (+ shift *branch-bits*))
(min* (round-down min shift*))
(idx (logand (ash (- min min*) (- shift)) *branch-mask*))
(root* (new-branch edit)))
(vector-set! root* idx root)
(cond
((and (<= min* i) (< i (+ min* (ash 1 shift*))))
(set-transient-intset-min! bs min*)
(set-transient-intset-shift! bs shift*)
(set-transient-intset-root! bs root*)
(adjoin-branch! (- i min*) shift* root*))
(else
(lp min* shift* root*)))))))
bs)
(($ )
(intset-add! (transient-intset bs) i))))
(define (intset-add bs i)
(define (adjoin i shift root)
(cond
((= shift *leaf-bits*)
(let ((idx (logand i *leaf-mask*)))
(if (and root (logbit? idx root))
root
(clone-leaf-and-set root idx #t))))
(else
(let* ((shift (- shift *branch-bits*))
(idx (logand (ash i (- shift)) *branch-mask*))
(node (and root (vector-ref root idx)))
(new-node (adjoin i shift node)))
(if (eq? node new-node)
root
(clone-branch-and-set root idx new-node))))))
(match bs
(($ min shift root)
(cond
((< i 0)
;; The power-of-two spanning trick doesn't work across 0.
(error "Intsets can only hold non-negative integers." i))
((not root)
;; Add first element.
(let ((min (round-down i shift)))
(make-intset min *leaf-bits*
(adjoin (- i min) *leaf-bits* root))))
((and (<= min i) (< i (+ min (ash 1 shift))))
;; Add element to set; level will not change.
(let ((old-root root)
(root (adjoin (- i min) shift root)))
(if (eq? root old-root)
bs
(make-intset min shift root))))
((< i min)
;; Rebuild the tree by unioning two intsets.
(intset-union (intset-add empty-intset i) bs))
(else
;; Add a new level and try again.
(intset-add (add-level min shift root) i))))))
(define-syntax intset
(syntax-rules ()
((intset) empty-intset)
((intset x x* ...) (intset-add (intset x* ...) x))))
(define (intset-remove bs i)
(define (remove i shift root)
(cond
((= shift *leaf-bits*)
(let ((idx (logand i *leaf-mask*)))
(if (logbit? idx root)
(let ((root (clone-leaf-and-set root idx #f)))
(and (not (leaf-empty? root)) root))
root)))
(else
(let* ((shift (- shift *branch-bits*))
(idx (logand (ash i (- shift)) *branch-mask*)))
(cond
((vector-ref root idx)
=> (lambda (node)
(let ((new-node (remove i shift node)))
(if (eq? node new-node)
root
(let ((root (clone-branch-and-set root idx new-node)))
(and (or new-node (not (branch-empty? root)))
root))))))
(else root))))))
(match bs
(($ min shift root)
(cond
((not root) bs)
((and (<= min i) (< i (+ min (ash 1 shift))))
(let ((old-root root)
(root (remove (- i min) shift root)))
(if (eq? root old-root)
bs
(make-intset/prune min shift root))))
(else bs)))))
(define (intset-ref bs i)
(define (ref min shift root)
(and (<= min i) (< i (+ min (ash 1 shift)))
(let ((i (- i min)))
(let lp ((node root) (shift shift))
(and node
(if (= shift *leaf-bits*)
(logbit? (logand i *leaf-mask*) node)
(let* ((shift (- shift *branch-bits*))
(idx (logand (ash i (- shift)) *branch-mask*)))
(lp (vector-ref node idx) shift))))))))
(match bs
(($ min shift root)
(ref min shift root))
(($ min shift root edit)
(assert-readable! edit)
(ref min shift root))))
(define* (intset-next bs #:optional i)
(define (visit-leaf node i)
(let lp ((idx (logand i *leaf-mask*)))
(if (logbit? idx node)
(logior (logand i (lognot *leaf-mask*)) idx)
(let ((idx (1+ idx)))
(and (< idx *leaf-size*)
(lp idx))))))
(define (visit-branch node shift i)
(let lp ((i i) (idx (logand (ash i (- shift)) *branch-mask*)))
(and (< idx *branch-size*)
(or (let ((node (vector-ref node idx)))
(and node (visit-node node shift i)))
(let ((inc (ash 1 shift)))
(lp (+ (round-down i shift) inc) (1+ idx)))))))
(define (visit-node node shift i)
(if (= shift *leaf-bits*)
(visit-leaf node i)
(visit-branch node (- shift *branch-bits*) i)))
(define (next min shift root)
(let ((i (if (and i (< min i))
(- i min)
0)))
(and root (< i (ash 1 shift))
(let ((i (visit-node root shift i)))
(and i (+ min i))))))
(match bs
(($ min shift root)
(next min shift root))
(($ min shift root edit)
(assert-readable! edit)
(next min shift root))))
(define* (intset-prev bs #:optional i)
(define (visit-leaf node i)
(let lp ((idx (logand i *leaf-mask*)))
(if (logbit? idx node)
(logior (logand i (lognot *leaf-mask*)) idx)
(let ((idx (1- idx)))
(and (<= 0 idx) (lp idx))))))
(define (visit-branch node shift i)
(let lp ((i i) (idx (logand (ash i (- shift)) *branch-mask*)))
(and (<= 0 idx)
(or (let ((node (vector-ref node idx)))
(and node (visit-node node shift i)))
(lp (1- (round-down i shift)) (1- idx))))))
(define (visit-node node shift i)
(if (= shift *leaf-bits*)
(visit-leaf node i)
(visit-branch node (- shift *branch-bits*) i)))
(define (prev min shift root)
(let ((i (if (and i (<= i (+ min (ash 1 shift))))
(- i min)
(1- (ash 1 shift)))))
(and root (<= 0 i)
(let ((i (visit-node root shift i)))
(and i (+ min i))))))
(match bs
(($ min shift root)
(prev min shift root))
(($ min shift root edit)
(assert-readable! edit)
(prev min shift root))))
(define-syntax-rule (make-intset-folder forward? seed ...)
(lambda (f set seed ...)
(define (visit-branch node shift min seed ...)
(cond
((= shift *leaf-bits*)
(let lp ((i (if forward? 0 (1- *leaf-size*))) (seed seed) ...)
(if (if forward? (< i *leaf-size*) (<= 0 i))
(if (logbit? i node)
(call-with-values (lambda () (f (+ i min) seed ...))
(lambda (seed ...)
(lp (if forward? (1+ i) (1- i)) seed ...)))
(lp (if forward? (1+ i) (1- i)) seed ...))
(values seed ...))))
(else
(let ((shift (- shift *branch-bits*)))
(let lp ((i (if forward? 0 (1- *branch-size*))) (seed seed) ...)
(if (if forward? (< i *branch-size*) (<= 0 i))
(let ((elt (vector-ref node i)))
(if elt
(call-with-values
(lambda ()
(visit-branch elt shift (+ min (ash i shift)) seed ...))
(lambda (seed ...)
(lp (if forward? (1+ i) (1- i)) seed ...)))
(lp (if forward? (1+ i) (1- i)) seed ...)))
(values seed ...)))))))
(match set
(($ min shift root)
(cond
((not root) (values seed ...))
(else (visit-branch root shift min seed ...))))
(($ )
(intset-fold f (persistent-intset set) seed ...)))))
(define intset-fold
(case-lambda
((f set)
((make-intset-folder #t) f set))
((f set seed)
((make-intset-folder #t seed) f set seed))
((f set s0 s1)
((make-intset-folder #t s0 s1) f set s0 s1))
((f set s0 s1 s2)
((make-intset-folder #t s0 s1 s2) f set s0 s1 s2))))
(define intset-fold-right
(case-lambda
((f set)
((make-intset-folder #f) f set))
((f set seed)
((make-intset-folder #f seed) f set seed))
((f set s0 s1)
((make-intset-folder #f s0 s1) f set s0 s1))
((f set s0 s1 s2)
((make-intset-folder #f s0 s1 s2) f set s0 s1 s2))))
(define (intset-size shift root)
(cond
((not root) 0)
((= *leaf-bits* shift) *leaf-size*)
(else
(let lp ((i (1- *branch-size*)))
(let ((node (vector-ref root i)))
(if node
(let ((shift (- shift *branch-bits*)))
(+ (intset-size shift node)
(* i (ash 1 shift))))
(lp (1- i))))))))
(define (intset-union a b)
;; Union leaves.
(define (union-leaves a b)
(logior (or a 0) (or b 0)))
;; Union A and B from index I; the result will be fresh.
(define (union-branches/fresh shift a b i fresh)
(let lp ((i 0))
(cond
((< i *branch-size*)
(let* ((a-child (vector-ref a i))
(b-child (vector-ref b i)))
(vector-set! fresh i (union shift a-child b-child))
(lp (1+ i))))
(else fresh))))
;; Union A and B from index I; the result may be eq? to A.
(define (union-branches/a shift a b i)
(let lp ((i i))
(cond
((< i *branch-size*)
(let* ((a-child (vector-ref a i))
(b-child (vector-ref b i)))
(if (eq? a-child b-child)
(lp (1+ i))
(let ((child (union shift a-child b-child)))
(cond
((eq? a-child child)
(lp (1+ i)))
(else
(let ((result (clone-branch-and-set a i child)))
(union-branches/fresh shift a b (1+ i) result))))))))
(else a))))
;; Union A and B; the may could be eq? to either.
(define (union-branches shift a b)
(let lp ((i 0))
(cond
((< i *branch-size*)
(let* ((a-child (vector-ref a i))
(b-child (vector-ref b i)))
(if (eq? a-child b-child)
(lp (1+ i))
(let ((child (union shift a-child b-child)))
(cond
((eq? a-child child)
(union-branches/a shift a b (1+ i)))
((eq? b-child child)
(union-branches/a shift b a (1+ i)))
(else
(let ((result (clone-branch-and-set a i child)))
(union-branches/fresh shift a b (1+ i) result))))))))
;; Seems they are the same but not eq?. Odd.
(else a))))
(define (union shift a-node b-node)
(cond
((not a-node) b-node)
((not b-node) a-node)
((eq? a-node b-node) a-node)
((= shift *leaf-bits*) (union-leaves a-node b-node))
(else (union-branches (- shift *branch-bits*) a-node b-node))))
(match (cons a b)
((($ a-min a-shift a-root) . ($ b-min b-shift b-root))
(cond
((not b-root) a)
((not a-root) b)
((not (= b-shift a-shift))
;; Hoist the set with the lowest shift to meet the one with the
;; higher shift.
(if (< b-shift a-shift)
(intset-union a (add-level b-min b-shift b-root))
(intset-union (add-level a-min a-shift a-root) b)))
((not (= b-min a-min))
;; Nodes at the same shift but different minimums will cover
;; disjoint ranges (due to the round-down call on min). Hoist
;; both until they cover the same range.
(intset-union (add-level a-min a-shift a-root)
(add-level b-min b-shift b-root)))
(else
;; At this point, A and B cover the same range.
(let ((root (union a-shift a-root b-root)))
(cond
((eq? root a-root) a)
((eq? root b-root) b)
(else (make-intset a-min a-shift root)))))))))
(define (intset-intersect a b)
;; Intersect leaves.
(define (intersect-leaves a b)
(let ((leaf (logand a b)))
(if (eqv? leaf 0) #f leaf)))
;; Intersect A and B from index I; the result will be fresh.
(define (intersect-branches/fresh shift a b i fresh)
(let lp ((i 0))
(cond
((< i *branch-size*)
(let* ((a-child (vector-ref a i))
(b-child (vector-ref b i)))
(vector-set! fresh i (intersect shift a-child b-child))
(lp (1+ i))))
((branch-empty? fresh) #f)
(else fresh))))
;; Intersect A and B from index I; the result may be eq? to A.
(define (intersect-branches/a shift a b i)
(let lp ((i i))
(cond
((< i *branch-size*)
(let* ((a-child (vector-ref a i))
(b-child (vector-ref b i)))
(if (eq? a-child b-child)
(lp (1+ i))
(let ((child (intersect shift a-child b-child)))
(cond
((eq? a-child child)
(lp (1+ i)))
(else
(let ((result (clone-branch-and-set a i child)))
(intersect-branches/fresh shift a b (1+ i) result))))))))
(else a))))
;; Intersect A and B; the may could be eq? to either.
(define (intersect-branches shift a b)
(let lp ((i 0))
(cond
((< i *branch-size*)
(let* ((a-child (vector-ref a i))
(b-child (vector-ref b i)))
(if (eq? a-child b-child)
(lp (1+ i))
(let ((child (intersect shift a-child b-child)))
(cond
((eq? a-child child)
(intersect-branches/a shift a b (1+ i)))
((eq? b-child child)
(intersect-branches/a shift b a (1+ i)))
(else
(let ((result (clone-branch-and-set a i child)))
(intersect-branches/fresh shift a b (1+ i) result))))))))
;; Seems they are the same but not eq?. Odd.
(else a))))
(define (intersect shift a-node b-node)
(cond
((or (not a-node) (not b-node)) #f)
((eq? a-node b-node) a-node)
((= shift *leaf-bits*) (intersect-leaves a-node b-node))
(else (intersect-branches (- shift *branch-bits*) a-node b-node))))
(define (different-mins lo-min lo-shift lo-root hi-min hi-shift hi lo-is-a?)
(cond
((<= lo-shift hi-shift)
;; If LO has a lower shift and a lower min, it is disjoint. If
;; it has the same shift and a different min, it is also
;; disjoint.
empty-intset)
(else
(let* ((lo-shift (- lo-shift *branch-bits*))
(lo-idx (ash (- hi-min lo-min) (- lo-shift))))
(cond
((>= lo-idx *branch-size*)
;; HI has a lower shift, but it not within LO.
empty-intset)
((vector-ref lo-root lo-idx)
=> (lambda (lo-root)
(let ((lo (make-intset (+ lo-min (ash lo-idx lo-shift))
lo-shift
lo-root)))
(if lo-is-a?
(intset-intersect lo hi)
(intset-intersect hi lo)))))
(else empty-intset))))))
(define (different-shifts-same-min min hi-shift hi-root lo lo-is-a?)
(cond
((vector-ref hi-root 0)
=> (lambda (hi-root)
(let ((hi (make-intset min
(- hi-shift *branch-bits*)
hi-root)))
(if lo-is-a?
(intset-intersect lo hi)
(intset-intersect hi lo)))))
(else empty-intset)))
(match (cons a b)
((($ a-min a-shift a-root) . ($ b-min b-shift b-root))
(cond
((< a-min b-min)
(different-mins a-min a-shift a-root b-min b-shift b #t))
((< b-min a-min)
(different-mins b-min b-shift b-root a-min a-shift a #f))
((< a-shift b-shift)
(different-shifts-same-min b-min b-shift b-root a #t))
((< b-shift a-shift)
(different-shifts-same-min a-min a-shift a-root b #f))
(else
;; At this point, A and B cover the same range.
(let ((root (intersect a-shift a-root b-root)))
(cond
((eq? root a-root) a)
((eq? root b-root) b)
(else (make-intset/prune a-min a-shift root)))))))))
(define (intset-subtract a b)
;; Intersect leaves.
(define (subtract-leaves a b)
(let ((out (logand a (lognot b))))
(if (zero? out) #f out)))
;; Subtract B from A starting at index I; the result will be fresh.
(define (subtract-branches/fresh shift a b i fresh)
(let lp ((i 0))
(cond
((< i *branch-size*)
(let* ((a-child (vector-ref a i))
(b-child (vector-ref b i)))
(vector-set! fresh i (subtract-nodes shift a-child b-child))
(lp (1+ i))))
((branch-empty? fresh) #f)
(else fresh))))
;; Subtract B from A. The result may be eq? to A.
(define (subtract-branches shift a b)
(let lp ((i 0))
(cond
((< i *branch-size*)
(let* ((a-child (vector-ref a i))
(b-child (vector-ref b i)))
(let ((child (subtract-nodes shift a-child b-child)))
(cond
((eq? a-child child)
(lp (1+ i)))
(else
(let ((result (clone-branch-and-set a i child)))
(subtract-branches/fresh shift a b (1+ i) result)))))))
(else a))))
(define (subtract-nodes shift a-node b-node)
(cond
((or (not a-node) (not b-node)) a-node)
((eq? a-node b-node) #f)
((= shift *leaf-bits*) (subtract-leaves a-node b-node))
(else (subtract-branches (- shift *branch-bits*) a-node b-node))))
(match (cons a b)
((($ a-min a-shift a-root) . ($ b-min b-shift b-root))
(define (return root)
(cond
((eq? root a-root) a)
(else (make-intset/prune a-min a-shift root))))
(cond
((<= a-shift b-shift)
(let lp ((b-min b-min) (b-shift b-shift) (b-root b-root))
(if (= a-shift b-shift)
(if (= a-min b-min)
(return (subtract-nodes a-shift a-root b-root))
a)
(let* ((b-shift (- b-shift *branch-bits*))
(b-idx (ash (- a-min b-min) (- b-shift)))
(b-min (+ b-min (ash b-idx b-shift)))
(b-root (and b-root
(<= 0 b-idx)
(< b-idx *branch-size*)
(vector-ref b-root b-idx))))
(lp b-min b-shift b-root)))))
(else
(return
(let lp ((a-min a-min) (a-shift a-shift) (a-root a-root))
(if (= a-shift b-shift)
(if (= a-min b-min)
(subtract-nodes a-shift a-root b-root)
a-root)
(let* ((a-shift (- a-shift *branch-bits*))
(a-idx (ash (- b-min a-min) (- a-shift)))
(a-min (+ a-min (ash a-idx a-shift)))
(old (and a-root
(<= 0 a-idx)
(< a-idx *branch-size*)
(vector-ref a-root a-idx)))
(new (lp a-min a-shift old)))
(if (eq? old new)
a-root
(let ((root (clone-branch-and-set a-root a-idx new)))
(and (or new (not (branch-empty? root)))
root))))))))))))
(define (bitvector->intset bv)
(define (finish-tail out min tail)
(if (zero? tail)
out
(intset-union out (make-intset min *leaf-bits* tail))))
(let lp ((out empty-intset) (min 0) (pos 0) (tail 0))
(let ((pos (bitvector-position bv #t pos)))
(cond
((not pos)
(finish-tail out min tail))
((< pos (+ min *leaf-size*))
(lp out min (1+ pos) (logior tail (ash 1 (- pos min)))))
(else
(let ((min* (round-down pos *leaf-bits*)))
(lp (finish-tail out min tail)
min* pos (ash 1 (- pos min*)))))))))
(define (intset-key-ranges intset)
(call-with-values
(lambda ()
(intset-fold (lambda (k start end closed)
(cond
((not start) (values k k closed))
((= k (1+ end)) (values start k closed))
(else (values k k (acons start end closed)))))
intset #f #f '()))
(lambda (start end closed)
(reverse (if start (acons start end closed) closed)))))
(define (range-string ranges)
(string-join (map (match-lambda
((start . start)
(format #f "~a" start))
((start . end)
(format #f "~a-~a" start end)))
ranges)
","))
(define (print-helper port tag intset)
(let ((ranges (intset-key-ranges intset)))
(match ranges
(()
(format port "#<~a>" tag))
(_
(format port "#<~a ~a>" tag (range-string ranges))))))
(define (print-intset intset port)
(print-helper port "intset" intset))
(define (print-transient-intset intset port)
(print-helper port "transient-intset" intset))
(set-record-type-printer! print-intset)
(set-record-type-printer! print-transient-intset)