git.fiddlerwoaroof.com
Raw Blame History
(in-package :cl-user)
(defpackage yaml.emitter
  (:use :cl)
  (:import-from :cffi
                :foreign-free
                :null-pointer)
  (:import-from :libyaml.emitter
                :allocate-emitter
                :emitter-initialize
                :emitter-delete
                :set-output
                :stream-start-event-initialize
                :stream-end-event-initialize
                :document-start-event-initialize
                :document-end-event-initialize
                :scalar-event-initialize
                :sequence-start-event-initialize
                :sequence-end-event-initialize
                :mapping-start-event-initialize
                :mapping-end-event-initialize)
  (:import-from :libyaml.event
                :allocate-event
                :event-delete)
  (:import-from :libyaml.write-handler
                :*write-handler-callback*
                :*write-handler-stream*)
  (:export ;; Original interface
           :emit
           :emit-to-string
           :encode
           ;; libyaml based interface
           :stream-start-event
           :stream-end-event
           :document-start-event
           :document-end-event
           :scalar-event
           :sequence-start-event
           :sequence-end-event
           :mapping-start-event
           :mapping-end-event
           :emit-stream
           :emit-document
           :emit-sequence
           :emit-mapping
           :emit-scalar
           :emit-object
           :print-scalar
           :with-emitter-to-stream
           :with-emitter-to-string)
  (:documentation "The YAML emitter."))
(in-package :yaml.emitter)

;;; Encoder functions

(defgeneric encode (value stream)
  (:documentation "Write the YAML corresponding to value to a stream."))

(defmethod encode ((true (eql 't)) stream)
  "Encode true."
  (write-string "true" stream))

(defmethod encode ((true (eql 'nil)) stream)
  "Encode false."
  (write-string "false" stream))

(defmethod encode ((integer integer) stream)
  "Encode an integer."
  (princ integer stream))

(defmethod encode ((float float) stream)
  "Encode a float."
  (princ float stream))

(defmethod encode ((string string) stream)
  "Encode a string."
  ;; (write-string string stream)
  (format stream "~s" string))

(defmethod encode ((list list) stream)
  "Encode a list."
  (write-string "[" stream)
  (loop for sublist on list do
    (encode (first sublist) stream)
    (when (rest sublist)
      (write-string ", " stream)))
  (write-string "]" stream))

(defmethod encode ((vector vector) stream)
  "Encode a vector."
  (encode (loop for elem across vector collecting elem) stream))

(defmethod encode ((table hash-table) stream)
  "Encode a hash table."
  (write-string "{ " stream)
  (loop for sublist on (alexandria:hash-table-keys table) do
    (let ((key (first sublist)))
      (encode key stream)
      (write-string ": " stream)
      (encode (gethash key table) stream)
      (when (rest sublist)
        (write-string ", " stream))))
  (write-string " }" stream))

;;; Interface

(defun emit (value stream)
  "Emit a value to a stream."
  (encode value stream))

(defun emit-to-string (value)
  "Emit a value to string."
  (with-output-to-string (stream)
(emit value stream)))

;;; Wrappers around cl-libyaml event interface with defaults and keyword args

(defun stream-start-event (event &key (encoding :utf8-encoding))
  (stream-start-event-initialize event encoding))

(defun stream-end-event (event)
  (stream-end-event-initialize event))

(defun document-start-event (event &key (version-directive (null-pointer))
                                     (tag-directive-start (null-pointer))
                                     (tag-directive-end (null-pointer))
                                     (implicit nil))
  (document-start-event-initialize event version-directive
                                   tag-directive-start
                                   tag-directive-end
                                   implicit))

(defun document-end-event (event &key (implicit nil))
  (document-end-event-initialize event implicit))

(defun sequence-start-event (event &key (anchor (null-pointer))
                                     (tag (null-pointer))
                                     (implicit nil)
                                     (style :any-sequence-style))
  (sequence-start-event-initialize event anchor tag implicit style))

(defun sequence-end-event (event)
  (sequence-end-event-initialize event))

(defun mapping-start-event (event &key (anchor (null-pointer))
                                    (tag (null-pointer))
                                    (implicit nil)
                                    (style :any-mapping-style))
  (mapping-start-event-initialize event anchor tag implicit style))

(defun mapping-end-event (event)
  (mapping-end-event-initialize event))

(defun scalar-event (event value length &key (anchor (null-pointer))
                                          (tag (null-pointer))
                                          (plain-implicit t)
                                          (quoted-implicit t)
                                          (style :plain-scalar-style))
  (scalar-event-initialize event anchor tag value length
                           plain-implicit quoted-implicit style))

;;; Emitter macros and output functions

;;; When passing a foreign emitter object, it is also paired with a
;;; foreign event object.

(defun foreign-emitter (emitter) (car emitter))

(defun foreign-event (emitter) (cdr emitter))

(defmacro with-emitter-to-stream ((emitter-var output-stream) &rest body)
  (let ((foreign-emitter (gensym "EMITTER"))
        (foreign-event (gensym "EVENT")))
    `(let* ((,foreign-emitter (allocate-emitter))
            (,foreign-event (allocate-event))
            (,emitter-var (cons ,foreign-emitter ,foreign-event))
            (*write-handler-stream* ,output-stream))
      (unwind-protect
           (progn
             (emitter-initialize ,foreign-emitter)
             (set-output ,foreign-emitter *write-handler-callback* (null-pointer))
             ,@body)
        (libyaml.event:event-delete ,foreign-event)
        (libyaml.emitter:emitter-delete ,foreign-emitter)
        (foreign-free ,foreign-event)
        (foreign-free ,foreign-emitter)))))

(defmacro with-emitter-to-string ((emitter-var) &rest body)
  (let ((str (gensym "STR")))
    `(with-output-to-string (,str)
       (with-emitter-to-stream (,emitter-var ,str)
         ,@body))))

(defmacro emit-stream ((emitter &key (encoding :utf8-encoding)) &body body)
  (let ((emitter-value (gensym "EMITTER"))
        (foreign-emitter (gensym "FOREIGN-EMITTER"))
        (foreign-event (gensym "FOREIGN-EVENT")))
    `(let* ((,emitter-value ,emitter)
            (,foreign-emitter (foreign-emitter ,emitter-value))
            (,foreign-event (foreign-event ,emitter-value)))
       (stream-start-event ,foreign-event :encoding ,encoding)
       (libyaml.emitter:emit ,foreign-emitter ,foreign-event)
       ,@body
       (stream-end-event ,foreign-event)
       (libyaml.emitter:emit ,foreign-emitter ,foreign-event))))

(defmacro emit-document ((emitter &rest rest
                                  &key version-directive
                                  tag-directive-start 
                                  tag-directive-end
                                  (implicit nil)) &body body)
  (declare (ignorable version-directive tag-directive-start
                      tag-directive-end implicit))
  (let ((emitter-value (gensym "EMITTER"))
        (foreign-emitter (gensym "FOREIGN-EMITTER"))
        (foreign-event (gensym "FOREIGN-EVENT")))
    `(let* ((,emitter-value ,emitter)
            (,foreign-emitter (foreign-emitter ,emitter-value))
            (,foreign-event (foreign-event ,emitter-value)))
       (apply #'document-start-event ,foreign-event (list ,@rest))
       (libyaml.emitter:emit ,foreign-emitter ,foreign-event)
       ,@body
       (document-end-event ,foreign-event :implicit ,implicit)
       (libyaml.emitter:emit ,foreign-emitter ,foreign-event))))

(defmacro emit-mapping ((emitter &rest rest &key anchor tag implicit style)
                        &body body)
  (declare (ignorable anchor tag implicit style))
  (let ((emitter-value (gensym "EMITTER"))
        (foreign-emitter (gensym "FOREIGN-EMITTER"))
        (foreign-event (gensym "FOREIGN-EVENT")))
    `(let* ((,emitter-value ,emitter)
            (,foreign-emitter (foreign-emitter ,emitter-value))
            (,foreign-event (foreign-event ,emitter-value)))
       (apply #'mapping-start-event ,foreign-event (list ,@rest))
       (libyaml.emitter:emit ,foreign-emitter ,foreign-event)
       ,@body
       (mapping-end-event ,foreign-event)
       (libyaml.emitter:emit ,foreign-emitter ,foreign-event))))

(defmacro emit-sequence ((emitter &rest rest &key anchor tag implicit style)
                         &body body)
  (declare (ignorable anchor tag implicit style))
  (let ((emitter-value (gensym "EMITTER"))
        (foreign-emitter (gensym "FOREIGN-EMITTER"))
        (foreign-event (gensym "FOREIGN-EVENT")))
    `(let* ((,emitter-value ,emitter)
            (,foreign-emitter (foreign-emitter ,emitter-value))
            (,foreign-event (foreign-event ,emitter-value)))
       (apply #'sequence-start-event ,foreign-event (list ,@rest))
       (libyaml.emitter:emit ,foreign-emitter ,foreign-event)
       ,@body
       (sequence-end-event ,foreign-event)
       (libyaml.emitter:emit ,foreign-emitter ,foreign-event))))

(defun emit-scalar (emitter value &rest rest &key anchor tag
                                               plain-implicit
                                               quoted-implicit
                                               style)
  (declare (ignorable anchor tag plain-implicit quoted-implicit style))
  (let ((printed-value (print-scalar value)))
    (apply #'scalar-event (foreign-event emitter)
           printed-value (length printed-value) rest)
    (libyaml.emitter:emit (foreign-emitter emitter) (foreign-event emitter))))

(defgeneric print-scalar (scalar)
  (:documentation "Convert a scalar object into its printed representation"))

(defmethod print-scalar ((scalar (eql 't)))
  "true")

(defmethod print-scalar ((scalar (eql 'nil)))
  "false")

(defmethod print-scalar ((scalar symbol))
  (symbol-name scalar))

(defmethod print-scalar ((scalar string))
  scalar)

(defmethod print-scalar ((scalar integer))
  (princ-to-string scalar))

(defmethod print-scalar ((scalar single-float))
  (let ((*read-default-float-format* 'single-float))
    (princ-to-string scalar)))

(defmethod print-scalar ((scalar double-float))
  (let ((*read-default-float-format* 'double-float))
    (princ-to-string scalar)))

(defgeneric emit-object (emitter obj)
  (:documentation "Emit YAML representation of obj"))

(defmethod emit-object (emitter (obj symbol))
  (emit-scalar emitter obj))

(defmethod emit-object (emitter (obj string))
  (emit-scalar emitter obj))

(defmethod emit-object (emitter (obj integer))
  (emit-scalar emitter obj))

(defmethod emit-object (emitter (obj float))
  (emit-scalar emitter obj))