2

Is there a decent way to write a Parenscript class macro that outputs ES6 class definitions?

If the class definitions look like this:

class Person {
    sayHello() {
        alert('hello');
    }

    walk() {
        alert('I am walking!');
    }
}

class Student extends Person {
    sayGoodBye() {
        alert('goodBye');
    }

    sayHello() {
        alert('hi, I am a student');
    }
}

I want to write them somewhat like this in Parenscript:

(def-class -person ()
  (say-hello () (alert "hello"))
  (walk () (alert "I am walking!")))

(def-class -student (-person)
  (say-good-bye () (alert "goodBye"))
  (say-hello () (alert "hi, I am a student")))

I have tried a couple of approaches - attached as answers below - but neither of them is entirely satisfactory. Is there a better solution that does not involve re-engineering Parenscript?

BnMcGn
  • 1,440
  • 8
  • 22

2 Answers2

4

Solution 1:

(defpsmacro def-class (name (&optional extends) &body body)
  (multiple-value-bind (constructor others)
      (gadgets:splitfilter (lambda (x) (string-equal (car x) 'constructor)) body)
    (let ((constructor (case (length constructor)
                         (0 nil)
                         (1 (car constructor))
                         (otherwise
                          (error "Class can't have more than one constructor"))))
          (const-lambda-list nil)
          (const-body nil))
      (when constructor
        (setf const-lambda-list (second constructor))
        (set const-body (cddr constructor)))
      `(progn
         (defun ,name ,const-lambda-list
           ,@const-body)
         ,@(mapcar
            (lambda (item)
              `(setf (@ ,name prototype ,(car item))
                     (lambda ,(second item) ,@(cddr item))))
            others)
         ,@(when extends
                 `((setf (@ ,name prototype) (chain -object (create (@ ,name prototype))))
                   (setf (@ ,name prototype constructor) ,name)))))))

Problems:

  • Outputs ES5 javascript.
  • Doesn't (yet) support static methods.
BnMcGn
  • 1,440
  • 8
  • 22
3

Solution 2:

(defpsmacro def-class-es6 (name (&optional extends) &body body)
  (let ((output nil))
    (push (format nil "class ~a~a {~&"
                  (symbol-to-js-string name)
                  (if extends
                      (format nil " extends ~a" (symbol-to-js-string extends))
                      ""))
          output)
    (dolist (itm body)
      (when (atom itm)
        (error "Non-list item found in class body."))
      (let ((staticp nil))
        (when (eq (car itm) :static)
          (setf itm (if (listp (cadr itm)) (cadr itm) (cdr itm)))
          (setf staticp t))
        (destructuring-bind (mname lambda-list . statements) itm
          (push
           (format
            nil "~a~a (~{~a~^ ~}) {~a}~&"
            (if staticp "static " "")
            (symbol-to-js-string mname)
            (mapcar #'symbol-to-js-string lambda-list)
            ;; Use parenscript to express a method body, then peel the
            ;; block contents out and put them in our own block.
            (let ((fbody (eval `(ps (lambda ,lambda-list ,@statements)))))
              (subseq fbody
                      (1+ (position #\{ fbody))
                      (position #\} fbody :from-end t))))
           output))))
    (push "}" output)
    `(lisp-raw ,(apply #'concatenate 'string (nreverse output)))))

This macro outputs ES6 code, but:

  • Relies on a Parenscript extension of mine that is not in the official distibution.
  • Is badly hackish, and won't indent stuff nicely.
BnMcGn
  • 1,440
  • 8
  • 22