git.fiddlerwoaroof.com
Raw Blame History
(in-package :cl-user)
(defpackage yaml.parser
  (:use :cl)
  (:import-from :alexandria
                :destructuring-case)
  (:import-from :libyaml.macros
                :with-parser
                :with-event)
  (:export :parse-string
	   :register-scalar-converter
	   :register-sequence-converter
	   :register-mapping-converter)
  (:documentation "The YAML parser."))
(in-package :yaml.parser)

(defvar +scalar-converters+ (make-hash-table :test #'equalp))
(defvar +sequence-converters+ (make-hash-table :test #'equalp))
(defvar +mapping-converters+ (make-hash-table :test #'equalp))

(defun scalar-converter (tag)
  (gethash tag +scalar-converters+))

(defun convert-scalar (string tag &optional (style :plain-scalar-stype))
  (let ((converter (scalar-converter tag)))
    (if converter
	(funcall converter string)
	(yaml.scalar:parse-scalar string style))))

(defun sequence-converter (tag)
  (gethash tag +sequence-converters+))

(defun convert-sequence (list tag)
  (let ((converter (sequence-converter tag)))
    (if converter
	(funcall converter list)
	list)))

(defun mapping-converter (tag)
  (gethash tag +mapping-converters+))

(defun convert-mapping (hashtable tag)
  (let ((converter (mapping-converter tag)))
    (if converter
	(funcall converter hashtable)
	hashtable)))

;;; The parser

(defun signal-reader-error (parser)
  (let ((message (libyaml.parser:error-message parser))
        (line (libyaml.parser:error-line parser))
        (column (libyaml.parser:error-column parser)))
    (error 'yaml.error:parsing-error
           :message message
           :line line
           :column column)))

(defun parse-yaml (input)
  "Parse a YAML string, returning a list of tokens."
  (let ((output (make-array 0 :fill-pointer 0 :adjustable t)))
    (with-parser (parser input)
      (with-event (event)
        (loop do
          ;; Parse the next event, checking for errors
          (let ((parsing-result (libyaml.parser:parse parser event)))
            (if parsing-result
                ;; Decide what to do with the event
                (let ((type (libyaml.event:event-type event)))
                  (flet ((add-to-output (data)
                           (vector-push-extend data output)))
                    (cond
                      ;; Stream events
                      ((eql type :stream-start-event)
                       ;; Do nothing
                       t)
                      ((eql type :stream-end-event)
                       (return-from parse-yaml output))
                      ;; Document events, push them to the output list
                      ((or (eql type :document-start-event)
                           (eql type :document-end-event))
                       (add-to-output (list type)))
                      ;; Alias and scalar event, push the type and data pair to
                      ;; the output list. Disabled since they are not supported.
                      #|
                      ((eql type :alias-event)
                       (add-to-output
                        (cons type
                              (libyaml.event:event-alias-data event))))
                      |#
                      ((eql type :scalar-event)
                       (add-to-output
                        (cons type
                              (libyaml.event:event-scalar-data event))))
                      ;; Sequence start and end events
                      ((eql type :sequence-start-event)
                       (add-to-output
                        (cons type
                              (libyaml.event:event-sequence-start-data event))))
                      ((eql type :sequence-end-event)
                       (add-to-output (list type)))
                      ;; Mapping start and end events
                      ((eql type :mapping-start-event)
                       (add-to-output
                        (cons type
                              (libyaml.event:event-mapping-start-data event))))
                      ((eql type :mapping-end-event)
                       (add-to-output (list type))))))
                ;; Signal an error
                (signal-reader-error parser))))))))

(defun parse-tokens (vector)
  (let ((contexts (list (list :documents))))
    (loop for token across vector do
      (destructuring-case token
        ;; Documents
        ((:document-start-event)
         (push (list) contexts))
        ((:document-end-event)
         (let ((con (pop contexts)))
           (setf (first contexts)
                 (append (first contexts)
                         con))))
        ;; Alias event
        ;; Disabled since it's not supported
        #|
        ((:alias-event &key anchor)
         (declare (ignore anchor))
         t)
        |#
        ;; Scalar
        ((:scalar-event &key anchor tag value length plain-implicit quoted-implicit style)
         (declare (ignore anchor length plain-implicit quoted-implicit))
         (setf (first contexts)
               (append (first contexts)
                       (list (convert-scalar value tag style)))))
        ;; Sequence start event
        ((:sequence-start-event &key anchor tag implicit style)
         (declare (ignore anchor implicit style))
         (push (list tag) contexts))
        ;; Mapping start event
        ((:mapping-start-event &key anchor tag implicit style)
         (declare (ignore anchor implicit style))
         (push (list tag) contexts))
        ;; End events
        ((:sequence-end-event)
         (destructuring-bind (tag &rest seq) (pop contexts)
           (setf (first contexts)
                 (append (first contexts)
                         (list (convert-sequence seq tag))))))
        ((:mapping-end-event)
         (destructuring-bind (tag &rest plist) (pop contexts)
           (setf (first contexts)
                 (append (first contexts)
                         (list (convert-mapping
				(alexandria:plist-hash-table plist :test #'equalp)
				tag))))))
        ;; Do nothing
        ((t &rest rest)
         (declare (ignore rest)))))
    (first contexts)))

;;; The public interface

(defun register-scalar-converter (tag converter)
  (setf (gethash tag +scalar-converters+) converter))

(defun register-sequence-converter (tag converter)
  (setf (gethash tag +sequence-converters+) converter))

(defun register-mapping-converter (tag converter)
  (setf (gethash tag +mapping-converters+) converter))

(defun parse-string (yaml-string)
  (parse-tokens (parse-yaml yaml-string)))