1 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
3 ;;; Confidential and proprietary information of ITA Software, Inc. ;;;
5 ;;; Copyright (c) 2012 ITA Software, Inc. All rights reserved. ;;;
7 ;;; Original author: Scott McKay ;;;
9 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
11 (in-package "PROTO-IMPL")
14 ;;; Protobufs schema pretty printing
16 (defun write-protobuf (protobuf &key (stream *standard-output*) (type :proto))
17 "Writes the protobuf object 'protobuf' (schema, message, enum, etc) onto
18 the given stream 'stream'in the format given by 'type' (:proto, :text, etc)."
19 (let ((*protobuf* protobuf))
20 (write-protobuf-as type protobuf stream)))
22 (defgeneric write-protobuf-as (type protobuf stream &key indentation)
24 "Writes the protobuf object 'protobuf' (schema, message, enum, etc) onto
25 the given stream 'stream' in the format given by 'type' (:proto, :text, etc)."))
27 (defgeneric write-protobuf-documentation (type docstring stream &key indentation)
29 "Writes the docstring as a \"block comment\" onto the given stream 'stream'
30 in the format given by 'type' (:proto, :text, etc)."))
33 ;;; Pretty print a schema as a .proto file
35 (defmethod write-protobuf-as ((type (eql :proto)) (protobuf protobuf) stream
37 (with-prefixed-accessors (name documentation syntax package imports optimize options) (proto- protobuf)
39 (write-protobuf-documentation type documentation stream :indentation indentation))
41 (format stream "~&syntax = \"~A\";~%~%" syntax))
43 (format stream "~&package ~A;~%~%" (substitute #\_ #\- package)))
45 (dolist (import imports)
46 (format stream "~&import \"~A\";~%" import))
48 (write-protobuf-header type stream)
50 (format stream "~&option optimize_for ~A;~%~%"
51 (if (eq optimize :space) "CODE_SIZE" "SPEED")))
53 (dolist (option options)
54 (format stream "~&option ~:/protobuf-option/;~%" option))
56 (dolist (enum (proto-enums protobuf))
57 (write-protobuf-as type enum stream :indentation indentation)
59 (dolist (msg (proto-messages protobuf))
60 (write-protobuf-as type msg stream :indentation indentation)
62 (dolist (svc (proto-services protobuf))
63 (write-protobuf-as type svc stream :indentation indentation)
66 (defmethod write-protobuf-documentation ((type (eql :proto)) docstring stream
68 (let ((lines (split-string docstring :separators '(#\newline #\return))))
70 (format stream "~&~@[~VT~]// ~A~%"
71 (and (not (zerop indentation)) indentation) line))))
73 (defvar *lisp-options* '(("lisp_package" "string" 195801)
74 ("lisp_name" "string" 195802)
75 ("lisp_alias" "string" 195803)))
77 (defmethod write-protobuf-header ((type (eql :proto)) stream)
78 (format stream "~&import \"net/proto2/proto/descriptor.proto\"~%~%")
79 (format stream "~&extend proto2.MessageOptions {~%")
80 (loop for (option type index) in *lisp-options* doing
81 (format stream "~& optional ~A ~A = ~D;~%" type option index))
82 (format stream "~&}~%~%"))
84 (defun cl-user::protobuf-option (stream option colon-p atsign-p)
85 (cond (colon-p ;~:/protobuf-option/ -- .proto format
86 (if (find (proto-name option) *lisp-options* :key #'first :test #'string=)
87 (format stream "(~A)~@[ = ~S~]" (proto-name option) (proto-value option))
88 (format stream "~A~@[ = ~S~]" (proto-name option) (proto-value option))))
89 (atsign-p ;~@/protobuf-option/ -- .lisp format
90 (format stream "~S ~S" (proto-name option) (proto-value option)))
91 (t ;~/protobuf-option/ -- keyword/value format
92 (format stream "~(:~A~) ~S" (proto-name option) (proto-value option)))))
94 (defmethod write-protobuf-as ((type (eql :proto)) (enum protobuf-enum) stream
96 (with-prefixed-accessors (name class alias-for documentation options) (proto- enum)
98 (write-protobuf-documentation type documentation stream :indentation indentation))
99 (format stream "~&~@[~VT~]enum ~A {~%"
100 (and (not (zerop indentation)) indentation) name)
101 (let ((other (and class (not (string= name (class-name->proto class))) class)))
103 (format stream "~&~VToption (lisp_name) = \"~A:~A\";~%"
104 (+ indentation 2) (package-name (symbol-package class)) (symbol-name class))))
106 (format stream "~&~VToption (lisp_alias) = \"~A:~A\";~%"
107 (+ indentation 2) (package-name (symbol-package alias-for)) (symbol-name alias-for)))
108 (dolist (option options)
109 (format stream "~&option ~:/protobuf-option/;~%" option))
110 (dolist (value (proto-values enum))
111 (write-protobuf-as type value stream :indentation (+ indentation 2)))
112 (format stream "~&~@[~VT~]}~%"
113 (and (not (zerop indentation)) indentation))))
115 (defparameter *protobuf-enum-comment-column* 56)
116 (defmethod write-protobuf-as ((type (eql :proto)) (val protobuf-enum-value) stream
117 &key (indentation 0))
118 (with-prefixed-accessors (name documentation index) (proto- val)
119 (format stream "~&~@[~VT~]~A = ~D;~:[~*~*~;~VT// ~A~]~%"
120 (and (not (zerop indentation)) indentation) name index
121 documentation *protobuf-enum-comment-column* documentation)))
124 (defmethod write-protobuf-as ((type (eql :proto)) (message protobuf-message) stream
125 &key (indentation 0))
126 (with-prefixed-accessors (name class alias-for extension-p documentation options) (proto- message)
128 (write-protobuf-documentation type documentation stream :indentation indentation))
129 (format stream "~&~@[~VT~]~A ~A {~%"
130 (and (not (zerop indentation)) indentation)
131 (if extension-p "extend" "message") name)
132 (let ((other (and class (not (string= name (class-name->proto class))) class)))
134 (format stream "~&~VToption (lisp_name) = \"~A:~A\";~%"
135 (+ indentation 2) (package-name (symbol-package class)) (symbol-name class))))
137 (format stream "~&~VToption (lisp_alias) = \"~A:~A\";~%"
138 (+ indentation 2) (package-name (symbol-package alias-for)) (symbol-name alias-for)))
139 (dolist (option options)
140 (format stream "~&~VToption ~:/protobuf-option/;~%"
141 (+ indentation 2) option))
143 (dolist (field (proto-fields message))
144 (when (proto-extension-p field)
145 (write-protobuf-as type field stream :indentation (+ indentation 2)))))
147 (dolist (enum (proto-enums message))
148 (write-protobuf-as type enum stream :indentation (+ indentation 2)))
149 (dolist (msg (proto-messages message))
150 (write-protobuf-as type msg stream :indentation (+ indentation 2)))
151 (dolist (field (proto-fields message))
152 (write-protobuf-as type field stream :indentation (+ indentation 2)))
153 (dolist (extension (proto-extensions message))
154 (write-protobuf-as type extension stream :indentation (+ indentation 2)))))
155 (format stream "~&~@[~VT~]}~%"
156 (and (not (zerop indentation)) indentation))))
158 (defparameter *protobuf-field-comment-column* 56)
159 (defmethod write-protobuf-as ((type (eql :proto)) (field protobuf-field) stream
160 &key (indentation 0))
161 (with-prefixed-accessors (name documentation required index default packed) (proto- field)
162 (let ((dflt (if (stringp default)
163 (if (i= (length default) 0) nil default)
165 (format stream "~&~@[~VT~]~(~A~) ~A ~A = ~D~@[ [default = ~A]~]~@[ [packed=true]~*~];~:[~*~*~;~VT// ~A~]~%"
166 (and (not (zerop indentation)) indentation)
167 required (proto-type field) name index dflt packed
168 documentation *protobuf-field-comment-column* documentation))))
170 (defmethod write-protobuf-as ((type (eql :proto)) (extension protobuf-extension) stream
171 &key (indentation 0))
172 (with-prefixed-accessors (from to) (proto-extension- extension)
173 (format stream "~&~@[~VT~]extensions ~D to ~D;~%"
174 (and (not (zerop indentation)) indentation)
175 from (if (eql to #.(1- (ash 1 29))) "max" to))))
178 (defmethod write-protobuf-as ((type (eql :proto)) (service protobuf-service) stream
179 &key (indentation 0))
180 (with-prefixed-accessors (name documentation) (proto- service)
182 (write-protobuf-documentation type documentation stream :indentation indentation))
183 (format stream "~&~@[~VT~]service ~A {~%"
184 (and (not (zerop indentation)) indentation) name)
185 (dolist (method (proto-methods service))
186 (write-protobuf-as type method stream :indentation (+ indentation 2)))
187 (format stream "~&~@[~VT~]}~%"
188 (and (not (zerop indentation)) indentation))))
190 (defmethod write-protobuf-as ((type (eql :proto)) (method protobuf-method) stream
191 &key (indentation 0))
192 (with-prefixed-accessors (name documentation input-name output-name options) (proto- method)
194 (write-protobuf-documentation type documentation stream :indentation indentation))
195 (format stream "~&~@[~VT~]rpc ~A (~@[~A~])~@[ returns (~A)~]"
196 (and (not (zerop indentation)) indentation)
197 name input-name output-name)
199 (format stream " {~%")
200 (dolist (option options)
201 (format stream "~&~@[~VT~]option ~:/protobuf-option/;~%"
202 (+ indentation 2) option))
203 (format stream "~@[~VT~]}"
204 (and (not (zerop indentation)) indentation)))
206 (format stream ";~%")))))
209 ;;; Pretty print a schema as a .lisp file
211 (defmethod write-protobuf-as ((type (eql :lisp)) (protobuf protobuf) stream
212 &key (indentation 0))
213 (with-prefixed-accessors (name class documentation package lisp-package imports optimize options) (proto- protobuf)
214 (let ((lisp-pkg (and lisp-package
215 (or (null package) (not (string-equal lisp-package package))))))
216 (when (or lisp-pkg package)
217 (format stream "~&(in-package \"~A\")~%~%" (or lisp-pkg package)))
219 (write-protobuf-documentation type documentation stream :indentation indentation))
220 (format stream "~&(proto:define-proto ~(~A~)" (or class name))
221 (if (or package lisp-pkg imports optimize options documentation)
222 (format stream "~% (")
223 (format stream " ("))
226 (format stream "~A:package ~A" spaces package)
227 (when (or lisp-pkg imports optimize options documentation)
231 (format stream "~A:lisp-package ~A" spaces lisp-pkg)
232 (when (or imports optimize options documentation)
236 (cond ((= (length imports) 1)
237 (format stream "~A:import \"~A\"" spaces (car imports)))
239 (format stream "~A:import (~{\"~A\"~^ ~})" spaces imports)))
240 (when (or optimize options documentation)
244 (format stream "~A:optimize ~(~S~)" spaces optimize)
245 (when (or options documentation)
249 (format stream "~A:options (~{~@/protobuf-option/~^ ~})" spaces options)
254 (format stream "~A:documentation ~S" spaces documentation)))
256 (dolist (enum (proto-enums protobuf))
257 (write-protobuf-as type enum stream :indentation 2))
258 (dolist (msg (proto-messages protobuf))
259 (write-protobuf-as type msg stream :indentation 2))
260 (dolist (svc (proto-services protobuf))
261 (write-protobuf-as type svc stream :indentation 2))
262 (format stream ")~%")))
264 (defmethod write-protobuf-documentation ((type (eql :lisp)) docstring stream
265 &key (indentation 0))
266 (let ((lines (split-string docstring :separators '(#\newline #\return))))
268 (format stream "~&~@[~VT~];; ~A~%"
269 (and (not (zerop indentation)) indentation) line))))
271 (defmethod write-protobuf-header ((type (eql :lisp)) stream)
272 (declare (ignorable type stream))
275 (defmethod write-protobuf-as ((type (eql :lisp)) (enum protobuf-enum) stream
276 &key (indentation 0))
278 (with-prefixed-accessors (name class alias-for documentation) (proto- enum)
280 (write-protobuf-documentation type documentation stream :indentation indentation))
281 (format stream "~@[~VT~](proto:define-enum ~(~S~)"
282 (and (not (zerop indentation)) indentation) class)
283 (let ((other (and name (not (string= name (class-name->proto class))) name)))
284 (cond ((or other alias-for documentation)
285 (format stream "~%~@[~VT~](~:[~*~*~;:name ~(~S~)~@[~%~VT~]~]~
286 ~:[~*~*~;:alias-for ~(~S~)~@[~%~VT~]~]~
287 ~:[~*~;:documentation ~S~])"
289 other other (and (or alias-for documentation) (+ indentation 5))
290 alias-for alias-for (and documentation (+ indentation 5))
291 documentation documentation))
293 (format stream " ()"))))
294 (loop for (value . more) on (proto-values enum) doing
295 (write-protobuf-as type value stream :indentation (+ indentation 2))
298 (format stream ")")))
300 (defmethod write-protobuf-as ((type (eql :lisp)) (val protobuf-enum-value) stream
301 &key (indentation 0))
302 (with-prefixed-accessors (value index) (proto- val)
303 (format stream "~&~@[~VT~](~(~A~) ~D)"
304 (and (not (zerop indentation)) indentation) value index)))
307 (defmethod write-protobuf-as ((type (eql :lisp)) (message protobuf-message) stream
308 &key (indentation 0))
309 (with-prefixed-accessors (name class alias-for conc-name extension-p documentation) (proto- message)
311 (write-protobuf-documentation type documentation stream :indentation indentation))
312 (format stream "~&~@[~VT~](proto:define-~A ~(~S~)"
313 (and (not (zerop indentation)) indentation)
314 (if extension-p "extend" "message") class)
315 (let ((other (and name (not (string= name (class-name->proto class))) name)))
317 (format stream " ()"))
318 ((or alias-for conc-name documentation)
319 (format stream "~%~@[~VT~](~:[~*~*~;:name ~(~S~)~@[~%~VT~]~]~
320 ~:[~*~*~;:alias-for ~(~S~)~@[~%~VT~]~]~
321 ~:[~*~*~;:conc-name ~(~S~)~@[~%~VT~]~]~
322 ~:[~*~;:documentation ~S~])"
324 other other (and (or alias-for conc-name documentation) (+ indentation 5))
325 alias-for alias-for (and (or documentation conc-name) (+ indentation 5))
326 conc-name conc-name (and documentation (+ indentation 5))
327 documentation documentation))
329 (format stream " ()"))))
331 (loop for (field . more) on (proto-fields message) doing
332 (when (proto-extension-p field)
333 (write-protobuf-as type field stream :indentation (+ indentation 2))
337 (loop for (enum . more) on (proto-enums message) doing
338 (write-protobuf-as type enum stream :indentation (+ indentation 2))
341 (loop for (msg . more) on (proto-messages message) doing
342 (write-protobuf-as type msg stream :indentation (+ indentation 2))
345 (loop for (field . more) on (proto-fields message) doing
346 (write-protobuf-as type field stream :indentation (+ indentation 2))
349 (loop for (extension . more) on (proto-extensions message) doing
350 (write-protobuf-as type extension stream :indentation (+ indentation 2))
353 (format stream ")")))
355 (defparameter *protobuf-slot-comment-column* 56)
356 (defmethod write-protobuf-as ((type (eql :lisp)) (field protobuf-field) stream
357 &key (indentation 0))
358 (with-prefixed-accessors (value reader writer class documentation required default) (proto- field)
359 (let ((dflt (protobuf-default-to-clos-init default class))
360 (clss (let ((cl (case class
361 ((:int32 :uint32 :int64 :uint64 :sint32 :sint64
362 :fixed32 :sfixed32 :fixed64 :sfixed64) 'integer)
364 ((:double) 'double-float)
369 (cond ((eq required :optional)
371 ((eq required :repeated)
374 (format stream (if (keywordp class)
375 ;; Keyword means a primitive type, print default with ~S
376 "~&~@[~VT~](~(~S~) :type ~(~S~)~@[ :default ~S~]~
377 ~@[ :reader ~(~S~)~]~@[ :writer ~(~S~)~])~:[~*~*~;~VT; ~A~]"
378 ;; Non-keyword must mean an enum type, print default with ~A
379 "~&~@[~VT~](~(~S~) :type ~(~S~)~@[ :default ~(:~A~)~]~
380 ~@[ :reader ~(~S~)~]~@[ :writer ~(~S~)~])~:[~*~*~;~VT; ~A~]")
381 (and (not (zerop indentation)) indentation)
382 value clss dflt reader writer
383 documentation *protobuf-slot-comment-column* documentation))))
385 (defmethod write-protobuf-as ((type (eql :lisp)) (extension protobuf-extension) stream
386 &key (indentation 0))
387 (with-prefixed-accessors (from to) (proto-extension- extension)
388 (format stream "~&~@[~VT~](define-extension ~D ~D)"
389 (and (not (zerop indentation)) indentation)
390 from (if (eql to #.(1- (ash 1 29))) "max" to))))
393 (defmethod write-protobuf-as ((type (eql :lisp)) (service protobuf-service) stream
394 &key (indentation 0))
395 (with-prefixed-accessors (class documentation conc-name) (proto- service)
397 (write-protobuf-documentation type documentation stream :indentation indentation))
398 (format stream "~&~@[~VT~](proto:define-service ~(~S~)"
399 (and (not (zerop indentation)) indentation) (proto-class service))
401 (format stream "~%~@[~VT~](:documentation ~S)"
402 (+ indentation 4) documentation))
404 (format stream " ()")))
405 (loop for (method . more) on (proto-methods service) doing
406 (write-protobuf-as type method stream :indentation (+ indentation 2))
409 (format stream ")")))
411 (defmethod write-protobuf-as ((type (eql :lisp)) (method protobuf-method) stream
412 &key (indentation 0))
413 (with-prefixed-accessors
414 (function documentation input-type output-type options) (proto- method)
416 (write-protobuf-documentation type documentation stream :indentation indentation))
417 (format stream "~&~@[~VT~](~(~S~) (~(~S~) ~(~S~))"
418 (and (not (zerop indentation)) indentation)
419 function input-type output-type)
421 (format stream "~%~VT:options (~{~@/protobuf-option/~^ ~})"
422 (+ indentation 2) options))
423 (format stream ")")))