Parenscript 中的 ES6 样式 类

ES6 style classes in Parenscript

有没有合适的方法来编写输出 ES6 class 定义的 Parenscript class 宏?

如果 class 定义如下所示:

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

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

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

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

我想用 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")))

我尝试了几种方法 - 作为答案附在下面 - 但它们都不完全令人满意。有没有不涉及重新设计Parenscript的更好的解决方案?

解决方案 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)))))))

问题:

  • 输出 ES5 javascript。
  • (尚)不支持静态方法。

解决方案 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)))))

这个宏输出ES6代码,但是:

  • 依赖于我的一个不在官方发行版中的 Parenscript extension
  • 非常黑,不会很好地缩进内容。