1 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
3 ;;; Free Software published under an MIT-like license. See LICENSE ;;;
5 ;;; Copyright (c) 2012 Google, Inc. All rights reserved. ;;;
7 ;;; Original author: Scott McKay ;;;
9 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
11 (in-package "PROTO-IMPL")
14 ;;; .proto file parsing
18 (declaim (inline proto-whitespace-char-p))
19 (defun proto-whitespace-char-p (ch)
20 (declare #.$optimize-fast-unsafe)
21 (and ch (member ch '(#\space #\tab #\return #\newline))))
23 (declaim (inline proto-eol-char-p))
24 (defun proto-eol-char-p (ch)
25 (declare #.$optimize-fast-unsafe)
26 (and ch (member ch '(#\return #\newline))))
28 (declaim (inline proto-token-char-p))
29 (defun proto-token-char-p (ch)
30 (declare #.$optimize-fast-unsafe)
31 (and ch (or (alpha-char-p ch)
33 (member ch '(#\_ #\.)))))
36 (defun skip-whitespace (stream)
37 "Skip all the whitespace characters that are coming up in the stream."
38 (loop for ch = (peek-char nil stream nil)
39 until (or (null ch) (not (proto-whitespace-char-p ch)))
40 do (read-char stream nil)))
42 (defun expect-char (stream char &optional chars within)
43 "Expect to see 'char' as the next character in the stream; signal an error if it's not there.
44 Then skip all of the following whitespace.
45 The return value is the character that was eaten."
48 (member (peek-char nil stream nil) char)
49 (eql (peek-char nil stream nil) char))
50 (setq ch (read-char stream))
51 (error "No '~C' found~@[ within '~A'~] at position ~D"
52 char within (file-position stream)))
53 (maybe-skip-chars stream chars)
56 (defun maybe-skip-chars (stream chars)
57 "Skip some optional characters in the stream,
58 then skip all of the following whitespace."
59 (skip-whitespace stream)
62 (let ((ch (peek-char nil stream nil)))
63 (when (or (null ch) (not (member ch chars)))
64 (skip-whitespace stream)
65 (return-from maybe-skip-chars)))
69 ;;--- Collect the comment so we can attach it to its associated object
70 (defun maybe-skip-comments (stream)
71 "If what appears next in the stream is a comment, skip it and any following comments,
72 then skip any following whitespace."
74 (let ((ch (peek-char nil stream nil)))
75 (when (or (null ch) (not (eql ch #\/)))
76 (return-from maybe-skip-comments))
78 (case (peek-char nil stream nil)
80 (skip-line-comment stream))
82 (skip-block-comment stream))
84 (skip-whitespace stream)
85 (return-from maybe-skip-comments))
87 (error "Found a '~C' at position ~D to start a comment, but no following '~C' or '~C'"
88 #\/ (file-position stream) #\/ #\*))))))
90 (defun skip-line-comment (stream)
91 "Skip to the end of a line comment, that is, to the end of the line.
92 Then skip any following whitespace."
93 (loop for ch = (read-char stream nil)
94 until (or (null ch) (proto-eol-char-p ch)))
95 (skip-whitespace stream))
97 (defun skip-block-comment (stream)
98 "Skip to the end of a block comment, that is, until a '*/' is seen.
99 Then skip any following whitespace."
100 (loop for ch = (read-char stream nil)
102 (error "Premature end of file while skipping block comment"))
104 (eql (peek-char nil stream nil) #\/))
105 (read-char stream nil)
107 (skip-whitespace stream))
110 (defun parse-token (stream &optional additional-chars)
111 "Parse the next token in the stream, then skip the following whitespace.
112 The returned value is the token."
113 (when (let ((ch (peek-char nil stream nil)))
114 (or (proto-token-char-p ch) (member ch additional-chars)))
115 (loop for ch = (read-char stream nil)
116 for ch1 = (peek-char nil stream nil)
117 collect ch into token
119 (and (not (proto-token-char-p ch1))
120 (not (member ch1 additional-chars))))
122 (skip-whitespace stream)
123 (return (coerce token 'string))))))
125 (defun parse-parenthesized-token (stream)
126 "Parse the next token in the stream, then skip the following whitespace.
127 The token might be surrounded by parentheses.
128 The returned value is the token."
129 (let ((left (peek-char nil stream nil)))
132 (when (proto-token-char-p (peek-char nil stream nil))
133 (loop for ch = (read-char stream nil)
134 for ch1 = (peek-char nil stream nil)
135 collect ch into token
136 until (or (null ch1) (not (proto-token-char-p ch1)))
138 (skip-whitespace stream)
140 (expect-char stream #\)))
141 (return (coerce token 'string)))))))
143 (defun parse-token-or-string (stream)
144 (if (eql (peek-char nil stream nil) #\")
145 (values (parse-string stream) 'string)
146 (values (parse-token stream) 'symbol)))
148 (defun parse-string (stream)
149 "Parse the next quoted string in the stream, then skip the following whitespace.
150 The returned value is the string, without the quotation marks."
151 (loop with ch0 = (read-char stream nil)
152 for ch = (read-char stream nil)
153 until (or (null ch) (char= ch ch0))
155 do (setq ch (unescape-char stream))
156 collect ch into string
158 (skip-whitespace stream)
159 (return (coerce string 'string)))))
161 (defun unescape-char (stream)
162 "Parse the next \"escaped\" character from the stream."
163 (let ((ch (read-char stream nil)))
164 (assert (not (null ch)) ()
165 "End of stream reached while reading escaped character")
169 (let* ((d1 (digit-char-p (read-char stream) 16))
170 (d2 (digit-char-p (read-char stream) 16)))
171 (code-char (+ (* d1 16) d2))))
172 ((#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9)
173 (if (not (digit-char-p (peek-char nil stream nil)))
175 ;; Three octal digits
176 (let* ((d1 (digit-char-p ch 8))
177 (d2 (digit-char-p (read-char stream) 8))
178 (d3 (digit-char-p (read-char stream) 8)))
179 (code-char (+ (* d1 64) (* d2 8) d3)))))
189 (defun escape-char (ch)
190 "The inverse of 'unescape-char', for printing."
191 (if (and (standard-char-p ch) (graphic-char-p ch))
199 ((#\backspace) "\\b")
203 (format nil "\\x~2,'0X" (char-code ch))))))
205 (defun parse-signed-int (stream)
206 "Parse the next token in the stream as an integer, then skip the following whitespace.
207 The returned value is the integer."
208 (let* ((sign (if (eql (peek-char nil stream nil) #\-)
209 (progn (read-char stream) -1)
211 (int (parse-unsigned-int stream)))
214 (defun parse-unsigned-int (stream)
215 "Parse the next token in the stream as an integer, then skip the following whitespace.
216 The returned value is the integer."
217 (when (digit-char-p (peek-char nil stream nil))
218 (loop for ch = (read-char stream nil)
219 for ch1 = (peek-char nil stream nil)
220 collect ch into token
221 until (or (null ch1) (and (not (digit-char-p ch1)) (not (eql ch #\x))))
223 (skip-whitespace stream)
224 (let ((token (coerce token 'string)))
225 (if (starts-with token "0x")
226 (let ((*read-base* 16))
227 (return (parse-integer (subseq token 2))))
228 (return (parse-integer token))))))))
230 (defun parse-float (stream)
231 "Parse the next token in the stream as a float, then skip the following whitespace.
232 The returned value is the float."
233 (let ((number (parse-number stream)))
235 (coerce number 'float))))
237 (defun parse-number (stream)
238 (when (let ((ch (peek-char nil stream nil)))
239 (or (digit-char-p ch) (member ch '(#\- #\+ #\.))))
240 (let ((token (parse-token stream '(#\- #\+ #\.))))
242 (skip-whitespace stream)
243 (parse-numeric-string token)))))
245 (defun parse-numeric-string (string)
246 (cond ((starts-with string "0x")
247 (parse-integer (subseq string 2) :radix 16))
248 ((starts-with string "-0x")
249 (- (parse-integer (subseq string 3) :radix 16)))
251 (read-from-string string))))
254 ;;; The parser itself
256 (defun parse-schema-from-file (filename &key name class (conc-name ""))
257 "Parses the named file as a .proto file, and returns the Protobufs schema."
258 (with-open-file (stream filename
260 :external-format :utf-8
261 :element-type 'character)
262 (let ((*compile-file-pathname* (pathname stream))
263 (*compile-file-truename* (truename stream)))
264 (parse-schema-from-stream stream
265 :name (or name (class-name->proto (pathname-name (pathname stream))))
266 :class (or class (kintern (pathname-name (pathname stream))))
267 :conc-name conc-name))))
269 ;; The syntax for Protocol Buffers is so simple that it doesn't seem worth
270 ;; writing a sophisticated parser
271 ;; Note that we don't put the result into *all-schemas*; that's done in 'define-schema'
272 (defun parse-schema-from-stream (stream &key name class (conc-name ""))
273 "Parses a top-level .proto file from the stream 'stream'.
274 Returns the protobuf schema that describes the .proto file."
275 (let* ((schema (make-instance 'protobuf-schema
279 (*protobuf-package* *package*)
280 (*protobuf-conc-name* conc-name))
282 (skip-whitespace stream)
283 (maybe-skip-comments stream)
284 (let ((char (peek-char nil stream nil)))
286 (remove-options schema "lisp_package")
287 (return-from parse-schema-from-stream schema))
288 ((proto-token-char-p char)
289 (let ((token (parse-token stream)))
290 (cond ((string= token "syntax")
291 (parse-proto-syntax stream schema))
292 ((string= token "package")
293 (parse-proto-package stream schema))
294 ((string= token "import")
295 (parse-proto-import stream schema))
296 ((string= token "option")
297 (let* ((option (parse-proto-option stream schema))
298 (name (and option (proto-name option)))
299 (value (and option (proto-value option))))
300 (when (and option (option-name= name "lisp_package"))
301 (let ((package (or (find-proto-package value) *protobuf-package*)))
302 (setf (proto-lisp-package schema) value)
303 (setq *protobuf-package* package)))))
304 ((string= token "enum")
305 (parse-proto-enum stream schema))
306 ((string= token "extend")
307 (parse-proto-extend stream schema))
308 ((string= token "message")
309 (parse-proto-message stream schema))
310 ((string= token "service")
311 (parse-proto-service stream schema)))))
313 (error "Syntax error at position ~D" (file-position stream))))))))
315 (defun parse-proto-syntax (stream schema &optional (terminator #\;))
316 "Parse a Protobufs syntax line from 'stream'.
317 Updates the 'protobuf-schema' object to use the syntax."
318 (let ((syntax (prog2 (expect-char stream #\= () "syntax")
319 (parse-string stream)
320 (expect-char stream terminator () "syntax")
321 (maybe-skip-comments stream))))
322 (setf (proto-syntax schema) syntax)))
324 (defun parse-proto-package (stream schema &optional (terminator #\;))
325 "Parse a Protobufs package line from 'stream'.
326 Updates the 'protobuf-schema' object to use the package."
327 (check-type schema protobuf-schema)
328 (let* ((package (prog1 (parse-token stream)
329 (expect-char stream terminator () "package")
330 (maybe-skip-comments stream)))
331 (lisp-pkg (or (proto-lisp-package schema)
332 (substitute #\- #\_ package))))
333 (setf (proto-package schema) package)
334 (unless (proto-lisp-package schema)
335 (setf (proto-lisp-package schema) lisp-pkg))
336 (let ((package (or (find-proto-package lisp-pkg) *protobuf-package*)))
337 (setq *protobuf-package* package))))
339 (defun parse-proto-import (stream schema &optional (terminator #\;))
340 "Parse a Protobufs import line from 'stream'.
341 Updates the 'protobuf-schema' object to use the package."
342 (check-type schema protobuf-schema)
343 (let ((import (prog1 (parse-string stream)
344 (expect-char stream terminator () "package")
345 (maybe-skip-comments stream))))
346 (process-imports schema import)
347 (setf (proto-imports schema) (nconc (proto-imports schema) (list import)))))
349 (defun parse-proto-option (stream protobuf &optional (terminators '(#\;)))
350 "Parse a Protobufs option line from 'stream'.
351 Updates the 'protobuf-schema' (or message, service, method) to have the option."
352 (check-type protobuf (or null base-protobuf))
354 (key (prog1 (parse-parenthesized-token stream)
355 (expect-char stream #\= () "option")))
356 (val (prog1 (let ((ch (peek-char nil stream nil)))
358 (parse-string stream))
359 ((or (digit-char-p ch) (member ch '(#\- #\+ #\.)))
360 (parse-number stream))
361 (t (kintern (parse-token stream)))))
362 (setq terminator (expect-char stream terminators () "option"))
363 (maybe-skip-comments stream)))
364 (option (make-instance 'protobuf-option
368 (setf (proto-options protobuf) (nconc (proto-options protobuf) (list option)))
369 (values option terminator))
371 ;; If nothing to graft the option into, just return it as the value
372 (values option terminator)))))
375 (defun parse-proto-enum (stream protobuf)
376 "Parse a Protobufs 'enum' from 'stream'.
377 Updates the 'protobuf-schema' or 'protobuf-message' object to have the enum."
378 (check-type protobuf (or protobuf-schema protobuf-message))
379 (let* ((name (prog1 (parse-token stream)
380 (expect-char stream #\{ () "enum")
381 (maybe-skip-comments stream)))
382 (enum (make-instance 'protobuf-enum
383 :class (proto->class-name name *protobuf-package*)
386 (let ((name (parse-token stream)))
388 (expect-char stream #\} '(#\;) "enum")
389 (maybe-skip-comments stream)
390 (setf (proto-enums protobuf) (nconc (proto-enums protobuf) (list enum)))
391 (let ((type (find-option enum "lisp_name")))
393 (setf (proto-class enum) (make-lisp-symbol type))))
394 (let ((alias (find-option enum "lisp_alias")))
396 (setf (proto-alias-for enum) (make-lisp-symbol alias))))
397 (return-from parse-proto-enum enum))
398 (if (string= name "option")
399 (parse-proto-option stream enum)
400 (parse-proto-enum-value stream enum name))))))
402 (defun parse-proto-enum-value (stream enum name)
403 "Parse a Protobufs enum value from 'stream'.
404 Updates the 'protobuf-enum' object to have the enum value."
405 (check-type enum protobuf-enum)
406 (expect-char stream #\= () "enum")
407 (let* ((idx (prog1 (parse-signed-int stream)
408 (expect-char stream #\; () "enum")
409 (maybe-skip-comments stream)))
410 (value (make-instance 'protobuf-enum-value
413 :value (proto->enum-name name *protobuf-package*))))
414 (setf (proto-values enum) (nconc (proto-values enum) (list value)))
418 (defun parse-proto-message (stream protobuf &optional name)
419 "Parse a Protobufs 'message' from 'stream'.
420 Updates the 'protobuf-schema' or 'protobuf-message' object to have the message."
421 (check-type protobuf (or protobuf-schema protobuf-message))
422 (let* ((name (prog1 (or name (parse-token stream))
423 (expect-char stream #\{ () "message")
424 (maybe-skip-comments stream)))
425 (class (proto->class-name name *protobuf-package*))
426 (message (make-instance 'protobuf-message
430 ;; Maybe force accessors for all slots
431 :conc-name (conc-name-for-type class *protobuf-conc-name*)))
432 (*protobuf* message))
434 (let ((token (parse-token stream)))
436 (expect-char stream #\} '(#\;) "message")
437 (maybe-skip-comments stream)
438 (setf (proto-messages protobuf) (nconc (proto-messages protobuf) (list message)))
439 (let ((type (find-option message "lisp_name")))
441 (setf (proto-class message) (make-lisp-symbol type))))
442 (let ((alias (find-option message "lisp_alias")))
444 (setf (proto-alias-for message) (make-lisp-symbol alias))))
445 (return-from parse-proto-message message))
446 (cond ((string= token "enum")
447 (parse-proto-enum stream message))
448 ((string= token "extend")
449 (parse-proto-extend stream message))
450 ((string= token "message")
451 (parse-proto-message stream message))
452 ((member token '("required" "optional" "repeated") :test #'string=)
453 (parse-proto-field stream message token))
454 ((string= token "option")
455 (parse-proto-option stream message))
456 ((string= token "extensions")
457 (parse-proto-extension stream message))
459 (error "Unrecognized token ~A at position ~D"
460 token (file-position stream))))))))
462 (defun parse-proto-extend (stream protobuf)
463 "Parse a Protobufs 'extend' from 'stream'.
464 Updates the 'protobuf-schema' or 'protobuf-message' object to have the message."
465 (check-type protobuf (or protobuf-schema protobuf-message))
466 (let* ((name (prog1 (parse-token stream)
467 (expect-char stream #\{ () "extend")
468 (maybe-skip-comments stream)))
469 (message (find-message protobuf name))
470 (extends (and message
471 (make-instance 'protobuf-message
472 :class (proto->class-name name *protobuf-package*)
474 :parent (proto-parent message)
475 :conc-name (proto-conc-name message)
476 :alias-for (proto-alias-for message)
477 :enums (copy-list (proto-enums message))
478 :messages (copy-list (proto-messages message))
479 :fields (copy-list (proto-fields message))
480 :extensions (copy-list (proto-extensions message))
481 :message-type :extends))) ;this message is an extension
482 (*protobuf* extends))
484 (let ((token (parse-token stream)))
486 (expect-char stream #\} '(#\;) "extend")
487 (maybe-skip-comments stream)
488 (setf (proto-messages protobuf) (nconc (proto-messages protobuf) (list extends)))
489 (setf (proto-extenders protobuf) (nconc (proto-extenders protobuf) (list extends)))
490 (let ((type (find-option extends "lisp_name")))
492 (setf (proto-class extends) (make-lisp-symbol type))))
493 (let ((alias (find-option extends "lisp_alias")))
495 (setf (proto-alias-for extends) (make-lisp-symbol alias))))
496 (return-from parse-proto-extend extends))
497 (cond ((member token '("required" "optional" "repeated") :test #'string=)
498 (let ((field (parse-proto-field stream extends token message)))
499 (setf (proto-extended-fields extends) (nconc (proto-extended-fields extends) (list field)))))
500 ((string= token "option")
501 (parse-proto-option stream extends))
503 (error "Unrecognized token ~A at position ~D"
504 token (file-position stream))))))))
506 (defun parse-proto-field (stream message required &optional extended-from)
507 "Parse a Protobufs field from 'stream'.
508 Updates the 'protobuf-message' object to have the field."
509 (check-type message protobuf-message)
510 (let ((type (parse-token stream)))
511 (if (string= type "group")
512 (parse-proto-group stream message required extended-from)
513 (let* ((name (prog1 (parse-token stream)
514 (expect-char stream #\= () "message")))
515 (idx (parse-unsigned-int stream))
516 (opts (prog1 (parse-proto-field-options stream)
517 (expect-char stream #\; () "message")
518 (maybe-skip-comments stream)))
519 (packed (find-option opts "packed"))
520 (ptype (if (member type '("int32" "int64" "uint32" "uint64" "sint32" "sint64"
521 "fixed32" "fixed64" "sfixed32" "sfixed64"
522 "string" "bytes" "bool" "float" "double") :test #'string=)
525 (class (if (keywordp ptype) ptype (proto->class-name type *protobuf-package*)))
526 (slot (proto->slot-name name *protobuf-package*))
527 (reqd (kintern required))
528 (field (make-instance 'protobuf-field
532 ;; One of :required, :optional or :repeated
536 ;; Fields parsed from .proto files usually get an accessor
537 :reader (let ((conc-name (proto-conc-name message)))
539 (intern (format nil "~A~A" conc-name slot) *protobuf-package*)))
540 :default (multiple-value-bind (default type default-p)
541 (find-option opts "default")
542 (declare (ignore type))
545 (if (eq reqd :repeated) $empty-list $empty-default)))
546 :packed (and packed (boolean-true-p packed))
547 :message-type (proto-message-type message)
548 :options (remove-options opts "default" "packed"))))
550 (assert (index-within-extensions-p idx extended-from) ()
551 "The index ~D is not in range for extending ~S"
552 idx (proto-class extended-from)))
553 (let ((slot (find-option opts "lisp_name")))
555 (setf (proto-value field) (make-lisp-symbol type))))
556 (setf (proto-fields message) (nconc (proto-fields message) (list field)))
559 (defun parse-proto-group (stream message required &optional extended-from)
560 "Parse a (deprecated) Protobufs group from 'stream'.
561 Updates the 'protobuf-message' object to have the group type and field."
562 (check-type message protobuf-message)
563 (let* ((type (prog1 (parse-token stream)
564 (expect-char stream #\= () "message")))
565 (name (slot-name->proto (proto->slot-name type)))
566 (idx (parse-unsigned-int stream))
567 (msg (parse-proto-message stream message type))
568 (class (proto->class-name type *protobuf-package*))
569 (slot (proto->slot-name name *protobuf-package*))
570 (field (make-instance 'protobuf-field
574 :required (kintern required)
577 ;; Groups parsed from .proto files usually get an accessor
578 :reader (let ((conc-name (proto-conc-name message)))
580 (intern (format nil "~A~A" conc-name slot) *protobuf-package*)))
581 :message-type :group)))
582 (setf (proto-message-type msg) :group)
584 (assert (index-within-extensions-p idx extended-from) ()
585 "The index ~D is not in range for extending ~S"
586 idx (proto-class extended-from)))
587 (setf (proto-fields message) (nconc (proto-fields message) (list field)))
590 (defun parse-proto-field-options (stream)
591 "Parse any options in a Protobufs field from 'stream'.
592 Returns a list of 'protobuf-option' objects."
593 (with-collectors ((options collect-option))
594 (let ((terminator nil))
596 (cond ((eql (peek-char nil stream nil) #\[)
597 (expect-char stream #\[ () "message"))
598 ((eql terminator #\,))
600 (return-from parse-proto-field-options options)))
601 (multiple-value-bind (option term)
602 (parse-proto-option stream nil '(#\] #\,))
603 (setq terminator term)
604 (collect-option option))))))
606 (defun parse-proto-extension (stream message)
607 (check-type message protobuf-message)
608 (let* ((from (parse-unsigned-int stream))
609 (token (parse-token stream))
610 (to (let ((ch (peek-char nil stream nil)))
611 (cond ((digit-char-p (peek-char nil stream nil))
612 (parse-unsigned-int stream))
614 (t (parse-token stream))))))
615 (expect-char stream #\; () "message")
616 (assert (or (null token) (string= token "to")) ()
617 "Expected 'to' in 'extensions' at position ~D" (file-position stream))
618 (assert (or (integerp to) (string= to "max")) ()
619 "Extension value is not an integer or 'max' as position ~D" (file-position stream))
620 (let ((extension (make-instance 'protobuf-extension
622 :to (if (integerp to) to #.(1- (ash 1 29))))))
623 (setf (proto-extensions message)
624 (nconc (proto-extensions message)
629 (defun parse-proto-service (stream schema)
630 "Parse a Protobufs 'service' from 'stream'.
631 Updates the 'protobuf-schema' object to have the service."
632 (check-type schema protobuf-schema)
633 (let* ((name (prog1 (parse-token stream)
634 (expect-char stream #\{ () "service")
635 (maybe-skip-comments stream)))
636 (service (make-instance 'protobuf-service
637 :class (proto->class-name name *protobuf-package*)
641 (let ((token (parse-token stream)))
643 (expect-char stream #\} '(#\;) "service")
644 (maybe-skip-comments stream)
645 (setf (proto-services schema) (nconc (proto-services schema) (list service)))
646 (return-from parse-proto-service service))
647 (cond ((string= token "option")
648 (parse-proto-option stream service))
649 ((string= token "rpc")
650 (parse-proto-method stream service (iincf index)))
652 (error "Unrecognized token ~A at position ~D"
653 token (file-position stream))))))))
655 (defun parse-proto-method (stream service index)
656 "Parse a Protobufs method from 'stream'.
657 Updates the 'protobuf-service' object to have the method."
658 (check-type service protobuf-service)
659 (let* ((name (parse-token stream))
660 (in (prog2 (expect-char stream #\( () "service")
662 (expect-char stream #\) () "service")))
663 (ret (parse-token stream))
664 (out (prog2 (expect-char stream #\( () "service")
666 (expect-char stream #\) () "service")))
667 (opts (let ((opts (parse-proto-method-options stream)))
668 (when (or (null opts) (eql (peek-char nil stream nil) #\;))
669 (expect-char stream #\; () "service"))
670 (maybe-skip-comments stream)
672 (stub (proto->class-name name *protobuf-package*))
673 (method (make-instance 'protobuf-method
676 :input-type (proto->class-name in *protobuf-package*)
678 :output-type (proto->class-name out *protobuf-package*)
682 (let* ((name (find-option method "lisp_name"))
683 (stub (or (and name (make-lisp-symbol name))
685 (setf (proto-class method) stub
686 (proto-client-stub method) stub
687 (proto-server-stub method) (intern (format nil "~A-~A" 'do stub) *protobuf-package*)))
688 (assert (string= ret "returns") ()
689 "Syntax error in 'message' at position ~D" (file-position stream))
690 (setf (proto-methods service) (nconc (proto-methods service) (list method)))
693 (defun parse-proto-method-options (stream)
694 "Parse any options in a Protobufs method from 'stream'.
695 Returns a list of 'protobuf-option' objects."
696 (when (eql (peek-char nil stream nil) #\{)
697 (expect-char stream #\{ () "service")
698 (maybe-skip-comments stream)
699 (with-collectors ((options collect-option))
701 (when (eql (peek-char nil stream nil) #\})
703 (assert (string= (parse-token stream) "option") ()
704 "Syntax error in 'message' at position ~D" (file-position stream))
705 (collect-option (parse-proto-option stream nil)))
706 (expect-char stream #\} '(#\;) "service")
707 (maybe-skip-comments stream)