/
bitfield.lisp
330 lines (275 loc) · 11.6 KB
/
bitfield.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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
(in-package #:bitfield)
(deftype bitfield ()
"A bitfield is a non-negative integer that efficiently encodes
information about some booleans, enumerations, or small integers."
'unsigned-byte)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; Bitfield Slots
(defgeneric bitfield-slot-name (bitfield-slot)
(:documentation
"Returns a symbol that is the name of the bitfield slot."))
(defgeneric bitfield-slot-start (bitfield-slot)
(:documentation
"Returns the position of the first bit of this slot in the bitfield."))
(defgeneric bitfield-slot-end (bitfield-slot)
(:documentation
"Returns the position right after the last bit of this slot in the bitfield."))
(defgeneric bitfield-slot-size (bitfield-slot)
(:documentation
"Returns an unsigned byte that is the number of distinct states of the slot."))
(defgeneric bitfield-slot-initform (bitfield-slot)
(:documentation
"Returns a form that produces the initial value for that slot."))
(defgeneric bitfield-slot-pack (bitfield-slot value-form)
(:documentation
"Takes a form that produces a value and turns it into a form that produces
a non-negative integer representing that value."))
(defgeneric bitfield-slot-unpack (bitfield-slot value-form)
(:documentation
"Take a form that produces a value that is encoded as a non-negative
integer (as produced by BITFIELD-SLOT-PACK), and turn it into a form that
produces the decoded value."))
(defgeneric parse-atomic-bitfield-slot-specifier
(specifier &key initform)
(:documentation
"Parses an atomic bitfield slot specifier, i.e., a bitfield slot
specifier that is not a list. Returns three values:
1. A designator for a bitfield slot class.
2. The size of the bitfield slot.
3. A list of additional arguments that will be supplied to MAKE-INSTANCE
when creating the bitfield slot instance."))
(defgeneric parse-compound-bitfield-slot-specifier
(specifier arguments &key initform)
(:documentation
"Parses a compount bitfield slot specifier, i.e., a bitfield slot
specifier that is a list. The SPECIFIER is the CAR of that list and the
ARGUMENTS are the CDR of that list. Returns three values:
1. A designator for a bitfield slot class.
2. The size of the bitfield slot.
3. A list of additional arguments that will be supplied to MAKE-INSTANCE
when creating the bitfield slot instance."))
(defclass bitfield-slot ()
((%name :initarg :name :reader bitfield-slot-name)
(%initform :initarg :initform :reader bitfield-slot-initform)
(%start :initarg :start :reader bitfield-slot-start)
(%end :initarg :end :reader bitfield-slot-end)
(%size :initarg :size :reader bitfield-slot-size)))
;;; Boolean Slots
(defclass bitfield-boolean-slot (bitfield-slot)
())
(defmethod bitfield-slot-pack ((slot bitfield-boolean-slot) value-form)
`(if ,value-form 1 0))
(defmethod bitfield-slot-unpack ((slot bitfield-boolean-slot) value-form)
`(ecase ,value-form (0 nil) (1 t)))
(defmethod parse-atomic-bitfield-slot-specifier
((specifier (eql 'boolean)) &key (initform 'nil))
(values 'bitfield-boolean-slot
2
`(:initform ,initform)))
;;; Integer Slots
(defclass bitfield-integer-slot (bitfield-slot)
((%offset
:type integer
:initarg :offset
:reader bitfield-integer-slot-offset)))
(defmethod bitfield-slot-pack ((slot bitfield-integer-slot) value-form)
(let ((offset (bitfield-integer-slot-offset slot))
(size (bitfield-slot-size slot)))
`(the (integer 0 (,size))
(- (the (integer ,offset (,(+ offset size))) ,value-form)
,offset))))
(defmethod bitfield-slot-unpack ((slot bitfield-integer-slot) value-form)
(let ((offset (bitfield-integer-slot-offset slot))
(size (bitfield-slot-size slot)))
`(the (integer ,offset (,(+ offset size)))
(+ ,value-form ,offset))))
(defmethod parse-atomic-bitfield-slot-specifier
((specifier (eql 'bit)) &key (initform '0))
(values 'bitfield-unsigned-byte-slot
2
`(:offset 0 :initform ,initform)))
(defmethod parse-compound-bitfield-slot-specifier
((specifier (eql 'unsigned-byte)) arguments &key (initform '0))
(destructuring-bind (bits) arguments
(check-type bits unsigned-byte)
(values 'bitfield-integer-slot
(expt 2 bits)
`(:offset 0 :initform ,initform))))
(defmethod parse-compound-bitfield-slot-specifier
((specifier (eql 'signed-byte)) arguments &key (initform '0))
(destructuring-bind (bits) arguments
(check-type bits unsigned-byte)
(values 'bitfield-integer-slot
(expt 2 bits)
`(:offset ,(- (expt 2 (1- bits))) :initform ,initform))))
(defmethod parse-compound-bitfield-slot-specifier
((specifier (eql 'integer)) bounds &key (initform nil initform-supplied-p))
(flet ((fail ()
(error "Invalid integer bitfield slot specifier: ~S"
`(integer ,@bounds))))
(unless (typep bounds '(cons t (cons t null)))
(fail))
(destructuring-bind (lo hi) bounds
(let* ((start (typecase lo
(integer lo)
((cons integer null)
(1+ (first lo)))
(otherwise (fail))))
(end (typecase hi
(integer (1+ hi))
((cons integer null)
(first hi))
(otherwise (fail))))
(size (- end start)))
(unless (plusp size)
(fail))
(values 'bitfield-integer-slot
size
`(:offset ,start :initform ,(if initform-supplied-p initform start)))))))
;;; Member Slots
(defclass bitfield-member-slot (bitfield-slot)
((%objects
:type list
:initarg :objects
:reader bitfield-member-slot-objects)))
(defmethod bitfield-slot-pack ((slot bitfield-member-slot) value-form)
`(ecase ,value-form
,@(loop for key in (bitfield-member-slot-objects slot)
for value from 0
collect `((,key) ,value))))
(defmethod bitfield-slot-unpack ((slot bitfield-member-slot) value-form)
`(ecase ,value-form
,@(loop for key from 0
for value in (bitfield-member-slot-objects slot)
collect `((,key) ',value))))
(defmethod parse-compound-bitfield-slot-specifier
((specifier (eql 'member)) objects &key (initform `',(first objects)))
(values 'bitfield-member-slot
(length objects)
`(:initform ,initform :objects ,objects)))
;;; Parsing
;;; The position right after the last slot that has been parsed so far.
(defvar *bitfield-position*)
(defun parse-bitfield-slot (slot)
(destructuring-bind (slot-name slot-specifier &rest rest) slot
(check-type slot-name symbol)
(multiple-value-bind (slot-class size args)
(if (consp slot-specifier)
(apply #'parse-compound-bitfield-slot-specifier
(car slot-specifier)
(cdr slot-specifier)
rest)
(apply #'parse-atomic-bitfield-slot-specifier
slot-specifier
rest))
(apply #'make-instance slot-class
:name slot-name
:size size
:start *bitfield-position*
:end (incf *bitfield-position* (integer-length (1- size)))
args))))
(defmacro define-bitfield (name &body slots)
"Defines an encoding of enumerable properties like booleans,
integers or finite sets as a single non-negative integer.
For a supplied bitfield name NAME, and for some slot definitions of the
form (SLOT-NAME TYPE &KEY INITFORM &ALLOW-OTHER-KEYS), this macro defines
the following functions:
1. A constructor named MAKE-{NAME}, that takes one keyword argument per
SLOT-NAME, similar to the default constructor generated by DEFSTRUCT.
It returns a bitfield whose entries have the values indicated by the
keyword arguments, or the supplied initform.
2. A clone operation named CLONE-{NAME}, that takes an existing bitfield
and one keyword argument per SLOT-NAME. It returns a copy of the
existing bitfield, but where each supplied keyword argument supersedes
the value of the corresponding slot.
3. A reader function named {NAME}-{SLOT-NAME} for each slot.
In addition to these functions, NAME is defined as a suitable subtype of
UNSIGNED-BYTE.
This macro supports boolean, integer, and member slots. It is also
possible to add new kinds of slots by defining new subclasses of
BITFIELD-SLOT and the corresponding methods on BITFIELD-SLOT-PACK,
BITFIELD-SLOT-UNPACK and PARSE-ATOMIC-BITFIELD-SLOT-SPECIFIER or
PARSE-COMPOUND-BITFIELD-SLOT-SPECIFIER.
Example:
(define-bitfield examplebits
(a boolean)
(b (signed-byte 2))
(c (unsigned-byte 3) :initform 1)
(d (integer -100 100))
(e (member foo bar baz)))
(defun examplebits-values (examplebits)
(list
(examplebits-a examplebits)
(examplebits-b examplebits)
(examplebits-c examplebits)
(examplebits-d examplebits)
(examplebits-e examplebits)))
(defparameter *default* (make-examplebits))
(examplebits-values *default*)
;; => (nil 0 1 -100 foo)
(defparameter *explicit* (make-examplebits :a t :b -1 :c 7 :d 42 :e 'baz))
(examplebits-values *explicit*)
;; => (t -1 7 42 baz)
(defparameter *clone* (clone-examplebits *explicit* :a nil :b -1 :c 2 :d -12 :e 'bar))
(examplebits-values *clone*)
;; => (nil -1 2 -12 bar)
"
(let* ((*bitfield-position* 0)
(package (symbol-package name))
(constructor
(intern (concatenate 'string "MAKE-" (symbol-name name)) package))
(cloner
(intern (concatenate 'string "CLONE-" (symbol-name name)) package))
(reader-prefix
(concatenate 'string ))
(slots
(mapcar #'parse-bitfield-slot slots))
(reader-names
(loop for slot in slots
collect
(intern (concatenate 'string (symbol-name name) "-" reader-prefix
(symbol-name (bitfield-slot-name slot)))
package))))
`(progn
(deftype ,name () '(unsigned-byte ,*bitfield-position*))
;; Define all slot readers.
,@(loop for slot in slots
for reader-name in reader-names
for start = (bitfield-slot-start slot)
for end = (bitfield-slot-end slot)
collect
`(declaim (inline ,reader-name))
collect
`(defun ,reader-name (,name)
(declare (,name ,name))
,(bitfield-slot-unpack
slot
`(ldb (byte ,(- end start) ,start) ,name))))
;; Define the cloner.
(declaim (inline ,cloner))
(defun ,cloner
(,name &key ,@(loop for slot in slots
for reader-name in reader-names
collect
`(,(bitfield-slot-name slot)
(,reader-name ,name))))
(declare (,name ,name))
(logior
,@(loop for slot in slots
collect
`(ash ,(bitfield-slot-pack slot (bitfield-slot-name slot))
,(bitfield-slot-start slot)))))
;; Define the constructor.
(declaim (inline ,constructor))
(defun ,constructor
(&key ,@(loop for slot in slots
collect
`(,(bitfield-slot-name slot)
,(bitfield-slot-initform slot))))
(logior
,@(loop for slot in slots
collect
`(ash ,(bitfield-slot-pack slot (bitfield-slot-name slot))
,(bitfield-slot-start slot)))))
',name)))