;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; ;;;
-;;; Confidential and proprietary information of ITA Software, Inc. ;;;
+;;; Free Software published under an MIT-like license. See LICENSE ;;;
;;; ;;;
-;;; Copyright (c) 2012 ITA Software, Inc. All rights reserved. ;;;
+;;; Copyright (c) 2012 Google, Inc. All rights reserved. ;;;
;;; ;;;
;;; Original author: Scott McKay ;;;
;;; ;;;
(defvar *suppress-line-breaks* nil
"When true, don't generate line breaks in the text format")
-(defgeneric print-text-format (object &optional type &key stream suppress-line-breaks)
+(defgeneric print-text-format (object &optional type &key stream suppress-line-breaks print-name)
(:documentation
"Prints the object 'object' of type 'type' onto the stream 'stream' using the
textual format.
(defmethod print-text-format (object &optional type
&key (stream *standard-output*)
- (suppress-line-breaks *suppress-line-breaks*))
- (let* ((type (or type (class-of object)))
+ (suppress-line-breaks *suppress-line-breaks*) (print-name t))
+ (let* ((type (or type (type-of object)))
(message (find-message-for-class type)))
- (assert message ()
- "There is no Protobuf message having the type ~S" type)
+ (unless message
+ (serialization-error "There is no Protobuf message having the type ~S" type))
(macrolet ((read-slot (object slot reader)
;; Don't do a boundp check, we assume the object is fully populated
;; Unpopulated slots should be "nullable" and should contain nil
(when (or slot reader)
(cond ((eq (proto-required field) :repeated)
(cond ((keywordp type)
- (map () #'(lambda (v)
- (print-prim v type field stream
- (or suppress-line-breaks indent)))
- (read-slot object slot reader)))
+ (doseq (v (read-slot object slot reader))
+ (print-prim v type field stream
+ (or suppress-line-breaks indent))))
((typep (setq msg (and type (or (find-message trace type)
- (find-enum trace type))))
+ (find-enum trace type)
+ (find-type-alias trace type))))
'protobuf-message)
(let ((values (if slot (read-slot object slot reader) (list object))))
(when values
(if suppress-line-breaks
(format stream "~A { " (proto-name field))
(format stream "~&~VT~A {~%" indent (proto-name field)))
- (map () (curry #'do-field v msg indent)
- (proto-fields msg))
+ (dolist (f (proto-fields msg))
+ (do-field v msg indent f))
(if suppress-line-breaks
(format stream "} ")
(format stream "~&~VT}~%" indent)))))))
((typep msg 'protobuf-enum)
- (map () #'(lambda (v)
- (print-enum v msg field stream
- (or suppress-line-breaks indent)))
- (read-slot object slot reader)))))
+ (doseq (v (read-slot object slot reader))
+ (print-enum v msg field stream
+ (or suppress-line-breaks indent))))
+ ((typep msg 'protobuf-type-alias)
+ (let ((type (proto-proto-type msg)))
+ (doseq (v (read-slot object slot reader))
+ (let ((v (funcall (proto-serializer msg) v)))
+ (print-prim v type field stream
+ (or suppress-line-breaks indent))))))
+ (t
+ (undefined-field-type "While printing ~S to text format,"
+ object type field))))
(t
- (cond ((keywordp type)
+ (cond ((eq type :bool)
+ (let ((v (cond ((or (eq (proto-required field) :required)
+ (null slot))
+ (read-slot object slot reader))
+ ((slot-boundp object slot)
+ (read-slot object slot reader))
+ (t :unbound))))
+ (unless (eq v :unbound)
+ (print-prim v type field stream
+ (or suppress-line-breaks indent)))))
+ ((keywordp type)
(let ((v (read-slot object slot reader)))
- (when (or v (eq type :bool))
+ (when (and v (not (equal v (proto-default field))))
(print-prim v type field stream
(or suppress-line-breaks indent)))))
((typep (setq msg (and type (or (find-message trace type)
- (find-enum trace type))))
+ (find-enum trace type)
+ (find-type-alias trace type))))
'protobuf-message)
(let ((v (if slot (read-slot object slot reader) object)))
(when v
(if suppress-line-breaks
(format stream "~A { " (proto-name field))
(format stream "~&~VT~A {~%" indent (proto-name field)))
- (map () (curry #'do-field v msg indent)
- (proto-fields msg))
+ (dolist (f (proto-fields msg))
+ (do-field v msg indent f))
(if suppress-line-breaks
(format stream "} ")
(format stream "~&~VT}~%" indent))))))
((typep msg 'protobuf-enum)
(let ((v (read-slot object slot reader)))
- (when v
+ (when (and v (not (eql v (proto-default field))))
(print-enum v msg field stream
- (or suppress-line-breaks indent))))))))))))
+ (or suppress-line-breaks indent)))))
+ ((typep msg 'protobuf-type-alias)
+ (let ((v (read-slot object slot reader)))
+ (when v
+ (let ((v (funcall (proto-serializer msg) v))
+ (type (proto-proto-type msg)))
+ (print-prim v type field stream
+ (or suppress-line-breaks indent))))))
+ (t
+ (undefined-field-type "While printing ~S to text format,"
+ object type field)))))))))
(declare (dynamic-extent #'do-field))
- (if suppress-line-breaks
- (format stream "~A { " (proto-name message))
- (format stream "~&~A {~%" (proto-name message)))
- (map () (curry #'do-field object message 0) (proto-fields message))
+ (if print-name
+ (if suppress-line-breaks
+ (format stream "~A { " (proto-name message))
+ (format stream "~&~A {~%" (proto-name message)))
+ (format stream "{"))
+ (dolist (f (proto-fields message))
+ (do-field object message 0 f))
(if suppress-line-breaks
(format stream "}")
(format stream "~&}~%"))
;;; Parse objects that were serialized using the text format
-(defgeneric parse-text-format (type &key stream)
+(defgeneric parse-text-format (type &key stream parse-name)
(:documentation
"Parses an object of type 'type' from the stream 'stream' using the textual format."))
-(defmethod parse-text-format ((type symbol) &key (stream *standard-input*))
+(defmethod parse-text-format ((type symbol)
+ &key (stream *standard-input*) (parse-name t))
(let ((message (find-message-for-class type)))
- (assert message ()
- "There is no Protobuf message having the type ~S" type)
- (parse-text-format message :stream stream)))
+ (unless message
+ (serialization-error "There is no Protobuf message having the type ~S" type))
+ (parse-text-format message :stream stream :parse-name parse-name)))
-(defmethod parse-text-format ((message protobuf-message) &key (stream *standard-input*))
- (let ((name (parse-token stream)))
- (assert (string= name (proto-name message)) ()
- "The message is not of the expected type ~A" (proto-name message)))
+(defmethod parse-text-format ((message protobuf-message)
+ &key (stream *standard-input*) (parse-name t))
+ (when parse-name
+ (let ((name (parse-token stream)))
+ (unless (string= name (proto-name message))
+ (serialization-error "The message is not of the expected type ~A" (proto-name message)))))
(labels ((deserialize (type trace)
(let* ((message (find-message trace type))
(object (and message
(setf (slot-value object slot) (nreverse (slot-value object slot))))
(return-from deserialize object))
(let* ((name (parse-token stream))
- (field (and name (find name (proto-fields message) :key #'proto-name :test #'string=)))
+ (field (and name (find-field message name)))
(type (and field (if (eq (proto-class field) 'boolean) :bool (proto-class field))))
(slot (and field (proto-value field)))
msg)
(let ((val (case type
((:float :double) (parse-float stream))
((:string) (parse-string stream))
- ((:bool) (if (string= (parse-token stream) "true") t nil))
- (otherwise (parse-int stream)))))
+ ((:bool) (if (boolean-true-p (parse-token stream)) t nil))
+ (otherwise (parse-signed-int stream)))))
(when slot
(pushnew slot rslots)
(push val (slot-value object slot)))))
((typep (setq msg (and type (or (find-message trace type)
- (find-enum trace type))))
+ (find-enum trace type)
+ (find-type-alias trace type))))
'protobuf-message)
(when (eql (peek-char nil stream nil) #\:)
(read-char stream))
(val (and enum (proto-value enum))))
(when slot
(pushnew slot rslots)
- (push val (slot-value object slot)))))))
+ (push val (slot-value object slot)))))
+ ((typep msg 'protobuf-type-alias)
+ (let ((type (proto-proto-type msg)))
+ (expect-char stream #\:)
+ (let ((val (case type
+ ((:float :double) (parse-float stream))
+ ((:string) (parse-string stream))
+ ((:bool) (if (boolean-true-p (parse-token stream)) t nil))
+ (otherwise (parse-signed-int stream)))))
+ (when slot
+ (pushnew slot rslots)
+ (push (funcall (proto-deserializer msg) val)
+ (slot-value object slot))))))
+ (t
+ (undefined-field-type "While parsing ~S from text format,"
+ message type field))))
(t
(cond ((keywordp type)
(expect-char stream #\:)
(let ((val (case type
((:float :double) (parse-float stream))
((:string) (parse-string stream))
- ((:bool) (if (string= (parse-token stream) "true") t nil))
- (otherwise (parse-int stream)))))
+ ((:bool) (if (boolean-true-p (parse-token stream)) t nil))
+ (otherwise (parse-signed-int stream)))))
(when slot
(setf (slot-value object slot) val))))
((typep (setq msg (and type (or (find-message trace type)
- (find-enum trace type))))
+ (find-enum trace type)
+ (find-type-alias trace type))))
'protobuf-message)
(when (eql (peek-char nil stream nil) #\:)
(read-char stream))
(enum (find name (proto-values msg) :key #'proto-name :test #'string=))
(val (and enum (proto-value enum))))
(when slot
- (setf (slot-value object slot) val))))))))))))
- (skip-field (stream)
- ;; Skip either a token or a balanced {}-pair
- (ecase (peek-char nil stream nil)
- ((#\:)
- (read-char stream)
- (skip-whitespace stream)
- (parse-token-or-string stream))
- ((#\{)
- (let ((depth 0))
- (loop for ch = (read-char stream)
- do (cond ((eql ch #\")
- (loop for ch0 = (read-char stream)
- until (eql ch0 #\")))
- ((eql ch #\{)
- (iincf depth))
- ((eql ch #\})
- (idecf depth)))
- until (i= depth 0)))))))
- (declare (dynamic-extent #'deserialize #'skip-field))
+ (setf (slot-value object slot) val))))
+ ((typep msg 'protobuf-type-alias)
+ (let ((type (proto-proto-type msg)))
+ (expect-char stream #\:)
+ (let ((val (case type
+ ((:float :double) (parse-float stream))
+ ((:string) (parse-string stream))
+ ((:bool) (if (boolean-true-p (parse-token stream)) t nil))
+ (otherwise (parse-signed-int stream)))))
+ (when slot
+ (setf (slot-value object slot)
+ (funcall (proto-deserializer msg) val))))))
+ (t
+ (undefined-field-type "While parsing ~S from text format,"
+ message type field)))))))))))
+ (declare (dynamic-extent #'deserialize))
(deserialize (proto-class message) message)))
+
+(defun skip-field (stream)
+ "Skip either a token or a balanced {}-pair."
+ (ecase (peek-char nil stream nil)
+ ((#\:)
+ (read-char stream)
+ (skip-whitespace stream)
+ (parse-token-or-string stream))
+ ((#\{)
+ (let ((depth 0))
+ (loop for ch = (read-char stream)
+ do (cond ((eql ch #\")
+ (loop for ch0 = (read-char stream)
+ until (eql ch0 #\")))
+ ((eql ch #\{)
+ (iincf depth))
+ ((eql ch #\})
+ (idecf depth)))
+ until (i= depth 0))))))