]> asedeno.scripts.mit.edu Git - cl-protobufs.git/blob - serialize.lisp
206db8081f3ecb7bac1d20557e18f12ff7588f07
[cl-protobufs.git] / serialize.lisp
1 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2 ;;;                                                                  ;;;
3 ;;; Confidential and proprietary information of ITA Software, Inc.   ;;;
4 ;;;                                                                  ;;;
5 ;;; Copyright (c) 2012 ITA Software, Inc.  All rights reserved.      ;;;
6 ;;;                                                                  ;;;
7 ;;; Original author: Scott McKay                                     ;;;
8 ;;;                                                                  ;;;
9 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
10
11 (in-package "PROTO-IMPL")
12
13
14 ;;; Protobuf serialization from Lisp objects
15
16 ;;; Serialization
17
18 ;; Serialize the object using the given protobuf type
19 (defun serialize-object-to-stream (object type &key (stream *standard-output*) visited)
20   "Serializes the object 'object' of type 'type' onto the stream 'stream'
21    using the wire format.
22    'type' is the Lisp name of a Protobufs message (usually the name of a 
23    Lisp class) or a 'protobuf-message'.
24    'visited' is a hash table used to cache object sizes. If it is supplied, it will be
25    cleared before it is used; otherwise, a fresh table will be created.
26    The return value is the buffer containing the serialized object. If the stream is
27    nil, the buffer is not actually written to anywhere."
28   (let* ((visited (let ((v (or visited (make-hash-table))))
29                     (clrhash v)
30                     v))
31          (size    (object-size object type visited))
32          (buffer  (make-array size :element-type '(unsigned-byte 8))))
33     (serialize-object object type buffer 0 visited)
34     (when stream
35       (write-sequence buffer stream))
36     buffer))
37
38 (defun serialize-object-to-file (filename object type &key visited)
39   (with-open-file (stream filename
40                    :direction :output
41                    :element-type '(unsigned-byte 8))
42     (serialize-object-to-stream object type :stream stream :visited visited)))
43
44 ;; Allow clients to add their own methods
45 ;; This is how we address the problem of cycles, e.g. -- if you have an object
46 ;; that may contain cycles, serialize the cyclic object using a "handle"
47 (defgeneric serialize-object (object type buffer &optional start visited)
48   (:documentation
49    "Serializes the object 'object' of type 'type' into the byte array 'buffer'
50     using the wire format.
51     'type' is the Lisp name of a Protobufs message (usually the name of a 
52     Lisp class) or a 'protobuf-message'.
53     The object is serialized into the byte array given by 'buffer' starting
54     at the fixnum index 'index' using the wire format.
55     'visited' is a hash table used to cache object sizes.
56     The return value is the buffer containing the serialized object."))
57
58 (defmethod serialize-object (object (type symbol) buffer &optional start visited)
59   (let ((message (find-message-for-class type)))
60     (assert message ()
61             "There is no Protobuf message having the type ~S" type)
62     (serialize-object object message buffer start visited)))
63
64 ;; 'visited' is used to cache object sizes
65 ;; If it's passed in explicitly, it is assumed to already have the sizes within it
66 ;; The default method uses metadata from the protobuf "schema" for the message
67 (defmethod serialize-object (object (message protobuf-message) buffer &optional start visited)
68   (declare (type (simple-array (unsigned-byte 8)) buffer))
69   (let ((visited (or visited (make-hash-table)))
70         (index   (or start 0)))
71     (declare (type fixnum index))
72     (macrolet ((read-slot (object slot reader)
73                  ;; Don't do a boundp check, we assume the object is fully populated
74                  ;; Unpopulated slots should be "nullable" and should contain nil
75                  `(if ,reader
76                     (funcall ,reader ,object)
77                     (slot-value ,object ,slot))))
78       (labels ((do-field (object trace field)
79                  ;; We don't do cycle detection here
80                  ;; If the client needs it, he can define his own 'serialize-object'
81                  ;; method to clean things up first
82                  (let* ((type   (if (eq (proto-class field) 'boolean) :bool (proto-class field)))
83                         (slot   (proto-value field))
84                         (reader (proto-reader field))
85                         msg)
86                    (when (or slot reader)
87                      (cond ((eq (proto-required field) :repeated)
88                             (cond ((and (proto-packed field) (packed-type-p type))
89                                    (let ((tag (make-tag type (proto-index field))))
90                                      (setq index (serialize-packed (read-slot object slot reader)
91                                                                    type tag buffer index))))
92                                   ((keywordp type)
93                                    (let ((tag (make-tag type (proto-index field))))
94                                      (map () #'(lambda (v)
95                                                  (setq index (serialize-prim v type tag buffer index)))
96                                              (read-slot object slot reader))))
97                                   ((typep (setq msg (and type (or (find-message trace type)
98                                                                   (find-enum trace type))))
99                                           'protobuf-message)
100                                    (if (eq (proto-message-type msg) :group)
101                                      (dolist (v (if slot (read-slot object slot reader) (list object)))
102                                        ;; To serialize a group, we encode a start tag,
103                                        ;; serialize the fields, then encode an end tag
104                                        (let ((tag1 (make-tag $wire-type-start-group (proto-index field)))
105                                              (tag2 (make-tag $wire-type-end-group   (proto-index field))))
106                                          (setq index (encode-uint32 tag1 buffer index))
107                                          (map () (curry #'do-field v msg)
108                                                  (proto-fields msg))
109                                          (setq index (encode-uint32 tag2 buffer index))))
110                                      (dolist (v (if slot (read-slot object slot reader) (list object)))
111                                        ;; To serialize an embedded message, first say that it's
112                                        ;; a string, then encode its size, then serialize its fields
113                                        (let ((tag (make-tag $wire-type-string (proto-index field)))
114                                              (len (object-size v msg visited)))
115                                          (setq index (encode-uint32 tag buffer index))
116                                          (setq index (encode-uint32 len buffer index)))
117                                        (map () (curry #'do-field v msg)
118                                                (proto-fields msg)))))
119                                   ((typep msg 'protobuf-enum)
120                                    (let ((tag (make-tag $wire-type-varint (proto-index field))))
121                                      (map () #'(lambda (v)
122                                                  (setq index (serialize-enum v (proto-values msg) tag buffer index)))
123                                              (read-slot object slot reader))))))
124                            (t
125                             (cond ((eq type :bool)
126                                    ;; We have to handle optional boolean fields specially
127                                    ;; because "false" and nil are the same value in Lisp
128                                    (let ((v (cond ((or (eq (proto-required field) :required)
129                                                        (null slot))
130                                                    (read-slot object slot reader))
131                                                   ((slot-boundp object slot)
132                                                    (read-slot object slot reader))
133                                                   (t :unbound))))
134                                      (unless (eq v :unbound)
135                                        (let ((tag (make-tag :bool (proto-index field))))
136                                          (setq index (serialize-prim v type tag buffer index))))))
137                                   ((keywordp type)
138                                    (let ((v (read-slot object slot reader)))
139                                      (when v
140                                        (let ((tag (make-tag type (proto-index field))))
141                                          (setq index (serialize-prim v type tag buffer index))))))
142                                   ((typep (setq msg (and type (or (find-message trace type)
143                                                                   (find-enum trace type))))
144                                           'protobuf-message)
145                                    (let ((v (if slot (read-slot object slot reader) object)))
146                                      (when v
147                                        (if (eq (proto-message-type msg) :group)
148                                          (let ((tag1 (make-tag $wire-type-start-group (proto-index field)))
149                                                (tag2 (make-tag $wire-type-end-group   (proto-index field))))
150                                            (setq index (encode-uint32 tag1 buffer index))
151                                            (map () (curry #'do-field v msg)
152                                                    (proto-fields msg))
153                                            (setq index (encode-uint32 tag2 buffer index)))
154                                          (let ((tag (make-tag $wire-type-string (proto-index field)))
155                                                (len (object-size v msg visited)))
156                                            (setq index (encode-uint32 tag buffer index))
157                                            (setq index (encode-uint32 len buffer index))
158                                            (map () (curry #'do-field v msg)
159                                                    (proto-fields msg)))))))
160                                   ((typep msg 'protobuf-enum)
161                                    (let ((v (read-slot object slot reader)))
162                                      (when v
163                                        (let ((tag (make-tag $wire-type-varint (proto-index field))))
164                                          (setq index (serialize-enum v (proto-values msg) tag buffer index)))))))))))))
165         (declare (dynamic-extent #'do-field))
166         (map () (curry #'do-field object message) (proto-fields message))))
167     (values buffer index)))
168
169
170 ;;; Deserialization
171
172 (defun deserialize-object-from-stream (type &key (stream *standard-input*))
173   "Deserializes an object of the given type 'type' as a Protobuf object.
174    'type' is the Lisp name of a Protobufs message (usually the name of a 
175    Lisp class) or a 'protobuf-message'.
176    The return value is the object."
177   (let* ((size    (file-length stream))
178          (buffer  (make-array size :element-type '(unsigned-byte 8))))
179     (read-sequence buffer stream)
180     (deserialize-object type buffer 0 size)))
181
182 (defun deserialize-object-from-file (type filename)
183   (with-open-file (stream filename
184                    :direction :input
185                    :element-type '(unsigned-byte 8))
186     (deserialize-object-from-stream type :stream stream)))
187
188 ;; Allow clients to add their own methods
189 ;; This is you might preserve object identity, e.g.
190 (defgeneric deserialize-object (type buffer &optional start end end-tag)
191   (:documentation
192    "Deserializes an object of the given type 'type' as a Protobufs object.
193     'type' is the Lisp name of a Protobufs message (usually the name of a 
194     Lisp class) or a 'protobuf-message'.
195     The encoded bytes are in the byte array given by 'buffer' starting at
196     the fixnum index 'start' up to the end of the buffer, given by 'end'.
197     'start' defaults to 0, 'end' defaults to the length of the buffer.
198     'end-tag' is used internally to handle the (deprecated) \"group\" feature.
199     The return values are the object and the index at which deserialization stopped.."))
200
201 (defmethod deserialize-object ((type symbol) buffer &optional start end (end-tag 0))
202   (let ((message (find-message-for-class type)))
203     (assert message ()
204             "There is no Protobuf message having the type ~S" type)
205     (deserialize-object message buffer start end end-tag)))
206
207 ;; The default method uses metadata from the protobuf "schema" for the message
208 (defmethod deserialize-object ((message protobuf-message) buffer &optional start end (end-tag 0))
209   (declare (type (simple-array (unsigned-byte 8)) buffer))
210   (let ((index   (or start 0))
211         (length  (or end (length buffer))))
212     (declare (type fixnum index length))
213     (macrolet ((read-slot (object slot reader)
214                  `(if ,reader
215                     (funcall ,reader ,object)
216                     (slot-value ,object ,slot)))
217                (write-slot (object slot writer value)
218                  `(if ,writer
219                     (funcall ,writer ,object ,value)
220                     (setf (slot-value ,object ,slot) ,value))))
221       (labels ((deserialize (type trace end end-tag)
222                  (declare (type fixnum end end-tag))
223                  (let* ((message (find-message trace type))
224                         (object  (and message
225                                       (make-instance (or (proto-alias-for message) (proto-class message)))))
226                         ;; All the slots into which we store a repeated element
227                         ;; These will be reversed at the end of deserialization
228                         (rslots ()))
229                    (loop
230                      (multiple-value-bind (tag idx)
231                          (if (i< index end) (decode-uint32 buffer index) (values 0 index))
232                        ;; We're done if we've gotten to the end index or
233                        ;; we see an end tag that matches a previous group's start tag
234                        ;; Note that the default end tag is 0, which is also an end of
235                        ;; message marker (there can never be "real" zero tags because
236                        ;; field indices start at 1)
237                        (setq index idx)
238                        (when (i= tag end-tag)
239                          ;; Reverse the repeated slots
240                          (dolist (field rslots)
241                            (let ((slot   (proto-value field))
242                                  (reader (proto-reader field))
243                                  (writer (proto-writer field)))
244                              (write-slot object slot writer
245                                          (nreverse (read-slot object slot reader)))))
246                          (return-from deserialize
247                            (values object index)))
248                        (let* ((fidx  (ilogand (iash tag -3) #x1FFFFFFF))
249                               (field (find fidx (proto-fields message) :key #'proto-index))
250                               (type  (and field (if (eq (proto-class field) 'boolean) :bool (proto-class field))))
251                               ;; It's OK for this to be null
252                               ;; That means we're parsing some version of a message
253                               ;; that has the field, but our current message does not
254                               ;; We still have to deserialize everything, though
255                               (slot   (and field (proto-value field)))
256                               (reader (and field (proto-reader field)))
257                               (writer (and field (proto-writer field)))
258                               msg)
259                          (if (null field)
260                            ;; If there's no field descriptor for this index, just skip
261                            ;; the next element in the buffer having the given wire type
262                            (setq index (skip-element buffer index tag))
263                            ;;--- Check for mismatched wire type, running past end of buffer, etc
264                            (cond ((and field (eq (proto-required field) :repeated))
265                                   (cond ((and (proto-packed field) (packed-type-p type))
266                                          (multiple-value-bind (values idx)
267                                              (deserialize-packed type buffer index)
268                                            (setq index idx)
269                                            (when slot
270                                              (write-slot object slot writer values))))
271                                         ((keywordp type)
272                                          (multiple-value-bind (val idx)
273                                              (deserialize-prim type buffer index)
274                                            (setq index idx)
275                                            (when slot
276                                              (pushnew field rslots)
277                                              ;; This "push" will type-check the entire list for
278                                              ;; 'quux:list-of', so avoid using that type in classes
279                                              ;; in Protobufs if performance is an issue
280                                              ;; We'll reverse the slots at the last minute
281                                              (write-slot object slot writer
282                                                          (cons val (read-slot object slot reader))))))
283                                         ((typep (setq msg (and type (or (find-message trace type)
284                                                                         (find-enum trace type))))
285                                                 'protobuf-message)
286                                          (if (eq (proto-message-type msg) :group)
287                                            (let* ((etag (make-tag $wire-type-end-group fidx))
288                                                   (obj (deserialize type msg length etag)))
289                                              (when slot
290                                                (pushnew field rslots)
291                                                (write-slot object slot writer
292                                                            (cons obj (read-slot object slot reader)))))
293                                            (multiple-value-bind (len idx)
294                                                (decode-uint32 buffer index)
295                                              (setq index idx)
296                                              (let ((obj (deserialize type msg (+ index len) 0)))
297                                                (when slot
298                                                  (pushnew field rslots)
299                                                  (write-slot object slot writer
300                                                              (cons obj (read-slot object slot reader))))))))
301                                         ((typep msg 'protobuf-enum)
302                                          (multiple-value-bind (val idx)
303                                              (deserialize-enum (proto-values msg) buffer index)
304                                            (setq index idx)
305                                            (when slot
306                                              (pushnew field rslots)
307                                              (write-slot object slot writer
308                                                          (cons val (read-slot object slot reader))))))))
309                                  (t
310                                   (cond ((keywordp type)
311                                          (multiple-value-bind (val idx)
312                                              (deserialize-prim type buffer index)
313                                            (setq index idx)
314                                            (when slot
315                                              (write-slot object slot writer val))))
316                                         ((typep (setq msg (and type (or (find-message trace type)
317                                                                         (find-enum trace type))))
318                                                 'protobuf-message)
319                                          (if (eq (proto-message-type msg) :group)
320                                            (let* ((etag (make-tag $wire-type-end-group fidx))
321                                                   (obj (deserialize type msg length etag)))
322                                              (when slot
323                                                (write-slot object slot writer obj)))
324                                            (multiple-value-bind (len idx)
325                                                (decode-uint32 buffer index)
326                                              (setq index idx)
327                                              (let ((obj (deserialize type msg (+ index len) 0)))
328                                                (when slot
329                                                  (write-slot object slot writer obj))))))
330                                         ((typep msg 'protobuf-enum)
331                                          (multiple-value-bind (val idx)
332                                              (deserialize-enum (proto-values msg) buffer index)
333                                            (setq index idx)
334                                            (when slot
335                                              (write-slot object slot writer val))))))))))))))
336         (declare (dynamic-extent #'deserialize))
337         (deserialize (proto-class message) message length end-tag)))))
338
339 ;;; Object sizes
340
341 ;; Allow clients to add their own methods
342 ;; This is how we address the problem of cycles, e.g. -- if you have an object
343 ;; that may contain cycles, return the size of the "handle" to the object
344 (defgeneric object-size (object type &optional visited)
345   (:documentation
346    "Computes the size in bytes of the object 'object' of type 'type'.
347     'type' is the Lisp name of a Protobufs message (usually the name of a 
348     Lisp class) or a 'protobuf-message'.
349     'visited' is a hash table used to cache object sizes.
350     The return value is the size of the object in bytes."))
351
352 (defmethod object-size (object (type symbol) &optional visited)
353   (let ((message (find-message-for-class type)))
354     (assert message ()
355             "There is no Protobuf message having the type ~S" type)
356     (object-size object message visited)))
357
358 ;; 'visited' is used to cache object sizes
359 ;; The default method uses metadata from the protobuf "schema" for the message
360 (defmethod object-size (object (message protobuf-message) &optional visited)
361   (let ((size (and visited (gethash object visited))))
362     (when size
363       (return-from object-size size)))
364   (let ((size 0))
365     (declare (type fixnum size))
366     (macrolet ((read-slot (object slot reader)
367                  ;; Don't do a boundp check, we assume the object is fully populated
368                  ;; Unpopulated slots should be "nullable" and should contain nil
369                  `(if ,reader
370                     (funcall ,reader ,object)
371                     (slot-value ,object ,slot))))
372       (labels ((do-field (object trace field)
373                  ;; We don't do cycle detection here
374                  ;; If the client needs it, he can define his own 'object-size'
375                  ;; method to clean things up first
376                  (let* ((type   (if (eq (proto-class field) 'boolean) :bool (proto-class field)))
377                         (slot   (proto-value field))
378                         (reader (proto-reader field))
379                         msg)
380                    (when (or slot reader)
381                      (cond ((eq (proto-required field) :repeated)
382                             (cond ((and (proto-packed field) (packed-type-p type))
383                                    (let ((tag (make-tag type (proto-index field))))
384                                      (iincf size (packed-size (read-slot object slot reader) type tag))))
385                                   ((keywordp type)
386                                    (let ((tag (make-tag type (proto-index field))))
387                                      (map () #'(lambda (v)
388                                                  (iincf size (prim-size v type tag)))
389                                              (read-slot object slot reader))))
390                                   ((typep (setq msg (and type (or (find-message trace type)
391                                                                   (find-enum trace type))))
392                                           'protobuf-message)
393                                    (if (eq (proto-message-type msg) :group)
394                                      (dolist (v (if slot (read-slot object slot reader) (list object)))
395                                        (let ((tag1 (make-tag $wire-type-start-group (proto-index field)))
396                                              (tag2 (make-tag $wire-type-end-group   (proto-index field))))
397                                          (iincf size (length32 tag1))
398                                          (map () (curry #'do-field v msg)
399                                                  (proto-fields msg))
400                                          (iincf size (length32 tag2))))
401                                      (dolist (v (if slot (read-slot object slot reader) (list object)))
402                                        (let ((tag (make-tag $wire-type-string (proto-index field)))
403                                              (len (object-size v msg visited)))
404                                          (iincf size (length32 tag))
405                                          (iincf size (length32 len))
406                                          (map () (curry #'do-field v msg)
407                                                  (proto-fields msg))))))
408                                   ((typep msg 'protobuf-enum)
409                                    (let ((tag (make-tag $wire-type-varint (proto-index field))))
410                                      (map () #'(lambda (v)
411                                                  (iincf size (enum-size v (proto-values msg) tag)))
412                                              (read-slot object slot reader))))))
413                            (t
414                             (cond ((eq type :bool)
415                                    (let ((v (cond ((or (eq (proto-required field) :required)
416                                                        (null slot))
417                                                    (read-slot object slot reader))
418                                                   ((slot-boundp object slot)
419                                                    (read-slot object slot reader))
420                                                   (t :unbound))))
421                                      (unless (eq v :unbound)
422                                        (let ((tag (make-tag :bool (proto-index field))))
423                                          (iincf size (prim-size v type tag))))))
424                                   ((keywordp type)
425                                    (let ((v (read-slot object slot reader)))
426                                      (when v
427                                        (let ((tag (make-tag type (proto-index field))))
428                                          (iincf size (prim-size v type tag))))))
429                                   ((typep (setq msg (and type (or (find-message trace type)
430                                                                   (find-enum trace type))))
431                                           'protobuf-message)
432                                    (let ((v (if slot (read-slot object slot reader) object)))
433                                      (when v
434                                        (if (eq (proto-message-type msg) :group)
435                                          (let ((tag1 (make-tag $wire-type-start-group (proto-index field)))
436                                              (tag2 (make-tag $wire-type-end-group   (proto-index field))))
437                                            (iincf size (length32 tag1))
438                                            (map () (curry #'do-field v msg)
439                                                    (proto-fields msg))
440                                            (iincf size (length32 tag2)))
441                                          (let ((tag (make-tag $wire-type-string (proto-index field)))
442                                                (len (object-size v msg visited)))
443                                            (iincf size (length32 tag))
444                                            (iincf size (length32 len))
445                                            (map () (curry #'do-field v msg)
446                                                 (proto-fields msg)))))))
447                                   ((typep msg 'protobuf-enum)
448                                    (let ((v (read-slot object slot reader)))
449                                      (when v
450                                        (let ((tag (make-tag $wire-type-varint (proto-index field))))
451                                          (iincf size (enum-size (read-slot object slot reader) (proto-values msg) tag)))))))))))))
452         (declare (dynamic-extent #'do-field))
453         (map () (curry #'do-field object message) (proto-fields message))
454         (when visited
455           (setf (gethash object visited) size))   ;cache the size
456         size))))
457
458 \f
459 ;;; Compile-time generation of serializers
460 ;;; Type-checking is done at the top-level methods specialized on 'symbol',
461 ;;; so we turn off all type checking at the level of these functions
462
463 ;; Note well: keep this in sync with the main 'serialize-object' method above
464 (defun generate-serializer (message)
465   "Generate a 'serialize-object' method for the given message."
466   (with-gensyms (vobj vbuf vidx vval vclass)
467     (with-collectors ((serializers collect-serializer))
468       (dolist (field (proto-fields message))
469         (let* ((class  (if (eq (proto-class field) 'boolean) :bool (proto-class field)))
470                (msg    (and class (not (keywordp class))
471                             (or (find-message message class)
472                                 (find-enum message class))))
473                (reader (cond ((proto-reader field)
474                               `(,(proto-reader field) ,vobj))
475                              ((proto-value field)
476                               `(slot-value ,vobj ',(proto-value field)))))
477                (index  (proto-index field)))
478           (when reader
479             (cond ((eq (proto-required field) :repeated)
480                    (cond ((and (proto-packed field) (packed-type-p class))
481                           (collect-serializer
482                            (let ((tag (make-tag class index)))
483                              `(setq ,vidx (serialize-packed ,reader ,class ,tag ,vbuf ,vidx)))))
484                          ((keywordp class)
485                           (collect-serializer
486                            (let ((tag (make-tag class index)))
487                              `(dolist (,vval ,reader)
488                                 (setq ,vidx (serialize-prim ,vval ,class ,tag ,vbuf ,vidx))))))
489                          ((typep msg 'protobuf-message)
490                           (collect-serializer
491                            (if (eq (proto-message-type msg) :group)
492                              (let ((tag1 (make-tag $wire-type-start-group index))
493                                    (tag2 (make-tag $wire-type-end-group   index)))
494                                `(dolist (,vval ,reader)
495                                   (let ((len (or (and visited (gethash ,vval visited))
496                                                  (object-size ,vval ,msg visited))))
497                                     (setq ,vidx (encode-uint32 ,tag1 ,vbuf ,vidx))
498                                     (serialize-object ,vval ,msg ,vbuf ,vidx visited)
499                                     (iincf ,vidx len)
500                                     (setq ,vidx (encode-uint32 ,tag2 ,vbuf ,vidx)))))
501                              (let ((tag (make-tag $wire-type-string index)))
502                                `(dolist (,vval ,reader)
503                                   (let ((len (or (and visited (gethash ,vval visited))
504                                                  (object-size ,vval ,msg visited))))
505                                     (setq ,vidx (encode-uint32 ,tag ,vbuf ,vidx))
506                                     (setq ,vidx (encode-uint32 len ,vbuf ,vidx))
507                                     (serialize-object ,vval ,msg ,vbuf ,vidx visited)
508                                     (iincf ,vidx len)))))))
509                          ((typep msg 'protobuf-enum)
510                           (collect-serializer
511                            (let ((tag (make-tag $wire-type-varint index)))
512                              `(dolist (,vval ,reader)
513                                 (setq ,vidx (serialize-enum ,vval '(,@(proto-values msg)) ,tag ,vbuf ,vidx))))))))
514                   (t
515                    (cond ((keywordp class)
516                           (collect-serializer
517                            (let ((tag (make-tag class index)))
518                              (if (eq class :bool)
519                                (if (or (eq (proto-required field) :required)
520                                        (null (proto-value field)))
521                                  `(let ((,vval ,reader))
522                                     (setq ,vidx (serialize-prim ,vval ,class ,tag ,vbuf ,vidx)))
523                                  `(let ((,vval (cond ((slot-boundp ,vobj ',(proto-value field))
524                                                       ,reader)
525                                                      (t :unbound))))
526                                     (unless (eq ,vval :unbound)
527                                       (setq ,vidx (serialize-prim ,vval ,class ,tag ,vbuf ,vidx)))))
528                                `(let ((,vval ,reader))
529                                   (when ,vval
530                                     (setq ,vidx (serialize-prim ,vval ,class ,tag ,vbuf ,vidx))))))))
531                          ((typep msg 'protobuf-message)
532                           (collect-serializer
533                            (if (eq (proto-message-type msg) :group)
534                              (let ((tag1 (make-tag $wire-type-start-group index))
535                                    (tag2 (make-tag $wire-type-end-group   index)))
536                                `(let ((,vval ,reader))
537                                   (when ,vval
538                                     (let ((len (or (and visited (gethash ,vval visited))
539                                                    (object-size ,vval ,msg visited))))
540                                       (setq ,vidx (encode-uint32 ,tag1 ,vbuf ,vidx))
541                                       (serialize-object ,vval ,msg ,vbuf ,vidx visited)
542                                       (iincf ,vidx len)
543                                       (setq ,vidx (encode-uint32 ,tag2 ,vbuf ,vidx))))))
544                              (let ((tag (make-tag $wire-type-string index)))
545                                `(let ((,vval ,reader))
546                                   (when ,vval
547                                     (let ((len (or (and visited (gethash ,vval visited))
548                                                    (object-size ,vval ,msg visited))))
549                                       (setq ,vidx (encode-uint32 ,tag ,vbuf ,vidx))
550                                       (setq ,vidx (encode-uint32 len ,vbuf ,vidx))
551                                       (serialize-object ,vval ,msg ,vbuf ,vidx visited)
552                                       (iincf ,vidx len))))))))
553                          ((typep msg 'protobuf-enum)
554                           (collect-serializer
555                            (let ((tag (make-tag $wire-type-varint index)))
556                              `(let ((,vval ,reader))
557                                 (when ,vval
558                                   (setq ,vidx (serialize-enum ,vval '(,@(proto-values msg)) ,tag ,vbuf ,vidx)))))))))))))
559       `(defmethod serialize-object
560            (,vobj (,vclass (eql ,message)) ,vbuf &optional (,vidx 0) visited)
561          (declare (optimize (speed 3) (safety 0) (debug 0)))
562          (declare (ignorable visited)
563                   (type (simple-array (unsigned-byte 8)) ,vbuf)
564                   (type fixnum ,vidx))
565          ,@serializers
566          (values ,vbuf ,vidx)))))
567
568 ;; Note well: keep this in sync with the main 'deserialize-object' method above
569 (defun generate-deserializer (message)
570   "Generate a 'deserialize-object' method for the given message."
571   (with-gensyms (vclass vbuf vidx vlen vendtag vobj vval)
572     (with-collectors ((deserializers collect-deserializer)
573                       ;; For tracking repeated slots that will need to be reversed
574                       (rslots collect-rslot))
575       (flet ((read-slot (object field)
576                (cond ((proto-reader field)
577                       `(,(proto-reader field) ,object))
578                      ((proto-value field)
579                       `(slot-value ,object ',(proto-value field)))))
580              (write-slot (object field value)
581                (cond ((proto-writer field)
582                       `(,(proto-writer field) ,object ,value))
583                      ((proto-value field)
584                       `(setf (slot-value ,object ',(proto-value field)) ,value)))))
585         (dolist (field (proto-fields message))
586           (let* ((class  (if (eq (proto-class field) 'boolean) :bool (proto-class field)))
587                  (msg    (and class (not (keywordp class))
588                               (or (find-message message class)
589                                   (find-enum message class))))
590                  (index  (proto-index field)))
591             (cond ((eq (proto-required field) :repeated)
592                    (cond ((and (proto-packed field) (packed-type-p class))
593                           (collect-deserializer
594                            `((,(make-tag class index))
595                              (multiple-value-bind (,vval idx)
596                                  (deserialize-packed ,class ,vbuf ,vidx)
597                                (setq ,vidx idx)
598                                ,(write-slot vobj field vval)))))
599                          ((keywordp class)
600                           (let ((temp (gensym (string (proto-value field)))))
601                             (collect-rslot (list field temp))
602                             (collect-deserializer
603                              `((,(make-tag class index))
604                                (multiple-value-bind (,vval idx)
605                                    (deserialize-prim ,class ,vbuf ,vidx)
606                                  (setq ,vidx idx)
607                                  (push ,vval ,temp))))))
608                          ((typep msg 'protobuf-message)
609                           (let ((temp (gensym (string (proto-value field)))))
610                             (collect-rslot (list field temp))
611                             (collect-deserializer
612                              (if (eq (proto-message-type msg) :group)
613                                `((,(make-tag $wire-type-start-group index))
614                                  (multiple-value-bind (,vval idx)
615                                      (deserialize-object ',class ,vbuf ,vidx ,vlen
616                                                          ,(make-tag $wire-type-end-group index))
617                                    (setq ,vidx idx)
618                                    (push ,vval ,temp)))
619                                `((,(make-tag $wire-type-string index))
620                                  ;; Call 'deserialize-object' with the name of the message
621                                  ;; class so that we preferentially get any optimized version
622                                  ;; of the method
623                                  (multiple-value-bind (len idx)
624                                      (decode-uint32 ,vbuf ,vidx)
625                                    (setq ,vidx idx)
626                                    (multiple-value-bind (,vval idx)
627                                        (deserialize-object ',class ,vbuf ,vidx (i+ ,vidx len) 0)
628                                      (setq ,vidx idx)
629                                      (push ,vval ,temp))))))))
630                          ((typep msg 'protobuf-enum)
631                           (let ((temp (gensym (string (proto-value field)))))
632                             (collect-rslot (list field temp))
633                             (collect-deserializer
634                              `((,(make-tag $wire-type-varint index))
635                                (multiple-value-bind (,vval idx)
636                                    (deserialize-enum '(,@(proto-values msg)) ,vbuf ,vidx)
637                                  (setq ,vidx idx)
638                                  (push ,vval ,temp))))))))
639                   (t
640                    (cond ((keywordp class)
641                           (collect-deserializer
642                            `((,(make-tag class index))
643                              (multiple-value-bind (,vval idx)
644                                  (deserialize-prim ,class ,vbuf ,vidx)
645                                (setq ,vidx idx)
646                                ,(write-slot vobj field vval)))))
647                          ((typep msg 'protobuf-message)
648                           (collect-deserializer
649                            (if (eq (proto-message-type msg) :group)
650                              `((,(make-tag $wire-type-start-group index))
651                                (multiple-value-bind (,vval idx)
652                                    (deserialize-object ',class ,vbuf ,vidx  ,vlen
653                                                        ,(make-tag $wire-type-end-group index))
654                                  (setq ,vidx idx)
655                                  ,(write-slot vobj field vval)))
656                              `((,(make-tag $wire-type-string index))
657                                (multiple-value-bind (len idx)
658                                    (decode-uint32 ,vbuf ,vidx)
659                                  (setq ,vidx idx)
660                                  (multiple-value-bind (,vval idx)
661                                      (deserialize-object ',class ,vbuf ,vidx (i+ ,vidx len) 0)
662                                    (setq ,vidx idx)
663                                    ,(write-slot vobj field vval)))))))
664                          ((typep msg 'protobuf-enum)
665                           (collect-deserializer
666                            `((,(make-tag $wire-type-varint index))
667                              (multiple-value-bind (,vval idx)
668                                  (deserialize-enum '(,@(proto-values msg)) ,vbuf ,vidx)
669                                (setq ,vidx idx)
670                                ,(write-slot vobj field vval)))))))))))
671       (let* ((rslots  (delete-duplicates rslots :key #'first))
672              (rfields (mapcar #'first  rslots))
673              (rtemps  (mapcar #'second rslots)))
674         `(defmethod deserialize-object
675              ((,vclass (eql ,message)) ,vbuf &optional ,vidx ,vlen (,vendtag 0))
676            (declare (optimize (speed 3) (safety 0) (debug 0)))
677            (declare (type (simple-array (unsigned-byte 8)) ,vbuf))
678            (let ((,vidx (or ,vidx 0))
679                  (,vlen (or ,vlen (length ,vbuf))))
680              (declare (type fixnum ,vidx ,vlen))
681              (let ((,vobj (make-instance ',(or (proto-alias-for message) (proto-class message))))
682                    ;; Bind the temporary variables that hold repeated slots
683                    ,@rtemps)
684                (loop
685                  (multiple-value-bind (tag idx)
686                      (if (i< ,vidx ,vlen) (decode-uint32 ,vbuf ,vidx) (values 0 ,vidx))
687                    (setq ,vidx idx)
688                    (when (i= tag ,vendtag)
689                      ;; Set the (un)reversed values of the repeated slots
690                      ,@(loop for field in rfields
691                              for temp in rtemps
692                              as slot = (proto-value field)
693                              as writer = (proto-writer field)
694                              collect (if writer
695                                        `(funcall ,writer ,vobj (nreverse ,temp))
696                                        `(setf (slot-value ,vobj ',slot) (nreverse ,temp))))
697                      (return-from deserialize-object
698                        (values ,vobj ,vidx)))
699                    (case tag
700                      ,@deserializers
701                      (otherwise
702                       (setq ,vidx (skip-element ,vbuf ,vidx tag)))))))))))))
703
704 ;; Note well: keep this in sync with the main 'object-size' method above
705 (defun generate-object-size (message)
706   "Generate an 'object-size' method for the given message."
707   (with-gensyms (vobj vsize vval vclass)
708     (with-collectors ((sizers collect-sizer))
709       (dolist (field (proto-fields message))
710         (let* ((class  (if (eq (proto-class field) 'boolean) :bool (proto-class field)))
711                (msg    (and class (not (keywordp class))
712                             (or (find-message message class)
713                                 (find-enum message class))))
714                (reader (cond ((proto-reader field)
715                               `(,(proto-reader field) ,vobj))
716                              ((proto-value field)
717                               `(slot-value ,vobj ',(proto-value field)))))
718                (index  (proto-index field)))
719           (when reader
720             (cond ((eq (proto-required field) :repeated)
721                    (cond ((and (proto-packed field) (packed-type-p class))
722                           (collect-sizer
723                            (let ((tag (make-tag class index)))
724                              `(iincf ,vsize (packed-size ,reader ,class ,tag)))))
725                          ((keywordp class)
726                           (collect-sizer
727                            (let ((tag (make-tag class index)))
728                              `(dolist (,vval ,reader)
729                                 (iincf ,vsize (prim-size ,vval ,class ,tag))))))
730                          ((typep msg 'protobuf-message)
731                           (collect-sizer
732                            (if (eq (proto-message-type msg) :group)
733                              (let ((tag1 (make-tag $wire-type-start-group index))
734                                    (tag2 (make-tag $wire-type-end-group   index)))
735                                `(dolist (,vval ,reader)
736                                   (let ((len (or (and visited (gethash ,vval visited))
737                                                  (object-size ,vval ,msg visited))))
738                                     (iincf ,vsize (length32 ,tag1))
739                                     (iincf ,vsize len)
740                                     (iincf ,vsize ,tag2))))
741                              (let ((tag (make-tag $wire-type-string index)))
742                                `(dolist (,vval ,reader)
743                                   (let ((len (or (and visited (gethash ,vval visited))
744                                                  (object-size ,vval ,msg visited))))
745                                     (iincf ,vsize (length32 ,tag))
746                                     (iincf ,vsize (length32 len))
747                                     (iincf ,vsize len)))))))
748                          ((typep msg 'protobuf-enum)
749                           (let ((tag (make-tag $wire-type-varint index)))
750                             (collect-sizer
751                              `(dolist (,vval ,reader)
752                                 (iincf ,vsize (enum-size ,vval '(,@(proto-values msg)) ,tag))))))))
753                   (t
754                    (cond ((keywordp class)
755                           (let ((tag (make-tag class index)))
756                             (collect-sizer
757                              (if (eq class :bool)
758                                (if (or (eq (proto-required field) :required)
759                                        (null (proto-value field)))
760                                  `(let ((,vval ,reader))
761                                     (declare (ignorable ,vval))
762                                     (iincf ,vsize (prim-size ,vval ,class ,tag)))
763                                  `(let ((,vval (cond ((slot-boundp ,vobj ',(proto-value field))
764                                                       ,reader)
765                                                      (t :unbound))))
766                                     (unless (eq ,vval :unbound)
767                                       (iincf ,vsize (prim-size ,vval ,class ,tag)))))
768                                `(let ((,vval ,reader))
769                                   (when ,vval
770                                     (iincf ,vsize (prim-size ,vval ,class ,tag))))))))
771                          ((typep msg 'protobuf-message)
772                           (collect-sizer
773                            (if (eq (proto-message-type msg) :group)
774                              (let ((tag1 (make-tag $wire-type-start-group index))
775                                    (tag2 (make-tag $wire-type-end-group   index)))
776                                `(let ((,vval ,reader))
777                                   (when ,vval
778                                     (let ((len (or (and visited (gethash ,vval visited))
779                                                    (object-size ,vval ,msg visited))))
780                                       (iincf ,vsize (length32 ,tag1))
781                                       (iincf ,vsize len)
782                                       (iincf ,vsize (length32 ,tag2))))))
783                              (let ((tag (make-tag $wire-type-string index)))
784                                `(let ((,vval ,reader))
785                                   (when ,vval
786                                     (let ((len (or (and visited (gethash ,vval visited))
787                                                    (object-size ,vval ,msg visited))))
788                                       (iincf ,vsize (length32 ,tag))
789                                       (iincf ,vsize (length32 len))
790                                       (iincf ,vsize len))))))))
791                          ((typep msg 'protobuf-enum)
792                           (let ((tag (make-tag $wire-type-varint index)))
793                             (collect-sizer
794                              `(let ((,vval ,reader))
795                                 (when ,vval
796                                   (iincf ,vsize (enum-size ,vval '(,@(proto-values msg)) ,tag)))))))))))))
797       `(defmethod object-size
798            (,vobj (,vclass (eql ,message)) &optional visited)
799            (declare (optimize (speed 3) (safety 0) (debug 0)))
800          (declare (ignorable visited))
801          (let ((,vsize (and visited (gethash ,vobj visited))))
802            (when ,vsize
803              (return-from object-size ,vsize)))
804          (let ((,vsize 0))
805            (declare (type fixnum ,vsize))
806            ,@sizers
807            (when visited
808              (setf (gethash ,vobj visited) ,vsize))
809            ,vsize)))))