]> asedeno.scripts.mit.edu Git - cl-protobufs.git/blob - text-format.lisp
Rename these files to avoid name clashes
[cl-protobufs.git] / text-format.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 ;;; Print objects using Protobufs text format
15
16 (defvar *suppress-line-breaks* nil
17   "When true, don't generate line breaks in the text format")
18
19 (defgeneric print-text-format (object &optional type &key stream suppress-line-breaks)
20   (:documentation
21    "Prints the object 'object' of type 'type' onto the stream 'stream' using the
22     textual format.
23     If 'suppress-line-breaks' is true, all the output is put on a single line."))
24
25 (defmethod print-text-format (object &optional type
26                               &key (stream *standard-output*)
27                                    (suppress-line-breaks *suppress-line-breaks*))
28   (let* ((type    (or type (type-of object)))
29          (message (find-message-for-class type)))
30     (assert message ()
31             "There is no Protobuf message having the type ~S" type)
32     (macrolet ((read-slot (object slot reader)
33                  ;; Don't do a boundp check, we assume the object is fully populated
34                  ;; Unpopulated slots should be "nullable" and should contain nil
35                  `(if ,reader
36                     (funcall ,reader ,object)
37                     (slot-value ,object ,slot))))
38       (labels ((do-field (object trace indent field)
39                  ;; We don't do cycle detection here
40                  ;; If the client needs it, he can define his own 'print-text-format'
41                  ;; method to clean things up first
42                  (let* ((type   (if (eq (proto-class field) 'boolean) :bool (proto-class field)))
43                         (slot   (proto-value field))
44                         (reader (proto-reader field))
45                         msg)
46                    (when (or slot reader)
47                      (cond ((eq (proto-required field) :repeated)
48                             (cond ((keywordp type)
49                                    (map () #'(lambda (v)
50                                                (print-prim v type field stream
51                                                            (or suppress-line-breaks indent)))
52                                            (read-slot object slot reader)))
53                                   ((typep (setq msg (and type (or (find-message trace type)
54                                                                   (find-enum trace type))))
55                                           'protobuf-message)
56                                    (let ((values (if slot (read-slot object slot reader) (list object))))
57                                      (when values
58                                        (let ((indent (+ indent 2)))
59                                          (dolist (v values)
60                                            (if suppress-line-breaks
61                                              (format stream "~A { " (proto-name field))
62                                              (format stream "~&~VT~A {~%" indent (proto-name field)))
63                                            (map () (curry #'do-field v msg indent)
64                                                    (proto-fields msg))
65                                            (if suppress-line-breaks
66                                              (format stream "} ")
67                                              (format stream "~&~VT}~%" indent)))))))
68                                   ((typep msg 'protobuf-enum)
69                                    (map () #'(lambda (v)
70                                                (print-enum v msg field stream
71                                                            (or suppress-line-breaks indent)))
72                                            (read-slot object slot reader)))))
73                            (t
74                             (cond ((eq type :bool)
75                                    (let ((v (cond ((or (eq (proto-required field) :required)
76                                                        (null slot))
77                                                    (read-slot object slot reader))
78                                                   ((slot-boundp object slot)
79                                                    (read-slot object slot reader))
80                                                   (t :unbound))))
81                                      (unless (eq v :unbound)
82                                        (print-prim v type field stream
83                                                    (or suppress-line-breaks indent)))))
84                                   ((keywordp type)
85                                    (let ((v (read-slot object slot reader)))
86                                      (when v
87                                        (print-prim v type field stream
88                                                    (or suppress-line-breaks indent)))))
89                                   ((typep (setq msg (and type (or (find-message trace type)
90                                                                   (find-enum trace type))))
91                                           'protobuf-message)
92                                    (let ((v (if slot (read-slot object slot reader) object)))
93                                      (when v
94                                        (let ((indent (+ indent 2)))
95                                          (if suppress-line-breaks
96                                              (format stream "~A { " (proto-name field))
97                                              (format stream "~&~VT~A {~%" indent (proto-name field)))
98                                          (map () (curry #'do-field v msg indent)
99                                                  (proto-fields msg))
100                                          (if suppress-line-breaks
101                                              (format stream "} ")
102                                              (format stream "~&~VT}~%" indent))))))
103                                   ((typep msg 'protobuf-enum)
104                                    (let ((v (read-slot object slot reader)))
105                                      (when v
106                                        (print-enum v msg field stream
107                                                    (or suppress-line-breaks indent))))))))))))
108         (declare (dynamic-extent #'do-field))
109         (if suppress-line-breaks
110           (format stream "~A { " (proto-name message))
111           (format stream "~&~A {~%" (proto-name message)))
112         (map () (curry #'do-field object message 0) (proto-fields message))
113         (if suppress-line-breaks
114           (format stream "}")
115           (format stream "~&}~%"))
116         nil))))
117
118 (defun print-prim (val type field stream indent)
119   (when (or val (eq type :bool))
120     (if (eq indent 't)
121       (format stream "~A: " (proto-name field))
122       (format stream "~&~VT~A: " (+ indent 2) (proto-name field)))
123     (ecase type
124       ((:int32 :uint32 :int64 :uint64 :sint32 :sint64
125         :fixed32 :sfixed32 :fixed64 :sfixed64)
126        (format stream "~D" val))
127       ((:string)
128        (format stream "\"~A\"" val))
129       ((:bytes)
130        (format stream "~S" val))
131       ((:bool)
132        (format stream "~A" (if val "true" "false")))
133       ((:float :double)
134        (format stream "~D" val))
135       ;; A few of our homegrown types
136       ((:symbol)
137        (let ((val (if (keywordp val)
138                     (string val)
139                     (format nil "~A:~A" (package-name (symbol-package val)) (symbol-name val)))))
140          (format stream "\"~A\"" val)))
141       ((:date :time :datetime :timestamp)
142        (format stream "~D" val)))
143     (if (eq indent 't)
144       (format stream " ")
145       (format stream "~%"))))
146
147 (defun print-enum (val enum field stream indent)
148   (when val
149     (if (eq indent 't)
150       (format stream "~A: " (proto-name field))
151       (format stream "~&~VT~A: " (+ indent 2) (proto-name field)))
152     (let ((name (let ((e (find val (proto-values enum) :key #'proto-value)))
153                   (and e (proto-name e)))))
154       (format stream "~A" name)
155       (if (eq indent 't)
156         (format stream " ")
157         (format stream "~%")))))
158
159
160 ;;; Parse objects that were serialized using the text format
161
162 (defgeneric parse-text-format (type &key stream)
163   (:documentation
164    "Parses an object of type 'type' from the stream 'stream' using the textual format."))
165
166 (defmethod parse-text-format ((type symbol) &key (stream *standard-input*))
167   (let ((message (find-message-for-class type)))
168     (assert message ()
169             "There is no Protobuf message having the type ~S" type)
170     (parse-text-format message :stream stream)))
171
172 (defmethod parse-text-format ((message protobuf-message) &key (stream *standard-input*))
173   (let ((name (parse-token stream)))
174     (assert (string= name (proto-name message)) ()
175             "The message is not of the expected type ~A" (proto-name message)))
176   (labels ((deserialize (type trace)
177              (let* ((message (find-message trace type))
178                     (object  (and message
179                                   (make-instance (or (proto-alias-for message) (proto-class message)))))
180                     (rslots ()))
181                (expect-char stream #\{)
182                (loop
183                  (skip-whitespace stream)
184                  (when (eql (peek-char nil stream nil) #\})
185                    (read-char stream)
186                    (dolist (slot rslots)
187                      (setf (slot-value object slot) (nreverse (slot-value object slot))))
188                    (return-from deserialize object))
189                  (let* ((name  (parse-token stream))
190                         (field (and name (find-field message name)))
191                         (type  (and field (if (eq (proto-class field) 'boolean) :bool (proto-class field))))
192                         (slot  (and field (proto-value field)))
193                         msg)
194                    (if (null field)
195                      (skip-field stream)
196                      (cond ((and field (eq (proto-required field) :repeated))
197                             (cond ((keywordp type)
198                                    (expect-char stream #\:)
199                                    (let ((val (case type
200                                                 ((:float :double) (parse-float stream))
201                                                 ((:string) (parse-string stream))
202                                                 ((:bool)   (if (boolean-true-p (parse-token stream)) t nil))
203                                                 (otherwise (parse-signed-int stream)))))
204                                      (when slot
205                                        (pushnew slot rslots)
206                                        (push val (slot-value object slot)))))
207                                   ((typep (setq msg (and type (or (find-message trace type)
208                                                                   (find-enum trace type))))
209                                           'protobuf-message)
210                                    (when (eql (peek-char nil stream nil) #\:)
211                                      (read-char stream))
212                                    (let ((obj (deserialize type msg)))
213                                      (when slot
214                                        (pushnew slot rslots)
215                                        (push obj (slot-value object slot)))))
216                                   ((typep msg 'protobuf-enum)
217                                    (expect-char stream #\:)
218                                    (let* ((name (parse-token stream))
219                                           (enum (find name (proto-values msg) :key #'proto-name :test #'string=))
220                                           (val  (and enum (proto-value enum))))
221                                      (when slot
222                                        (pushnew slot rslots)
223                                        (push val (slot-value object slot)))))))
224                            (t
225                             (cond ((keywordp type)
226                                    (expect-char stream #\:)
227                                    (let ((val (case type
228                                                 ((:float :double) (parse-float stream))
229                                                 ((:string) (parse-string stream))
230                                                 ((:bool)   (if (boolean-true-p (parse-token stream)) t nil))
231                                                 (otherwise (parse-signed-int stream)))))
232                                      (when slot
233                                        (setf (slot-value object slot) val))))
234                                   ((typep (setq msg (and type (or (find-message trace type)
235                                                                   (find-enum trace type))))
236                                           'protobuf-message)
237                                    (when (eql (peek-char nil stream nil) #\:)
238                                      (read-char stream))
239                                    (let ((obj (deserialize type msg)))
240                                      (when slot
241                                        (setf (slot-value object slot) obj))))
242                                   ((typep msg 'protobuf-enum)
243                                    (expect-char stream #\:)
244                                    (let* ((name (parse-token stream))
245                                           (enum (find name (proto-values msg) :key #'proto-name :test #'string=))
246                                           (val  (and enum (proto-value enum))))
247                                      (when slot
248                                        (setf (slot-value object slot) val))))))))))))
249            (skip-field (stream)
250              ;; Skip either a token or a balanced {}-pair
251              (ecase (peek-char nil stream nil)
252                ((#\:)
253                 (read-char stream)
254                 (skip-whitespace stream)
255                 (parse-token-or-string stream))
256                ((#\{)
257                 (let ((depth 0))
258                   (loop for ch = (read-char stream)
259                         do (cond ((eql ch #\")
260                                   (loop for ch0 = (read-char stream)
261                                         until (eql ch0 #\")))
262                                  ((eql ch #\{)
263                                   (iincf depth))
264                                  ((eql ch #\})
265                                   (idecf depth)))
266                         until (i= depth 0)))))))
267     (declare (dynamic-extent #'deserialize #'skip-field))
268     (deserialize (proto-class message) message)))