;; -*- Lisp -*- vim:filetype=lisp

#+LISPWORKS
(progn
  (defun gc () (mark-and-sweep 3))
  t)
#+LISPWORKS
T

(defun weak-ht-fill-initially (tab)
  (setf (gethash (copy-seq "foo") tab) 1)
  (setf (gethash 1 tab) (copy-seq "bar"))
  (setf (gethash (copy-seq "zoo") tab) (copy-seq "zot")))
weak-ht-fill-initially


;; Test that weak hash-tables of kind :KEY work.

(let ((tab (make-hash-table :test #+OpenMCL 'eq #-OpenMCL 'equal
                            #+ALLEGRO :weak-keys #+ALLEGRO t
                            #+CMU19 :weak-p #+CMU19 t
                            #+LISPWORKS :weak-kind #-(or ALLEGRO CMU19 LISPWORKS) :weak
                              #-(or ALLEGRO CMU19) :key)))
  (weak-ht-fill-initially tab)
  (gc)
  (list (gethash "foo" tab) (gethash 1 tab) (gethash "zoo" tab)))
(NIL "bar" NIL)


;; Test that weak hash-tables of kind :KEY don't suffer from the
;; "key in value" problem.

(let ((tab (make-hash-table :test 'eq
                            #+ALLEGRO :weak-keys #+ALLEGRO t
                            #+CMU19 :weak-p #+CMU19 t
                            #+LISPWORKS :weak-kind #-(or ALLEGRO CMU19 LISPWORKS) :weak
                              #-(or ALLEGRO CMU19) :key)))
  (let ((a (list 'x)))
    (let ((b (list 'y)))
      (setf (gethash a tab) 'xxx)
      (setf (gethash b tab) (cons 'yyy b)))
    (gc)
    (list (hash-table-count tab)
          (gethash a tab)
          (let ((l nil)) (maphash #'(lambda (k v) (push k l)) tab) l))))
(1 XXX ((X)))


;; Perform all the WEAK-MAPPING tests, emulating WEAK-MAPPING with
;; weak hash-tables.

(progn
  (defun make-freak-mapping (a b)
    (let ((tab (make-hash-table :test 'eq
                                #+ALLEGRO :weak-keys #+ALLEGRO t
                                #+CMU19 :weak-p #+CMU19 t
                                #+LISPWORKS :weak-kind #-(or ALLEGRO CMU19 LISPWORKS) :weak
                                  #-(or ALLEGRO CMU19) :key)))
      (setf (gethash a tab) b)
      tab))
  (defun freak-mapping-pair (tab)
    (let (a b c)
      (maphash #'(lambda (k v) (setq a k b v c t)) tab)
      (values a b c)))
  (defun freak-mapping-value (tab)
    (block nil
      (maphash #'(lambda (k v) (return-from nil v)) tab)
      nil))
  (defun (setf freak-mapping-value) (new-value tab)
    (block nil
      (maphash #'(lambda (k v) (setf (gethash k tab) new-value) (return-from nil))
               tab))
    new-value)
  t)
T

(let ((a (list 'x))
      (b (list 'y)))
  (let ((w (make-freak-mapping a b)))
    (gc)
    (list (multiple-value-list (freak-mapping-pair w))
          (multiple-value-list (freak-mapping-value w)))))
(((x) (y) t) ((y)))

(let ((a (list 'x))
      (b (list 'y)))
  (let ((w (make-freak-mapping a b)))
    (setq b nil)
    (gc)
    (list (multiple-value-list (freak-mapping-pair w))
          (multiple-value-list (freak-mapping-value w)))))
(((x) (y) t) ((y)))

(let ((a (list 'x))
      (b (list 'y)))
  (let ((w (make-freak-mapping a b)))
    (setq a nil)
    (gc)
    (list (multiple-value-list (freak-mapping-pair w))
          (multiple-value-list (freak-mapping-value w)))))
((nil nil nil) (nil))

(let ((a (list 'x))
      (b (list 'y)))
  (let ((w (make-freak-mapping a b)))
    (setq a nil b nil)
    (gc)
    (list (multiple-value-list (freak-mapping-pair w))
          (multiple-value-list (freak-mapping-value w)))))
((nil nil nil) (nil))

(let ((a1 (list 'x1))
      (a2 (list 'x2))
      (a3 (list 'x3))
      (a4 (list 'x4))
      (a5 (list 'x5)))
  (let ((w1 (make-freak-mapping a3 a4))
        (w2 (make-freak-mapping a1 a2))
        (w3 (make-freak-mapping a4 a5))
        (w4 (make-freak-mapping a2 a3)))
    (setq a2 nil a3 nil a4 nil a5 nil)
    (gc)
    (list (freak-mapping-value w2)
          (freak-mapping-value w4)
          (freak-mapping-value w1)
          (freak-mapping-value w3))))
((x2) (x3) (x4) (x5))

(let ((a1 (list 'x1))
      (a2 (list 'x2))
      (a3 (list 'x3))
      (a4 (list 'x4))
      (a5 (list 'x5)))
  (let ((w1 (make-freak-mapping a3 a4))
        (w2 (make-freak-mapping a1 a2))
        (w3 (make-freak-mapping a4 a5))
        (w4 (make-freak-mapping a2 a3)))
    (setq a1 nil a3 nil a4 nil a5 nil)
    (gc)
    (list (freak-mapping-value w2)
          (freak-mapping-value w4)
          (freak-mapping-value w1)
          (freak-mapping-value w3))))
(nil (x3) (x4) (x5))

(let ((a1 (list 'x1))
      (a2 (list 'x2))
      (a3 (list 'x3))
      (a4 (list 'x4))
      (a5 (list 'x5)))
  (let ((w1 (make-freak-mapping a3 a4))
        (w2 (make-freak-mapping a1 a2))
        (w3 (make-freak-mapping a4 a5))
        (w4 (make-freak-mapping a2 a3)))
    (setq a1 nil a2 nil a4 nil a5 nil)
    (gc)
    (list (freak-mapping-value w2)
          (freak-mapping-value w4)
          (freak-mapping-value w1)
          (freak-mapping-value w3))))
(nil nil (x4) (x5))

(let ((a1 (list 'x1))
      (a2 (list 'x2))
      (a3 (list 'x3))
      (a4 (list 'x4))
      (a5 (list 'x5)))
  (let ((w1 (make-freak-mapping a3 a4))
        (w2 (make-freak-mapping a1 a2))
        (w3 (make-freak-mapping a4 a5))
        (w4 (make-freak-mapping a2 a3)))
    (setq a1 nil a2 nil a3 nil a5 nil)
    (gc)
    (list (freak-mapping-value w2)
          (freak-mapping-value w4)
          (freak-mapping-value w1)
          (freak-mapping-value w3))))
(nil nil nil (x5))

(let ((a1 (list 'x1))
      (a2 (list 'x2))
      (a3 (list 'x3))
      (a4 (list 'x4))
      (a5 (list 'x5)))
  (let ((w1 (make-freak-mapping a3 a4))
        (w2 (make-freak-mapping a1 a2))
        (w3 (make-freak-mapping a4 a5))
        (w4 (make-freak-mapping a2 a3)))
    (setq a1 nil a2 nil a3 nil a4 nil)
    (gc)
    (list (freak-mapping-value w2)
          (freak-mapping-value w4)
          (freak-mapping-value w1)
          (freak-mapping-value w3))))
(nil nil nil nil)

(let ((a (list 'x))
      (b (list 'y))
      (c (list 'z)))
  (let ((w (make-freak-mapping a b)))
    (setf (freak-mapping-value w) c)
    (list (multiple-value-list (freak-mapping-pair w))
          (multiple-value-list (freak-mapping-value w)))))
(((x) (z) t) ((z)))

(let ((a (list 'x))
      (b (list 'y))
      (c (list 'z)))
  (let ((w (make-freak-mapping a b)))
    (setf (freak-mapping-value w) c)
    (gc)
    (list (multiple-value-list (freak-mapping-pair w))
          (multiple-value-list (freak-mapping-value w)))))
(((x) (z) t) ((z)))

(let ((a (list 'x))
      (b (list 'y))
      (c (list 'z)))
  (let ((w (make-freak-mapping a b)))
    (gc)
    (setf (freak-mapping-value w) c)
    (list (multiple-value-list (freak-mapping-pair w))
          (multiple-value-list (freak-mapping-value w)))))
(((x) (z) t) ((z)))

(let ((a (list 'x))
      (b (list 'y))
      (c (list 'z)))
  (let ((w (make-freak-mapping a b)))
    (setq a nil)
    (setf (freak-mapping-value w) c)
    (gc)
    (list (multiple-value-list (freak-mapping-pair w))
          (multiple-value-list (freak-mapping-value w)))))
((nil nil nil) (nil))

(let ((a (list 'x))
      (b (list 'y))
      (c (list 'z)))
  (let ((w (make-freak-mapping a b)))
    (setq a nil)
    (gc)
    (setf (freak-mapping-value w) c)
    (list (multiple-value-list (freak-mapping-pair w))
          (multiple-value-list (freak-mapping-value w)))))
((nil nil nil) (nil))


;; Check that the GC can propagate through long chains of WEAK-MAPPINGs,
;; emulating WEAK-MAPPING with weak hash-tables.

(progn
  (defun test-weak-mapping-chain (n)
    (let (wm0)
      (let ((sym (make-array n)))
        (dotimes (i n) (setf (aref sym i) (make-symbol (prin1-to-string i))))
        ;; Build a chain
        ;;   (gethash sym0 wm0) = wm1
        ;;   (gethash sym1 wm1) = wm2
        ;;   ...
        (let ((wm (make-array n)))
          (dotimes (i n) (setf (aref wm i) (make-freak-mapping 'a 'b)))
          (setq wm0 (aref wm 0))
          (do ((i 1 (1+ i)))
              ((>= i n))
            (setf (gethash (aref sym (- i 1)) (aref wm (- i 1))) (aref wm i))))
        (time (gc))
        ;; Verify that the chain is still intact.
        (do ((i 0 (1+ i))
             (w wm0 (gethash (aref sym i) w)))
            ((>= i n)))
        (setq sym nil)
        (time (gc))
        (gethash 'a wm0))))
  (test-weak-mapping-chain 10000))
B

; Likewise with reverse order of allocation of the weak hash tables.
; This test exhibits O(n^2) behaviour in LispWorks 4.3.
(progn
  (defun test-weak-mapping-chain-reverse (n)
    (let (wm0)
      (let ((sym (make-array n)))
        (dotimes (i n) (setf (aref sym i) (make-symbol (prin1-to-string i))))
        ;; Build a chain
        ;;   (gethash sym0 wm0) = wm1
        ;;   (gethash sym1 wm1) = wm2
        ;;   ...
        (let ((wm (make-array n)))
          (dotimes (i n) (setf (aref wm (- n 1 i)) (make-freak-mapping 'a 'b)))
          (setq wm0 (aref wm 0))
          (do ((i 1 (1+ i)))
              ((>= i n))
            (setf (gethash (aref sym (- i 1)) (aref wm (- i 1))) (aref wm i))))
        (time (gc))
        ;; Verify that the chain is still intact.
        (do ((i 0 (1+ i))
             (w wm0 (gethash (aref sym i) w)))
            ((>= i n)))
        (setq sym nil)
        (time (gc))
        (gethash 'a wm0))))
  (test-weak-mapping-chain-reverse 10000))
B


;; Test that weak hash-tables of kind :VALUE work.

#+(or CLISP OpenMCL LISPWORKS)
(let ((tab (make-hash-table :test 'eq
                            #+LISPWORKS :weak-kind #-LISPWORKS :weak :value)))
  (setf (gethash 'foo tab) 1)
  (setf (gethash 1 tab) (copy-seq "bar"))
  (setf (gethash 'zoo tab) (copy-seq "zot"))
  (gc)
  (list (gethash 'foo tab) (gethash 1 tab) (gethash 'zoo tab)))
#+(or CLISP OpenMCL LISPWORKS)
(1 NIL NIL)

#+(or CLISP LISPWORKS)
(let ((tab (make-hash-table :test 'equal
                            #+LISPWORKS :weak-kind #-LISPWORKS :weak :value)))
  (weak-ht-fill-initially tab)
  (gc)
  (list (gethash "foo" tab) (gethash 1 tab) (gethash "zoo" tab)))
#+(or CLISP LISPWORKS)
(1 NIL NIL)

;; Test that weak hash-tables of kind :VALUE don't suffer from the
;; "value in key" problem.

#+(or CLISP OpenMCL LISPWORKS)
(let ((tab (make-hash-table :test 'eq
                            #+LISPWORKS :weak-kind #-LISPWORKS :weak :value)))
  (let ((a (list 'x)))
    (let ((b (list 'y)))
      (setf (gethash 'xxx tab) a)
      (setf (gethash (cons 'yyy b) tab) b))
    (gc)
    (list (hash-table-count tab)
          (eq (gethash 'xxx tab) a)
          (let ((l nil)) (maphash #'(lambda (k v) (push v l)) tab) l))))
#+(or CLISP OpenMCL LISPWORKS)
(1 T ((X)))


;; Test that weak hash-tables of kind :KEY-AND-VALUE work.

#+(or CLISP LISPWORKS)
(let ((tab (make-hash-table :test #+OpenMCL 'eq #-OpenMCL 'equal
                            #+LISPWORKS :weak-kind #+LISPWORKS :both
                            #-LISPWORKS :weak #-LISPWORKS :key-and-value)))
  (weak-ht-fill-initially tab)
  (gc)
  (list (gethash "foo" tab) (gethash 1 tab) (gethash "zoo" tab)))
#+(or CLISP LISPWORKS)
(NIL NIL NIL)


;; Test that weak hash-tables of kind :KEY-OR-VALUE work.

#+(or CLISP LISPWORKS)
(let ((tab (make-hash-table :test #+OpenMCL 'eq #-OpenMCL 'equal
                            #+LISPWORKS :weak-kind #+LISPWORKS :either
                            #-LISPWORKS :weak #-LISPWORKS :key-or-value)))
  #+LISPWORKS (set-hash-table-weak tab :either)
  (weak-ht-fill-initially tab)
  (gc)
  (list (gethash "foo" tab) (gethash 1 tab) (gethash "zoo" tab)))
#+(or CLISP LISPWORKS)
(1 "bar" NIL)

(symbols-cleanup '(weak-ht-fill-initially make-freak-mapping freak-mapping-pair
                   freak-mapping-value test-weak-mapping-chain
                   test-weak-mapping-chain-reverse))
()
