until (or (null ch) (not (proto-whitespace-char-p ch)))
do (read-char stream nil)))
+(defun expect-char (stream char &optional chars within)
+ "Expect to see 'char' as the next character in the stream; signal an error if it's not there.
+ Then skip all of the following whitespace."
+ (if (if (listp char)
+ (member (peek-char nil stream nil) char)
+ (eql (peek-char nil stream nil) char))
+ (read-char stream)
+ (error "No '~C' found~@[ within '~A'~] at position ~D"
+ char within (file-position stream)))
+ (maybe-skip-chars stream chars))
+
+(defun maybe-skip-chars (stream chars)
+ "Skip some optional characters in the stream,
+ then skip all of the following whitespace."
+ (skip-whitespace stream)
+ (when chars
+ (loop
+ (let ((ch (peek-char nil stream nil)))
+ (when (or (null ch) (not (member ch chars)))
+ (skip-whitespace stream)
+ (return-from maybe-skip-chars)))
+ (read-char stream))))
+
+
;;--- Collect the comment so we can attach it to its associated object
(defun maybe-skip-comments (stream)
"If what appears next in the stream is a comment, skip it and any following comments,
(skip-whitespace stream))
-(defun expect-char (stream ch &optional within)
- "Expect to see 'ch' as the next character in the stream; signal an error if it's not there.
- Then skip all of the following whitespace."
- (if (if (listp ch)
- (member (peek-char nil stream nil) ch)
- (eql (peek-char nil stream nil) ch))
- (read-char stream)
- (error "No '~C' found~@[ within '~A'~] at position ~D"
- ch within (file-position stream)))
- (skip-whitespace stream))
-
-
(defun parse-token (stream)
"Parse the next token in the stream, then skip the following whitespace.
The returned value is the token."
(defun parse-proto-syntax (stream protobuf &optional (terminator #\;))
"Parse a Protobufs syntax line from 'stream'.
Updates the 'protobuf' object to use the syntax."
- (let ((syntax (prog2 (expect-char stream #\= "syntax")
+ (let ((syntax (prog2 (expect-char stream #\= () "syntax")
(parse-string stream)
- (expect-char stream terminator "syntax")
+ (expect-char stream terminator () "syntax")
(maybe-skip-comments stream))))
(setf (proto-syntax protobuf) syntax)))
Updates the 'protobuf' object to use the package."
(check-type protobuf protobuf)
(let* ((package (prog1 (parse-token stream)
- (expect-char stream terminator "package")
+ (expect-char stream terminator () "package")
(maybe-skip-comments stream)))
(lisp-pkg (or (proto-lisp-package protobuf)
(substitute #\- #\_ package))))
Updates the 'protobuf' object to use the package."
(check-type protobuf protobuf)
(let ((import (prog1 (parse-string stream)
- (expect-char stream terminator "package")
+ (expect-char stream terminator () "package")
(maybe-skip-comments stream))))
(process-imports import)
(setf (proto-imports protobuf) (nconc (proto-imports protobuf) (list import)))))
Updates the 'protobuf' (or message, service, method) to have the option."
(check-type protobuf (or null base-protobuf))
(let* ((key (prog1 (parse-parenthesized-token stream)
- (expect-char stream #\= "option")))
+ (expect-char stream #\= () "option")))
(val (prog1 (if (eql (peek-char nil stream nil) #\")
(parse-string stream)
(parse-token stream))
- (expect-char stream terminator "option")
+ (expect-char stream terminator () "option")
(maybe-skip-comments stream)))
(option (make-instance 'protobuf-option
:name key
Updates the 'protobuf' or 'protobuf-message' object to have the enum."
(check-type protobuf (or protobuf protobuf-message))
(let* ((name (prog1 (parse-token stream)
- (expect-char stream #\{ "enum")
+ (expect-char stream #\{ () "enum")
(maybe-skip-comments stream)))
(enum (make-instance 'protobuf-enum
:class (proto->class-name name *protobuf-package*)
(loop
(let ((name (parse-token stream)))
(when (null name)
- (expect-char stream #\} "enum")
+ (expect-char stream #\} '(#\;) "enum")
(maybe-skip-comments stream)
(setf (proto-enums protobuf) (nconc (proto-enums protobuf) (list enum)))
(let ((type (find-option enum "lisp_name")))
"Parse a Protobufs enum value from 'stream'.
Updates the 'protobuf-enum' object to have the enum value."
(check-type enum protobuf-enum)
- (expect-char stream #\= "enum")
+ (expect-char stream #\= () "enum")
(let* ((idx (prog1 (parse-int stream)
- (expect-char stream #\; "enum")
+ (expect-char stream #\; () "enum")
(maybe-skip-comments stream)))
(value (make-instance 'protobuf-enum-value
:name name
Updates the 'protobuf' or 'protobuf-message' object to have the message."
(check-type protobuf (or protobuf protobuf-message))
(let* ((name (prog1 (or name (parse-token stream))
- (expect-char stream #\{ "message")
+ (expect-char stream #\{ () "message")
(maybe-skip-comments stream)))
(message (make-instance 'protobuf-message
:class (proto->class-name name *protobuf-package*)
(loop
(let ((token (parse-token stream)))
(when (null token)
- (expect-char stream #\} "message")
+ (expect-char stream #\} '(#\;) "message")
(maybe-skip-comments stream)
(setf (proto-messages protobuf) (nconc (proto-messages protobuf) (list message)))
(let ((type (find-option message "lisp_name")))
Updates the 'protobuf' or 'protobuf-message' object to have the message."
(check-type protobuf (or protobuf protobuf-message))
(let* ((name (prog1 (parse-token stream)
- (expect-char stream #\{ "extend")
+ (expect-char stream #\{ () "extend")
(maybe-skip-comments stream)))
(message (find-message *protobuf* name))
(extends (and message
(loop
(let ((token (parse-token stream)))
(when (null token)
- (expect-char stream #\} "extend")
+ (expect-char stream #\} '(#\;) "extend")
(maybe-skip-comments stream)
(setf (proto-messages protobuf) (nconc (proto-messages protobuf) (list extends)))
(setf (proto-extenders protobuf) (nconc (proto-extenders protobuf) (list extends)))
(if (string= type "group")
(parse-proto-group stream message required extended-from)
(let* ((name (prog1 (parse-token stream)
- (expect-char stream #\= "message")))
+ (expect-char stream #\= () "message")))
(idx (parse-int stream))
(opts (prog1 (parse-proto-field-options stream)
- (expect-char stream #\; "message")
+ (expect-char stream #\; () "message")
(maybe-skip-comments stream)))
(dflt (find-option opts "default"))
(packed (find-option opts "packed"))
Updates the 'protobuf-message' object to have the group type and field."
(check-type message protobuf-message)
(let* ((type (prog1 (parse-token stream)
- (expect-char stream #\= "message")))
+ (expect-char stream #\= () "message")))
(name (slot-name->proto (proto->slot-name type)))
(idx (parse-int stream))
(msg (parse-proto-message stream message type))
(loop
(unless (eql (peek-char nil stream nil) #\[)
(return-from parse-proto-field-options options))
- (expect-char stream #\[ "message")
+ (expect-char stream #\[ () "message")
(collect-option (parse-proto-option stream nil #\])))
options))
(to (if (digit-char-p (peek-char nil stream nil))
(parse-int stream)
(parse-token stream))))
- (expect-char stream #\; "message")
+ (expect-char stream #\; () "message")
(assert (string= token "to") ()
"Expected 'to' in 'extensions' at position ~D" (file-position stream))
(assert (or (integerp to) (string= to "max")) ()
Updates the 'protobuf-protobuf' object to have the service."
(check-type protobuf protobuf)
(let* ((name (prog1 (parse-token stream)
- (expect-char stream #\{ "service")
+ (expect-char stream #\{ () "service")
(maybe-skip-comments stream)))
(service (make-instance 'protobuf-service
:class (proto->class-name name *protobuf-package*)
(loop
(let ((token (parse-token stream)))
(when (null token)
- (expect-char stream #\} "service")
+ (expect-char stream #\} '(#\;) "service")
(maybe-skip-comments stream)
(setf (proto-services protobuf) (nconc (proto-services protobuf) (list service)))
(return-from parse-proto-service service))
Updates the 'protobuf-service' object to have the method."
(check-type service protobuf-service)
(let* ((name (parse-token stream))
- (in (prog2 (expect-char stream #\( "service")
+ (in (prog2 (expect-char stream #\( () "service")
(parse-token stream)
- (expect-char stream #\) "service")))
+ (expect-char stream #\) () "service")))
(ret (parse-token stream))
- (out (prog2 (expect-char stream #\( "service")
+ (out (prog2 (expect-char stream #\( () "service")
(parse-token stream)
- (expect-char stream #\) "service")))
+ (expect-char stream #\) () "service")))
(opts (let ((opts (parse-proto-method-options stream)))
(when (or (null opts) (eql (peek-char nil stream nil) #\;))
- (expect-char stream #\; "service"))
+ (expect-char stream #\; () "service"))
(maybe-skip-comments stream)
opts))
(method (make-instance 'protobuf-method
"Parse any options in a Protobufs method from 'stream'.
Returns a list of 'protobuf-option' objects."
(when (eql (peek-char nil stream nil) #\{)
- (expect-char stream #\{ "service")
+ (expect-char stream #\{ () "service")
(maybe-skip-comments stream)
(with-collectors ((options collect-option))
(loop
(assert (string= (parse-token stream) "option") ()
"Syntax error in 'message' at position ~D" (file-position stream))
(collect-option (parse-proto-option stream nil #\;)))
- (expect-char stream #\} "service")
+ (expect-char stream #\} '(#\;) "service")
(maybe-skip-comments stream)
options)))
;;; Protobufs schema pretty printing
-(defun write-protobuf (protobuf &key (stream *standard-output*) (type :proto))
+(defun write-protobuf (protobuf &rest keys
+ &key (stream *standard-output*) (type :proto) &allow-other-keys)
"Writes the protobuf object 'protobuf' (schema, message, enum, etc) onto
the given stream 'stream'in the format given by 'type' (:proto, :text, etc)."
(let ((*protobuf* protobuf))
- (write-protobuf-as type protobuf stream)))
+ (apply #'write-protobuf-as type protobuf stream keys)))
(defgeneric write-protobuf-as (type protobuf stream &key indentation &allow-other-keys)
(:documentation
;;; Pretty print a schema as a .lisp file
+(defvar *show-lisp-enum-indexes* t)
+(defvar *show-lisp-field-indexes* t)
+
(defmethod write-protobuf-as ((type (eql :lisp)) (protobuf protobuf) stream
- &key (indentation 0))
+ &key (indentation 0)
+ (show-field-indexes *show-lisp-field-indexes*)
+ (show-enum-indexes *show-lisp-enum-indexes*))
(with-prefixed-accessors (name class documentation package lisp-package imports) (proto- protobuf)
(let* ((optimize (let ((opt (find-option protobuf "optimize_for")))
(and opt (cond ((string= opt "SPEED") :speed)
(options (remove "optimize_for" (proto-options protobuf) :test #'string-equal :key #'proto-name))
(pkg (and package (if (stringp package) package (string package))))
(lisp-pkg (and lisp-package (if (stringp lisp-package) lisp-package (string lisp-package))))
+ (*show-lisp-enum-indexes* show-enum-indexes)
+ (*show-lisp-field-indexes* show-field-indexes)
(*protobuf-package* (or (find-package lisp-pkg)
(find-package (string-upcase lisp-pkg))
*package*))
&key (indentation 0) more)
(declare (ignore more))
(with-prefixed-accessors (value index) (proto- val)
- (format stream "~&~@[~VT~](~(~A~) ~D)"
- (and (not (zerop indentation)) indentation) value index)))
+ (if *show-lisp-enum-indexes*
+ (format stream "~&~@[~VT~](~(~A~) ~D)"
+ (and (not (zerop indentation)) indentation) value index)
+ (format stream "~&~@[~VT~]~(~A~)"
+ (and (not (zerop indentation)) indentation) value))))
(defmethod write-protobuf-as ((type (eql :lisp)) (message protobuf-message) stream
(cond (group
(write-protobuf-as type group stream :indentation indentation :index index :arity required))
(t
- (format stream (if (keywordp class)
- ;; Keyword means a primitive type, print default with ~S
- "~&~@[~VT~](~(~S~) :type ~(~S~)~@[ :default ~S~]~
- ~@[ :reader ~(~S~)~]~@[ :writer ~(~S~)~])~:[~*~*~;~VT; ~A~]"
- ;; Non-keyword must mean an enum type, print default with ~A
- "~&~@[~VT~](~(~S~) :type ~(~S~)~@[ :default ~(:~A~)~]~
- ~@[ :reader ~(~S~)~]~@[ :writer ~(~S~)~])~:[~*~*~;~VT; ~A~]")
- (and (not (zerop indentation)) indentation)
- value clss dflt reader writer
- ;; Don't write the comment if we'll insert a close paren after it
- (and more documentation) *protobuf-slot-comment-column* documentation))))))
+ (let ((slot (if *show-lisp-field-indexes*
+ (format nil "(~(~S~) ~D)" value index)
+ (format nil "~(~S~)" value))))
+ (format stream (if (keywordp class)
+ ;; Keyword means a primitive type, print default with ~S
+ "~&~@[~VT~](~A :type ~(~S~)~@[ :default ~S~]~
+ ~@[ :reader ~(~S~)~]~@[ :writer ~(~S~)~])~:[~*~*~;~VT; ~A~]"
+ ;; Non-keyword must mean an enum type, print default with ~A
+ "~&~@[~VT~](~A :type ~(~S~)~@[ :default ~(:~A~)~]~
+ ~@[ :reader ~(~S~)~]~@[ :writer ~(~S~)~])~:[~*~*~;~VT; ~A~]")
+ (and (not (zerop indentation)) indentation)
+ slot clss dflt reader writer
+ ;; Don't write the comment if we'll insert a close paren after it
+ (and more documentation) *protobuf-slot-comment-column* documentation)))))))
(defmethod write-protobuf-as ((type (eql :lisp)) (extension protobuf-extension) stream
&key (indentation 0) more)