Классы стиля ES6 в Parenscript
Есть ли достойный способ написать макрос класса Parenscript, который выводит определения классов ES6?
Если определения классов выглядят так:
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?
2 ответа
Решение
Решение 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, которого нет в официальном выпуске.
- Это ужасно хакерская вещь, и она не будет хорошо разбираться.