git.fiddlerwoaroof.com
Raw Blame History
(in-package #:data-class)

(eval-when (:compile-toplevel :load-toplevel :execute)
  (defun get-channel-element (xml-dom el)
    ($ (inline xml-dom) el (text) (node))))

(defun element-name-from-symbol (sym)
  (let* ((base (string-downcase sym))
         (split (split-sequence:split-sequence #\~ base))
         (capit (cons (car split) (map 'list #'nstring-capitalize (cdr split)))))
    (apply #'concatenate 'string capit)))

(defmacro ensure-slot (sym &body or-else)
  `(handler-case
     ,sym
     (unbound-slot (c)
       (declare (ignore c))
       ,@or-else)))

(defmacro lazy-load-slot (class-name doc-slot root-el name tag-name &key transform value)
  `(defmethod ,name :before ((self ,class-name))
     (with-slots (,name ,doc-slot) self
       (ensure-slot ,name
         (alet ,(or value
                    `(get-channel-element ,doc-slot
                                          ,(format nil "~a > ~a" root-el tag-name)))
           ,(if transform
              `(setf ,name (when it (,transform it)))
              `(setf ,name it)))))))

(defgeneric %all-slots (self format))
(defun all-slots (self &optional format)
  (%all-slots self format))

(defgeneric slot-tags (self))

(defun process-slots-for-data-class (slots)
  (mapcar (fw.lu::destructuring-lambda ((slot tag . rest))
            (let ((tag (etypecase tag
                         (symbol (string-downcase tag))
                         (string tag))))
              (list* slot (make-keyword slot) tag rest)))
          (fw.lu:ensure-mapping slots)))

(deftest process-slots-for-data-class ()
  (let ((tc-1 '(a))
        (tc-2 '((a b)))
        (tc-3 '((a "b")))
        (tc-4 '((a b c)))
        (tc-5 '((a "b" c)))
        (tc-6 '((a "bC")))
        (tc-7 '((a "bC" d))))
    (should be equal '((a :a "a")) (process-slots-for-data-class tc-1))
    (should be equal '((a :a "b")) (process-slots-for-data-class tc-2))
    (should be equal '((a :a "b")) (process-slots-for-data-class tc-3))
    (should be equal '((a :a "b" c)) (process-slots-for-data-class tc-4))
    (should be equal '((a :a "b" c)) (process-slots-for-data-class tc-5))
    (should be equal '((a :a "bC")) (process-slots-for-data-class tc-6))
    (should be equal '((a :a "bC" d)) (process-slots-for-data-class tc-7))))

(defmacro define-data-class (name (doc-slot root-el) (&rest superclasses) &body slots)
  (declare (optimize (debug 3)))
  (flet ((make-slot-spec (slot slot-keyword)
           `(,slot :initarg ,slot-keyword :accessor ,slot)))
    (let ((slots (process-slots-for-data-class slots)))
      `(progn
         (defclass ,name ,superclasses
           ((slot-tags :allocation :class :initform
                       ',(loop for (_ slot-keyword tag) in slots
                               collect (cons slot-keyword tag)))
            ,@(mapcar (fw.lu::destructuring-lambda ((slot slot-keyword . r))
                        (declare (ignore r))
                        (make-slot-spec slot slot-keyword))
                      slots)))
         ,@(loop for (slot _ tag-name . rest) in slots
                 collect `(lazy-load-slot ,name ,doc-slot ,root-el ,slot ,tag-name ,@rest))
         (defmethod %all-slots ((self ,name) format)
           (pairlis (list ,@(mapcar (fw.lu::alambda (cadr fw.lu::it)) slots))
                    (list ,@(loop for (slot) in slots collect `(,slot self)))))))))