1 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
3 ;;; Free Software published under an MIT-like license. See LICENSE ;;;
5 ;;; Copyright (c) 2012-2013 Google, Inc. All rights reserved. ;;;
7 ;;; Original author: Scott McKay ;;;
9 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
11 (in-package "PROTO-IMPL")
14 ;;; Protobufs schema pretty printing
16 (defun write-schema (protobuf &rest keys
17 &key (stream *standard-output*) (type :proto) &allow-other-keys)
18 "Writes the object 'protobuf' (schema, message, enum, etc) onto the
19 stream 'stream' in the format given by 'type' (:proto, :text, etc)."
20 (let ((*protobuf* protobuf))
21 (apply #'write-schema-as type protobuf stream keys)))
23 (defgeneric write-schema-as (type protobuf stream &key indentation &allow-other-keys)
25 "Writes the protobuf object 'protobuf' (schema, message, enum, etc) onto
26 the given stream 'stream' in the format given by 'type' (:proto, :text, etc).
27 If 'more' is true, this means there are more enum values, fields, etc to
28 be written after the current one."))
30 (defgeneric write-schema-header (type schema stream)
32 "Writes a header for the schema onto the given stream 'stream'
33 in the format given by 'type' (:proto, :text, etc)."))
35 (defgeneric write-schema-documentation (type docstring stream &key indentation)
37 "Writes the docstring as a \"block comment\" onto the given stream 'stream'
38 in the format given by 'type' (:proto, :text, etc)."))
41 ;;; Pretty print a schema as a .proto file
43 (defmethod write-schema-as ((type (eql :proto)) (schema protobuf-schema) stream
45 (with-prefixed-accessors (documentation syntax package imports options) (proto- schema)
47 (write-schema-documentation type documentation stream :indentation indentation))
49 (format stream "~&syntax = \"~A\";~%~%" syntax))
51 (format stream "~&package ~A;~%~%" (substitute #\_ #\- package)))
53 (dolist (import imports)
54 (format stream "~&import \"~A\";~%" import))
56 (write-schema-header type schema stream)
58 (dolist (option options)
59 (format stream "~&option ~:/protobuf-option/;~%" option))
61 (loop for (enum . more) on (proto-enums schema) doing
62 (write-schema-as type enum stream :indentation indentation :more more)
64 (loop for (alias . more) on (proto-type-aliases schema) doing
65 (write-schema-as type alias stream :indentation indentation :more more)
67 (loop for (msg . more) on (proto-messages schema) doing
68 (write-schema-as type msg stream :indentation indentation :more more)
70 (loop for (svc . more) on (proto-services schema) doing
71 (write-schema-as type svc stream :indentation indentation :more more)
74 (defmethod write-schema-documentation ((type (eql :proto)) docstring stream
76 (let ((lines (split-string docstring :separators '(#\newline #\return))))
78 (format stream "~&~@[~VT~]// ~A~%"
79 (and (not (zerop indentation)) indentation) line))))
81 ;; Lisp was born in 1958 :-)
82 (defparameter *lisp-options* '(("lisp_package" string 195801)
83 ("lisp_name" string 195802)
84 ("lisp_alias" string 195803)
85 ("lisp_type" string 195804)
86 ("lisp_class" string 195805)
87 ("lisp_slot" string 195806)))
89 (defparameter *option-types* '(("allow_alias" boolean)
93 ("optimize_for" symbol)
96 ("stream_type" string)
97 ;; Keep the rest of these in alphabetical order
98 ("cc_api_version" integer)
99 ("cc_generic_services" symbol)
100 ("go_api_version" integer)
101 ("go_generic_services" symbol)
102 ("go_package" string)
103 ("java_api_version" integer)
104 ("java_generic_services" symbol)
105 ("java_java5_enums" boolean)
106 ("java_multiple_files" boolean)
107 ("java_outer_classname" string)
108 ("java_package" string)
109 ("java_use_javaproto2" boolean)
110 ("py_api_version" integer)
111 ("py_generic_services" symbol)))
113 (defmethod write-schema-header ((type (eql :proto)) (schema protobuf-schema) stream)
114 (when (any-lisp-option schema)
115 (format stream "~&import \"net/proto2/proto/descriptor.proto\";~%~%")
116 (format stream "~&extend proto2.MessageOptions {~%")
117 (loop for (option type index) in *lisp-options* doing
118 (format stream "~& optional ~(~A~) ~A = ~D;~%" type option index))
119 (format stream "~&}~%~%")))
121 (defgeneric any-lisp-option (schema)
123 "Returns true iff there is anything in the schema that would require that
124 the .proto file include and extend 'MessageOptions'.")
125 (:method ((schema protobuf-schema))
126 (labels ((find-one (protobuf)
127 (dolist (enum (proto-enums protobuf))
128 (with-prefixed-accessors (name class alias-for) (proto- enum)
130 (and class (not (string-equal name (class-name->proto class))) class))
131 (return-from any-lisp-option t))))
132 (dolist (msg (proto-messages protobuf))
133 (with-prefixed-accessors (name class alias-for) (proto- msg)
135 (and class (not (string-equal name (class-name->proto class))) class))
136 (return-from any-lisp-option t))))
137 (map () #'find-one (proto-messages protobuf))))
141 (defun cl-user::protobuf-option (stream option colon-p atsign-p)
142 (let* ((type (or (second (find (proto-name option) *option-types* :key #'first :test #'string=))
143 (proto-type option)))
144 (value (proto-value option)))
145 (cond (colon-p ;~:/protobuf-option/ -- .proto format
147 (cond ((find (proto-name option) *lisp-options* :key #'first :test #'string=)
149 ((symbol) "(~A)~@[ = ~A~]")
150 ((boolean) "(~A)~@[ = ~(~A~)~]")
152 (cond ((typep value 'standard-object)
153 ;; If the value is an instance of some class,
154 ;; then it must be some sort of complex option,
155 ;; so print the value using the text format
157 (with-output-to-string (s)
158 (print-text-format value nil
159 :stream s :print-name nil :suppress-line-breaks t)))
162 "(~A)~@[ = ~S~]")))))
165 ((symbol) "~A~@[ = ~A~]")
166 ((boolean) "~A~@[ = ~(~A~)~]")
168 (cond ((typep value 'standard-object)
170 (with-output-to-string (s)
171 (print-text-format value nil
172 :stream s :print-name nil :suppress-line-breaks t)))
174 (t "~A~@[ = ~S~]"))))))))
175 (format stream fmt-control (proto-name option) value)))
176 (atsign-p ;~@/protobuf-option/ -- string/value format
177 (let ((fmt-control (if (eq type 'symbol) "~(~S~) ~A" "~(~S~) ~S")))
178 (format stream fmt-control (proto-name option) value)))
179 (t ;~/protobuf-option/ -- keyword/value format
180 (let ((fmt-control (if (eq type 'symbol) "~(:~A~) ~A" "~(:~A~) ~S")))
181 (format stream fmt-control (proto-name option) value))))))
183 (defun cl-user::source-location (stream location colon-p atsign-p)
184 (declare (ignore colon-p atsign-p))
185 (format stream "(~S ~D ~D)"
186 (source-location-pathname location)
187 (source-location-start-pos location) (source-location-end-pos location)))
189 (defmethod write-schema-as ((type (eql :proto)) (enum protobuf-enum) stream
190 &key (indentation 0) more)
191 (declare (ignore more))
192 (with-prefixed-accessors (name class alias-for documentation options) (proto- enum)
194 (write-schema-documentation type documentation stream :indentation indentation))
195 (format stream "~&~@[~VT~]enum ~A {~%"
196 (and (not (zerop indentation)) indentation)
197 (maybe-qualified-name enum))
198 (let ((other (and class (not (string-equal name (class-name->proto class))) class)))
200 (format stream "~&~VToption (lisp_name) = \"~A:~A\";~%"
201 (+ indentation 2) (package-name (symbol-package class)) (symbol-name class))))
203 (format stream "~&~VToption (lisp_alias) = \"~A:~A\";~%"
204 (+ indentation 2) (package-name (symbol-package alias-for)) (symbol-name alias-for)))
205 (dolist (option options)
206 (format stream "~&option ~:/protobuf-option/;~%" option))
207 (loop for (value . more) on (proto-values enum) doing
208 (write-schema-as type value stream :indentation (+ indentation 2) :more more))
209 (format stream "~&~@[~VT~]}~%"
210 (and (not (zerop indentation)) indentation))))
212 (defparameter *protobuf-enum-comment-column* 56)
213 (defmethod write-schema-as ((type (eql :proto)) (val protobuf-enum-value) stream
214 &key (indentation 0) more)
215 (declare (ignore more))
216 (with-prefixed-accessors (name documentation index) (proto- val)
217 (format stream "~&~@[~VT~]~A = ~D;~:[~2*~;~VT// ~A~]~%"
218 (and (not (zerop indentation)) indentation)
219 (maybe-qualified-name val) index
220 documentation *protobuf-enum-comment-column* documentation)))
222 (defmethod write-schema-as ((type (eql :proto)) (alias protobuf-type-alias) stream
223 &key (indentation 0) more)
224 (declare (ignore more))
225 (with-prefixed-accessors (name lisp-type proto-type) (proto- alias)
226 (let ((comment (format nil "Note: there is an alias ~A that maps Lisp ~(~S~) to Protobufs ~(~A~)"
227 name lisp-type proto-type)))
228 (write-schema-documentation type comment stream :indentation indentation))
229 (format stream "~&~@[~VT~]~%"
230 (and (not (zerop indentation)) indentation))))
232 (defmethod write-schema-as ((type (eql :proto)) (message protobuf-message) stream
233 &key (indentation 0) more index arity)
234 (declare (ignore more arity))
235 (let ((*protobuf* message))
236 (with-prefixed-accessors (name class alias-for message-type documentation options) (proto- message)
237 (cond ((eq message-type :group)
238 ;; If we've got a group, the printer for fields has already
239 ;; printed a partial line (nice modularity, huh?)
240 (format stream "group ~A = ~D {~%" name index)
241 (let ((other (and class (not (string-equal name (class-name->proto class))) class)))
243 (format stream "~&~VToption (lisp_name) = \"~A:~A\";~%"
244 (+ indentation 2) (package-name (symbol-package class)) (symbol-name class))))
246 (format stream "~&~VToption (lisp_alias) = \"~A:~A\";~%"
247 (+ indentation 2) (package-name (symbol-package alias-for)) (symbol-name alias-for)))
248 (dolist (option options)
249 (format stream "~&~VToption ~:/protobuf-option/;~%"
250 (+ indentation 2) option))
251 (loop for (enum . more) on (proto-enums message) doing
252 (write-schema-as type enum stream :indentation (+ indentation 2) :more more))
253 (loop for (field . more) on (proto-fields message) doing
254 (write-schema-as type field stream
255 :indentation (+ indentation 2) :more more :message message))
256 (format stream "~&~@[~VT~]}~%"
257 (and (not (zerop indentation)) indentation)))
260 (write-schema-documentation type documentation stream :indentation indentation))
261 (format stream "~&~@[~VT~]~A ~A {~%"
262 (and (not (zerop indentation)) indentation)
263 (if (eq message-type :message) "message" "extend")
264 (maybe-qualified-name message))
265 (let ((other (and class (not (string-equal name (class-name->proto class))) class)))
267 (format stream "~&~VToption (lisp_name) = \"~A:~A\";~%"
268 (+ indentation 2) (package-name (symbol-package class)) (symbol-name class))))
270 (format stream "~&~VToption (lisp_alias) = \"~A:~A\";~%"
271 (+ indentation 2) (package-name (symbol-package alias-for)) (symbol-name alias-for)))
272 (dolist (option options)
273 (format stream "~&~VToption ~:/protobuf-option/;~%"
274 (+ indentation 2) option))
275 (cond ((eq message-type :extends)
276 (loop for (field . more) on (proto-extended-fields message) doing
277 (write-schema-as type field stream
278 :indentation (+ indentation 2) :more more
281 (loop for (enum . more) on (proto-enums message) doing
282 (write-schema-as type enum stream :indentation (+ indentation 2) :more more))
283 (loop for (msg . more) on (proto-messages message) doing
284 (unless (eq (proto-message-type msg) :group)
285 (write-schema-as type msg stream :indentation (+ indentation 2) :more more)))
286 (loop for (field . more) on (proto-fields message) doing
287 (write-schema-as type field stream
288 :indentation (+ indentation 2) :more more
290 (loop for (extension . more) on (proto-extensions message) doing
291 (write-schema-as type extension stream :indentation (+ indentation 2) :more more))))
292 (format stream "~&~@[~VT~]}~%"
293 (and (not (zerop indentation)) indentation)))))))
295 (defun maybe-qualified-name (x &optional name)
296 "Given a message, return a fully qualified name if the short name
297 is not sufficient to name the message in the current scope."
299 ((or protobuf-message protobuf-enum protobuf-enum-value
301 (cond ((string= (make-qualified-name (proto-parent x) (proto-name x))
302 (proto-qualified-name x))
305 (proto-qualified-name x))))
308 (defparameter *protobuf-field-comment-column* 56)
309 (defmethod write-schema-as ((type (eql :proto)) (field protobuf-field) stream
310 &key (indentation 0) more message)
311 (declare (ignore more))
312 (with-prefixed-accessors (name documentation required type index packed options) (proto- field)
313 (let* ((class (if (eq (proto-class field) 'boolean) :bool (proto-class field)))
314 (msg (and (not (keywordp class))
315 (or (find-message message class)
316 (find-enum message class)
317 (find-type-alias message class)))))
318 (cond ((and (typep msg 'protobuf-message)
319 (eq (proto-message-type msg) :group))
320 (format stream "~&~@[~VT~]~(~A~) "
321 (and (not (zerop indentation)) indentation) required)
322 (write-schema-as :proto msg stream :indentation indentation :index index :arity required))
324 (let* ((defaultp (if (proto-alias-for message)
325 ;; Special handling for imported CLOS classes
326 (if (eq (proto-required field) :optional)
328 (and (proto-default field)
329 (not (equalp (proto-default field) #()))
330 (not (empty-default-p field))))
331 (not (empty-default-p field))))
332 (default (proto-default field))
333 (default (and defaultp
334 (cond ((and (typep msg 'protobuf-enum)
335 (or (stringp default) (symbolp default)))
336 (let ((e (find default (proto-values msg)
337 :key #'proto-name :test #'string=)))
338 (and e (proto-name e))))
340 (if (boolean-true-p default) "true" "false"))
342 (default (and defaultp
343 (if (stringp default) (escape-string default) default))))
344 (if (typep msg 'protobuf-type-alias)
345 (format stream "~&~@[~VT~]~(~A~) ~(~A~) ~A = ~D~
346 ~:[~*~; [default = ~S]~]~@[ [packed = true]~*~]~{ [~:/protobuf-option/]~};~
347 ~:[~2*~;~VT// ~A~]~%"
348 (and (not (zerop indentation)) indentation)
349 required (proto-proto-type msg) name index
350 defaultp default packed options
351 t *protobuf-field-comment-column*
352 (format nil "alias maps Lisp ~(~S~) to Protobufs ~(~A~)"
353 (proto-lisp-type msg) (proto-proto-type msg)))
354 (format stream (if (and (keywordp class) (not (eq class :bool)))
355 ;; Keyword class means a primitive type, print default with ~S
356 "~&~@[~VT~]~(~A~) ~A ~A = ~D~
357 ~:[~*~; [default = ~S]~]~@[ [packed = true]~*~]~{ [~:/protobuf-option/]~};~
358 ~:[~2*~;~VT// ~A~]~%"
359 ;; Non-keyword class means an enum type, print default with ~A"
360 "~&~@[~VT~]~(~A~) ~A ~A = ~D~
361 ~:[~*~; [default = ~A]~]~@[ [packed = true]~*~]~{ [~:/protobuf-option/]~};~
362 ~:[~2*~;~VT// ~A~]~%")
363 (and (not (zerop indentation)) indentation)
364 required (maybe-qualified-name msg type) name index
365 defaultp default packed options
366 documentation *protobuf-field-comment-column* documentation))))))))
368 (defun escape-string (string)
369 (if (every #'(lambda (ch) (and (standard-char-p ch) (graphic-char-p ch))) string)
371 (with-output-to-string (s)
372 (loop for ch across string
373 as esc = (escape-char ch)
374 do (format s "~A" esc)))))
376 (defmethod write-schema-as ((type (eql :proto)) (extension protobuf-extension) stream
377 &key (indentation 0) more)
378 (declare (ignore more))
379 (with-prefixed-accessors (from to) (proto-extension- extension)
380 (format stream "~&~@[~VT~]extensions ~D~:[~*~; to ~D~];~%"
381 (and (not (zerop indentation)) indentation)
382 from (not (eql from to)) (if (eql to #.(1- (ash 1 29))) "max" to))))
384 (defmethod write-schema-as ((type (eql :proto)) (service protobuf-service) stream
385 &key (indentation 0) more)
386 (declare (ignore more))
387 (with-prefixed-accessors (name documentation) (proto- service)
389 (write-schema-documentation type documentation stream :indentation indentation))
390 (format stream "~&~@[~VT~]service ~A {~%"
391 (and (not (zerop indentation)) indentation) name)
392 (loop for (method . more) on (proto-methods service) doing
393 (write-schema-as type method stream :indentation (+ indentation 2) :more more))
394 (format stream "~&~@[~VT~]}~%"
395 (and (not (zerop indentation)) indentation))))
397 (defmethod write-schema-as ((type (eql :proto)) (method protobuf-method) stream
398 &key (indentation 0) more)
399 (declare (ignore more))
400 (with-prefixed-accessors
401 (name documentation input-name output-name streams-name options) (proto- method)
402 (let* ((imsg (find-message *protobuf* input-name))
403 (omsg (find-message *protobuf* output-name))
404 (smsg (find-message *protobuf* streams-name))
405 (iname (maybe-qualified-name imsg))
406 (oname (maybe-qualified-name omsg))
407 (sname (maybe-qualified-name smsg)))
409 (write-schema-documentation type documentation stream :indentation indentation))
410 (format stream "~&~@[~VT~]rpc ~A (~@[~A~])~@[ streams (~A)~]~@[ returns (~A)~]"
411 (and (not (zerop indentation)) indentation)
412 name iname sname oname)
414 (format stream " {~%")
415 (dolist (option options)
416 (format stream "~&~@[~VT~]option ~:/protobuf-option/;~%"
417 (+ indentation 2) option))
418 (format stream "~@[~VT~]}"
419 (and (not (zerop indentation)) indentation)))
421 (format stream ";~%"))))))
424 ;;; Pretty print a schema as a .lisp file
426 (defvar *show-lisp-enum-indexes* t)
427 (defvar *show-lisp-field-indexes* t)
428 (defvar *use-common-lisp-package* nil)
430 (defmethod write-schema-as ((type (eql :lisp)) (schema protobuf-schema) stream
432 (show-field-indexes *show-lisp-field-indexes*)
433 (show-enum-indexes *show-lisp-enum-indexes*)
434 (use-common-lisp *use-common-lisp-package*))
435 (with-prefixed-accessors (name class documentation package lisp-package imports) (proto- schema)
436 (let* ((optimize (let ((opt (find-option schema "optimize_for")))
437 (and opt (cond ((string= opt "SPEED") :speed)
438 ((string= opt "CODE_SIZE") :space)
440 (options (remove-if #'(lambda (x) (string= (proto-name x) "optimize_for"))
441 (proto-options schema)))
442 (pkg (and package (if (stringp package) package (string package))))
443 (lisp-pkg (and lisp-package (if (stringp lisp-package) lisp-package (string lisp-package))))
444 (rpc-pkg (and (or lisp-pkg pkg)
445 (format nil "~A-~A" (or lisp-pkg pkg) 'rpc)))
446 (*show-lisp-enum-indexes* show-enum-indexes)
447 (*show-lisp-field-indexes* show-field-indexes)
448 (*use-common-lisp-package* use-common-lisp)
449 (*protobuf-package* (find-proto-package lisp-pkg))
450 (*protobuf-rpc-package* (find-proto-package rpc-pkg))
451 ;; If *protobuf-package* has not been defined, print symbols
452 ;; from :common-lisp if *use-common-lisp-package* is true; or
453 ;; :keyword otherwise. This ensures that all symbols will be
454 ;; read back correctly.
455 ;; (The :keyword package does not use any other packages, so
456 ;; all symbols will be printed with package prefixes.
457 ;; Keywords are always printed as :keyword.)
458 (*package* (or *protobuf-package*
459 (when *use-common-lisp-package* (find-package :common-lisp))
460 (find-package :keyword)))
461 (exports (collect-exports schema)))
463 (let* ((pkg (string-upcase rpc-pkg))
464 (rpc-exports (remove-if-not
467 (package-name (symbol-package sym))
470 (*package* (or *protobuf-rpc-package*
471 (when *use-common-lisp-package* (find-package :common-lisp))
472 (find-package :keyword))))
474 (format stream "~&(cl:eval-when (:execute :compile-toplevel :load-toplevel)~
475 ~% (cl:unless (cl:find-package \"~A\")~
476 ~% (cl:defpackage ~A (:use~@[ ~(~S~)~]))))~
477 ~%(cl:in-package \"~A\")~
478 ~%(cl:export '(~{~A~^~% ~}))~%~%"
479 pkg pkg (and *use-common-lisp-package* :common-lisp) pkg
481 (when (or lisp-pkg pkg)
482 (let ((pkg (string-upcase (or lisp-pkg pkg))))
483 (format stream "~&(cl:eval-when (:execute :compile-toplevel :load-toplevel)~
484 ~% (cl:unless (cl:find-package \"~A\")~
485 ~% (cl:defpackage ~A (:use~@[ ~(~S~)~]))))~
486 ~%(cl:in-package \"~A\")~
487 ~%(cl:export '(~{~A~^~% ~}))~%~%"
488 pkg pkg (and *use-common-lisp-package* :common-lisp) pkg
492 (package-name (symbol-package sym))
496 (write-schema-documentation type documentation stream :indentation indentation))
497 (format stream "~&(proto:define-schema ~(~A~)" (or class name))
498 (if (or pkg lisp-pkg imports optimize options documentation)
499 (format stream "~% (")
500 (format stream " ("))
503 (format stream "~A:package \"~A\"" spaces pkg)
504 (when (or lisp-pkg imports optimize options documentation)
508 (format stream "~A:lisp-package \"~A\"" spaces lisp-pkg)
509 (when (or imports optimize options documentation)
513 (cond ((= (length imports) 1)
514 (format stream "~A:import \"~A\"" spaces (car imports)))
516 (format stream "~A:import (~{\"~A\"~^ ~})" spaces imports)))
517 (when (or optimize options documentation)
521 (format stream "~A:optimize ~(~S~)" spaces optimize)
522 (when (or options documentation)
526 (format stream "~A:options (~{~/protobuf-option/~^ ~})" spaces options)
531 (format stream "~A:documentation ~S" spaces documentation)))
533 (loop for (enum . more) on (proto-enums schema) doing
534 (write-schema-as type enum stream :indentation 2 :more more))
535 (loop for (alias . more) on (proto-type-aliases schema) doing
536 (write-schema-as type alias stream :indentation 2 :more more))
537 (loop for (msg . more) on (proto-messages schema) doing
538 (write-schema-as type msg stream :indentation 2 :more more))
539 (loop for (svc . more) on (proto-services schema) doing
540 (write-schema-as type svc stream :indentation 2 :more more)))
541 (format stream ")~%")))
543 (defmethod write-schema-documentation ((type (eql :lisp)) docstring stream
544 &key (indentation 0))
545 (let ((lines (split-string docstring :separators '(#\newline #\return))))
547 (format stream "~&~@[~VT~];; ~A~%"
548 (and (not (zerop indentation)) indentation) line))))
550 (defmethod write-schema-header ((type (eql :lisp)) (schema protobuf-schema) stream)
551 (declare (ignorable type stream))
554 (defmethod write-schema-as ((type (eql :lisp)) (enum protobuf-enum) stream
555 &key (indentation 0) more)
556 (declare (ignore more))
558 (with-prefixed-accessors (name class alias-for
559 documentation source-location) (proto- enum)
561 (write-schema-documentation type documentation stream :indentation indentation))
562 (format stream "~@[~VT~](proto:define-enum ~(~S~)"
563 (and (not (zerop indentation)) indentation) class)
564 (let ((other (and name (string/= name (class-name->proto class)) name)))
565 (cond ((or other alias-for documentation source-location)
566 (format stream "~%~@[~VT~](~:[~2*~;:name ~S~@[~%~VT~]~]~
567 ~:[~2*~;:alias-for ~(~S~)~@[~%~VT~]~]~
568 ~:[~2*~;:documentation ~S~@[~%~VT~]~]~
569 ~:[~*~;:source-location ~/source-location/~])"
571 other other (and (or alias-for documentation source-location) (+ indentation 5))
572 alias-for alias-for (and (or documentation source-location) (+ indentation 5))
573 documentation documentation (and source-location (+ indentation 5))
574 source-location source-location))
576 (format stream " ()"))))
577 (loop for (value . more) on (proto-values enum) doing
578 (write-schema-as type value stream :indentation (+ indentation 2) :more more)
581 (format stream ")")))
583 (defmethod write-schema-as ((type (eql :lisp)) (val protobuf-enum-value) stream
584 &key (indentation 0) more)
585 (declare (ignore more))
586 (with-prefixed-accessors (value index) (proto- val)
587 (if *show-lisp-enum-indexes*
588 (format stream "~&~@[~VT~](~(~A~) ~D)"
589 (and (not (zerop indentation)) indentation) value index)
590 (format stream "~&~@[~VT~]~(~A~)"
591 (and (not (zerop indentation)) indentation) value))))
593 (defmethod write-schema-as ((type (eql :lisp)) (alias protobuf-type-alias) stream
594 &key (indentation 0) more)
595 (declare (ignore more))
597 (with-prefixed-accessors (class lisp-type proto-type serializer deserializer) (proto- alias)
598 (format stream "~@[~VT~](proto:define-type-alias ~(~S~)"
599 (and (not (zerop indentation)) indentation) class)
600 (format stream " ()") ;no options yet
601 (format stream "~%~@[~VT~]:lisp-type ~(~S~)~
602 ~%~@[~VT~]:proto-type ~(~A~)~
603 ~%~@[~VT~]:serializer ~(~S~)~
604 ~%~@[~VT~]:deserializer ~(~S~))"
605 (+ indentation 2) lisp-type
606 (+ indentation 2) proto-type
607 (+ indentation 2) serializer
608 (+ indentation 2) deserializer)))
610 (defmethod write-schema-as ((type (eql :lisp)) (message protobuf-message) stream
611 &key (indentation 0) more index arity)
612 (declare (ignore more))
613 (let ((*protobuf* message))
614 (with-prefixed-accessors (name class alias-for conc-name message-type
615 documentation source-location) (proto- message)
616 (cond ((eq message-type :group)
618 (write-schema-documentation type documentation stream :indentation indentation))
619 (format stream "~&~@[~VT~](proto:define-group ~(~S~)"
620 (and (not (zerop indentation)) indentation) class)
621 (let ((other (and name (string/= name (class-name->proto class)) name)))
622 (format stream "~%~@[~VT~](:index ~D~@[~%~VT~]~
623 :arity ~(~S~)~@[~%~VT~]~
624 ~:[~2*~;:name ~S~@[~%~VT~]~]~
625 ~:[~2*~;:alias-for ~(~S~)~@[~%~VT~]~]~
626 ~:[~2*~;:conc-name ~(~S~)~@[~%~VT~]~]~
627 ~:[~2*~;:documentation ~S~@[~%~VT~]~]~
628 ~:[~*~;:source-location ~/source-location/~])"
630 index (+ indentation 5)
631 arity (and (or other alias-for conc-name documentation source-location) (+ indentation 5))
632 other other (and (or alias-for conc-name documentation source-location) (+ indentation 5))
633 alias-for alias-for (and (or conc-name documentation source-location) (+ indentation 5))
634 conc-name conc-name (and (or documentation source-location) (+ indentation 5))
635 documentation documentation (and source-location (+ indentation 5))
636 source-location source-location))
637 (loop for (enum . more) on (proto-enums message) doing
638 (write-schema-as type enum stream :indentation (+ indentation 2) :more more)
641 (loop for (field . more) on (proto-fields message) doing
642 (write-schema-as type field stream
643 :indentation (+ indentation 2) :more more
649 (write-schema-documentation type documentation stream :indentation indentation))
650 (format stream "~&~@[~VT~](proto:define-~A ~(~S~)"
651 (and (not (zerop indentation)) indentation)
652 (if (eq message-type :message) "message" "extend") class)
653 (let ((other (and name (string/= name (class-name->proto class)) name)))
654 (cond ((eq message-type :extends)
655 (format stream " ()"))
656 ((or other alias-for conc-name documentation source-location)
657 (format stream "~%~@[~VT~](~:[~2*~;:name ~S~@[~%~VT~]~]~
658 ~:[~2*~;:alias-for ~(~S~)~@[~%~VT~]~]~
659 ~:[~2*~;:conc-name ~(~S~)~@[~%~VT~]~]~
660 ~:[~2*~;:documentation ~S~@[~%~VT~]~]~
661 ~:[~*~;:source-location ~/source-location/~])"
663 other other (and (or alias-for conc-name documentation source-location) (+ indentation 5))
664 alias-for alias-for (and (or conc-name documentation source-location) (+ indentation 5))
665 conc-name conc-name (and (or documentation source-location) (+ indentation 5))
666 documentation documentation (and source-location (+ indentation 5))
667 source-location source-location))
669 (format stream " ()"))))
670 (cond ((eq message-type :extends)
671 (loop for (field . more) on (proto-extended-fields message) doing
672 (write-schema-as type field stream
673 :indentation (+ indentation 2) :more more
678 (loop for (enum . more) on (proto-enums message) doing
679 (write-schema-as type enum stream :indentation (+ indentation 2) :more more)
682 (loop for (msg . more) on (proto-messages message) doing
683 (unless (eq (proto-message-type msg) :group)
684 (write-schema-as type msg stream :indentation (+ indentation 2) :more more)
687 (loop for (field . more) on (proto-fields message) doing
688 (write-schema-as type field stream
689 :indentation (+ indentation 2) :more more
693 (loop for (extension . more) on (proto-extensions message) doing
694 (write-schema-as type extension stream :indentation (+ indentation 2) :more more)
696 (terpri stream)))))))
697 (format stream ")"))))
699 (defparameter *protobuf-slot-comment-column* 56)
700 (defmethod write-schema-as ((type (eql :lisp)) (field protobuf-field) stream
701 &key (indentation 0) more message)
702 (with-prefixed-accessors (value required index packed options documentation) (proto- field)
703 (let* ((class (if (eq (proto-class field) 'boolean) :bool (proto-class field)))
704 (msg (and (not (keywordp class))
705 (or (find-message message class)
706 (find-enum message class)
707 (find-type-alias message class))))
708 (type (let ((cl (case class
715 ((:fixed32) 'fixed32)
716 ((:fixed64) 'fixed64)
717 ((:sfixed32) 'sfixed32)
718 ((:sfixed64) 'sfixed64)
720 ((:double) 'double-float)
723 ((:bytes) 'byte-vector)
726 (cond ((eq required :optional)
728 ((eq required :repeated)
729 (if (vector-field-p field)
733 (cond ((and (typep msg 'protobuf-message)
734 (eq (proto-message-type msg) :group))
735 (write-schema-as :lisp msg stream :indentation indentation :index index :arity required))
737 (let* ((defaultp (if (proto-alias-for message)
738 (if (eq (proto-required field) :optional)
740 (and (proto-default field)
741 (not (equalp (proto-default field) #()))
742 (not (empty-default-p field))))
743 (not (empty-default-p field))))
744 (default (proto-default field))
745 (default (and defaultp
746 (cond ((and (typep msg 'protobuf-enum)
747 (or (stringp default) (symbolp default)))
748 (let ((e (find default (proto-values msg)
749 :key #'proto-name :test #'string=)))
750 (and e (proto-value e))))
752 (boolean-true-p default))
754 (default (and defaultp
755 (if (stringp default) (escape-string default) default)))
756 (conc-name (proto-conc-name message))
757 (reader (when (and (not (eq (proto-reader field) value))
758 (not (string-equal (proto-reader field)
759 (format nil "~A~A" conc-name value))))
760 (proto-reader field)))
761 (writer (when (and (not (eq (proto-writer field) value))
762 (not (string-equal (proto-writer field)
763 (format nil "~A~A" conc-name value))))
764 (proto-writer field)))
765 (slot-name (if *show-lisp-field-indexes*
766 (format nil "(~(~S~) ~D)" value index)
767 (format nil "~(~S~)" value))))
768 (format stream (if (and (keywordp class) (not (eq class :bool)))
769 ;; Keyword class means a primitive type, print default with ~S
770 "~&~@[~VT~](~A :type ~(~S~)~:[~*~; :default ~S~]~
771 ~@[ :reader ~(~S~)~]~@[ :writer ~(~S~)~]~@[ :packed ~(~S~)~]~
772 ~@[ :options (~{~/protobuf-option/~^ ~})~])~
774 ;; Non-keyword class means an enum type, print default with ~(~S~)
775 "~&~@[~VT~](~A :type ~(~S~)~:[~*~; :default ~(~S~)~]~
776 ~@[ :reader ~(~S~)~]~@[ :writer ~(~S~)~]~@[ :packed ~(~S~)~]~
777 ~@[ :options (~{~/protobuf-option/~^ ~})~])~
779 (and (not (zerop indentation)) indentation)
780 slot-name type defaultp default reader writer packed options
781 ;; Don't write the comment if we'll insert a close paren after it
782 (and more documentation) *protobuf-slot-comment-column* documentation)))))))
784 (defmethod write-schema-as ((type (eql :lisp)) (extension protobuf-extension) stream
785 &key (indentation 0) more)
786 (declare (ignore more))
787 (with-prefixed-accessors (from to) (proto-extension- extension)
788 (format stream "~&~@[~VT~](proto:define-extension ~D ~D)"
789 (and (not (zerop indentation)) indentation)
790 from (if (eql to #.(1- (ash 1 29))) "max" to))))
792 (defmethod write-schema-as ((type (eql :lisp)) (service protobuf-service) stream
793 &key (indentation 0) more)
794 (declare (ignore more))
795 (with-prefixed-accessors (class documentation name source-location) (proto- service)
797 (write-schema-documentation type documentation stream :indentation indentation))
798 (format stream "~&~@[~VT~](proto:define-service ~(~S~)"
799 (and (not (zerop indentation)) indentation) (proto-class service))
800 (let ((other (and name (string/= name (class-name->proto (proto-class service))) name)))
801 (cond ((or documentation other source-location)
802 (format stream "~%~@[~VT~](~:[~2*~;:documentation ~S~@[~%~VT~]~]~
803 ~:[~2*~;:name ~S~@[~%~VT~]~]~
804 ~:[~*~;:source-location ~/source-location/~])"
806 documentation documentation (and (or documentation source-location) (+ indentation 5))
807 other other (and source-location (+ indentation 5))
808 source-location source-location))
810 (format stream " ()"))))
811 (loop for (method . more) on (proto-methods service) doing
812 (write-schema-as type method stream :indentation (+ indentation 2) :more more)
815 (format stream ")")))
817 (defmethod write-schema-as ((type (eql :lisp)) (method protobuf-method) stream
818 &key (indentation 0) more)
819 (declare (ignore more))
820 (with-prefixed-accessors (class input-type output-type streams-type
821 name input-name output-name streams-name
822 options documentation source-location) (proto- method)
824 (write-schema-documentation type documentation stream :indentation indentation))
825 (format stream "~&~@[~VT~](~(~S~) (" (and (not (zerop indentation)) indentation) class)
826 (if (and input-name (string/= (class-name->proto input-type) input-name))
827 (format stream "(~(~S~) :name ~S) => " input-type input-name)
828 (format stream "~(~S~) => " input-type))
829 (if (and output-name (string/= (class-name->proto output-type) output-name))
830 (format stream "(~(~S~) :name ~S)" output-type output-name)
831 (format stream "~(~S~)" output-type))
833 (if (and streams-name (string/= (class-name->proto streams-type) streams-name))
834 (format stream " :streams (~(~S~) :name ~S)" streams-type streams-name)
835 (format stream " :streams ~(~S~)" streams-type)))
837 (when (and name (string/= (class-name->proto name) name))
838 (format stream "~%~VT:name ~S"
839 (+ indentation 2) name))
841 (format stream "~%~VT:options (~{~/protobuf-option/~^ ~})"
842 (+ indentation 2) options))
843 (format stream ")")))
846 ;;; Collect symbols to be exported
848 (defgeneric collect-exports (schema)
850 "Collect all the symbols that should be exported from a Protobufs package"))
852 (defmethod collect-exports ((schema protobuf-schema))
855 (append (mapcan #'collect-exports (proto-enums schema))
856 (mapcan #'collect-exports (proto-messages schema))
857 (mapcan #'collect-exports (proto-services schema))))
860 ;; Export just the type name
861 (defmethod collect-exports ((enum protobuf-enum))
862 (list (proto-class enum)))
864 ;; Export the class name and all of the accessor names
865 (defmethod collect-exports ((message protobuf-message))
866 (append (list (proto-class message))
867 (mapcan #'collect-exports (proto-messages message))
868 (mapcan #'collect-exports (proto-fields message))))
870 ;; Export just the slot accessor name
871 (defmethod collect-exports ((field protobuf-field))
872 (list (or (proto-reader field)
873 (proto-slot field))))
875 ;; Export the names of all the methods
876 (defmethod collect-exports ((service protobuf-service))
877 (mapcan #'collect-exports (proto-methods service)))
879 ;; Export just the method name
880 (defmethod collect-exports ((method protobuf-method))
881 (list (proto-client-stub method) (proto-server-stub method)))