-
Notifications
You must be signed in to change notification settings - Fork 2
/
target-table.lisp
156 lines (138 loc) · 5.16 KB
/
target-table.lisp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
(defpackage :overlord/target-table
(:use :cl :alexandria :serapeum)
(:import-from :overlord/target-protocol
:hash-target)
(:export
:target-table
:hash-friendly?
:make-target-table
:with-target-table-locked
:target-table-len
:target-table-ref
:target-table-rem
:target-table-member
:target-table-keys
:clear-target-table))
(in-package :overlord/target-table)
;;; TODO Rewrite this to use cl-custom-hash-table. I thought it would
;;; be better to only have only implementation of a target table
;;; (using Fset) to reduce the maintenance burden, but practically it
;;; makes the implementation much more complicated.
(defgeneric hash-friendly? (target)
(:documentation "Can TARGET be used as a key in a EQUAL hash
table?")
(:method ((x package)) t)
(:method ((x symbol)) t)
(:method ((x pathname)) t)
(:method (x) (declare (ignore x))
nil))
(defstruct (target-table (:conc-name target-table.)
(:constructor %make-target-table))
"A table for storing targets.
This wraps an Fset map (for custom target types) and a hash table \(for built-in types) and keeps them in sync."
(map (fset:empty-map) :type fset:map)
(hash-table (make-hash-table :test 'equal :size 1024)
:type hash-table :read-only t)
(lock (bt:make-recursive-lock) :read-only t)
(synchronized nil :type boolean :read-only t))
;;; Ensure target tables can be written.
(defmethod print-object ((self target-table) stream)
(when (or (null *print-readably*)
(not *read-eval*))
(return-from print-object
(call-next-method)))
(write-string (read-eval-prefix self stream) stream)
(format stream "~s"
`(alist-to-target-table
'(,@(target-table-to-alist self)))))
(-> make-target-table
(&key (:size (integer 0 *)) (:synchronized t))
target-table)
(defun make-target-table (&key (size 1024) synchronized)
(%make-target-table
:hash-table (make-hash-table :test 'equal
:size (max 1024 size))
:synchronized synchronized))
(defun alist-to-target-table (alist)
(lret* ((len (length alist))
(table (make-target-table :size len)))
(loop for (k . v) in alist
do (setf (target-table-ref table k) v))))
(defmacro with-target-table-locked ((target-table) &body body)
(once-only (target-table)
(with-thunk (body)
`(if (target-table.synchronized ,target-table)
(bt:with-recursive-lock-held ((target-table.lock ,target-table))
(funcall ,body))
(funcall ,body)))))
(-> target-table-len (target-table) array-length)
(defun target-table-len (table)
(with-target-table-locked (table)
(let ((hash-table (target-table.hash-table table))
(map (target-table.map table)))
(+ (hash-table-count hash-table)
(fset:size map)))))
(defun target-table-to-alist (table)
(collecting
(let ((hash-table (target-table.hash-table table))
map)
(with-target-table-locked (table)
(setf map (target-table.map table))
(do-hash-table (k v hash-table)
(collect (cons k v))))
(fset:do-map (k v map)
(collect (cons k v))))))
(-> target-table-ref (target-table t) (values t boolean))
(defun target-table-ref (table key)
(with-target-table-locked (table)
(if (hash-friendly? key)
(let ((hash (target-table.hash-table table)))
(gethash key hash))
(fset:lookup (target-table.map table) key))))
(-> (setf target-table-ref) (t target-table t) t)
(defun (setf target-table-ref) (value table key)
(prog1 value
(with-target-table-locked (table)
(if (hash-friendly? key)
(let ((hash (target-table.hash-table table)))
(setf (gethash key hash) value))
(callf #'fset:with (target-table.map table) key value)))))
(-> target-table-rem (target-table t) null)
(defun target-table-rem (table key)
(prog1 nil
(with-target-table-locked (table)
(if (hash-friendly? key)
(let ((hash (target-table.hash-table table)))
(remhash key hash))
(callf #'fset:less (target-table.map table) key)))))
(-> target-table-member (target-table t) boolean)
(defun target-table-member (table key)
(nth-value 1
(target-table-ref table key)))
(-> (setf target-table-member) (t target-table t) boolean)
(defun (setf target-table-member) (value table key)
(prog1 (true value)
(if value
(with-target-table-locked (table)
(unless (target-table-member table key)
(setf (target-table-ref table key) t)))
(target-table-rem table key))))
(-> target-table-keys (target-table) list)
(defun target-table-keys (table)
(with-target-table-locked (table)
(collecting
;; Keys from the hash table.
(do-hash-table (k v (target-table.hash-table table))
(declare (ignore v))
(collect k))
;; Keys from the Fset map.
(fset:do-map (k v (target-table.map table))
(declare (ignore v))
(collect k)))))
(-> clear-target-table (target-table) (values))
(defun clear-target-table (table)
(with-target-table-locked (table)
(clrhash (target-table.hash-table table))
(setf (target-table.map table)
(fset:empty-map)))
(values))