stoe/references/macroexpand-dammit.lisp

414 lines
14 KiB
Common Lisp

;;; Macroexpand dammit -- a portable code walker for Common Lisp
;;; Written by John Fremlin at MSI (http://www.msi.co.jp) Released
;;; into the public domain.
;;; http://john.freml.in/macroexpand-dammit
;;; Transforms code to return a quoted version its macroexpansion
;;; using the host lisp to implicitly augment the lexical environment.
;;; Expands macros, macrolets, symbol-macros, symbol-macrolets, and
;;; compiler-macros. Removes macrolets and symbol-macrolets.
;;; Supports a few non-standard special forms for current (2009) Lisps.
;;; Lightly tested on SBCL 1.0.29, ClozureCL 1.4-pre, Lispworks 5.1,
;;; Allegro 8.1
;;; 20100301
;; -- do not totally discard macrolet bodies (doh), as
;;; reported by mathrick on #lisp
;; 20100701
;; - correct the mistaken loop bindings to remove warnings for CCL.
;;; reported by Daniel Gackle
(cl:defpackage #:macroexpand-dammit
#+lispworks (:import-from #:lispworks #:compiler-let)
#+ccl (:import-from #:ccl #:compiler-let)
(:use #:cl)
(:export #:macroexpand-dammit
#:macroexpand-dammit-as-macro
#:macroexpand-dammit-expansion))
(cl:in-package #:macroexpand-dammit)
(eval-when (:compile-toplevel :load-toplevel :execute)
(defvar *form-handler* (make-hash-table))
(defun force-first (x)
(if (listp x) (first x) x))
(defun force-list (x)
(if (listp x) x (list x))))
(defvar *env*)
(defun binding-to-symbol (binding)
(let ((name (force-first binding)))
(cond ((listp name)
(assert (eq 'setf (first name)))
(check-type (second name) symbol)
(second name))
(t
name))))
(defmacro with-imposed-bindings (&body body)
`(locally ,@body)
#+sbcl
(destructuring-bind ((binder bindings &rest binder-body))
body
`(locally
(declare (sb-ext:disable-package-locks ,@(mapcar 'binding-to-symbol bindings)))
(,binder ,bindings
,@binder-body))))
(defmacro without-package-locking (&body body)
`(
#. (progn 'progn
#+sbcl 'sb-ext:without-package-locks)
,@body))
(defmacro defhandler (symbol lambda-list &body body)
(let ((syms (force-list symbol)))
(let ((func (intern (format nil "~A~A" 'handler- (first syms)))))
`(progn
(defun ,func ,lambda-list
,@body)
(setf
,@(loop for sym in syms
collect `(gethash ',sym *form-handler*)
collect `',func))))))
(defun e-list (list)
(mapcar #'e list))
(defhandler (progn locally) (progn &rest body)
`(list ',progn
,@(e-list body)))
(defhandler let (let bindings &rest body)
(let* ((names (loop for binding in bindings
collect
(force-first binding)))
(symbol-macrolet-names
(loop for name in names
when (nth-value 1 (macroexpand-1 name *env*))
collect name)))
`(list*
',let
(list
,@(loop for binding in bindings
collect
(if (symbolp binding)
`',binding
`(list ',(first binding)
,@(e-list (rest binding))))))
(with-imposed-bindings
(,let ,symbol-macrolet-names
(declare (ignorable ,@symbol-macrolet-names))
(m-list ,@body))))))
(defun dump-fbinding (name lambda-list &rest body)
(let (bound-vars)
(labels (
(binding-vars (&rest body)
`(let ,bound-vars
(declare (ignorable ,@bound-vars))
(m-list ,@body)))
(l (lambda-arg)
(cond ((member lambda-arg lambda-list-keywords)
`',lambda-arg)
(t
(destructuring-bind
(var &optional (val nil val-present-p) present-var)
(force-list lambda-arg)
(prog1
(if (listp lambda-arg)
`(list ',var ,@(when val-present-p `((car ,(binding-vars val))))
,@(when present-var `(',present-var)))
`',var)
(push var bound-vars)
(when present-var (push present-var bound-vars))))))))
`(list* ',name (list ,@(mapcar #'l lambda-list))
,(apply #'binding-vars body)))))
(defun dump-fbindings (bindings)
`(list ,@(mapcar (lambda (f) (apply 'dump-fbinding f)) bindings)))
(defun declare-fbindings-ignorable (bindings)
`(declare (ignorable ,@(mapcar (lambda (f)
`(function ,(force-first f))) bindings))))
(defun declare-lambda-list-ignorable (lambda-list)
`(declare (ignorable
,@(loop for binding in lambda-list
append
(unless (member binding lambda-list-keywords)
(destructuring-bind (var &optional default present-var)
(force-list binding)
(declare (ignore default))
(list* var (when present-var (list present-var)))))))))
(defun maybe-locally (forms)
(flet ((starts-with-declare ()
(and (listp (first forms)) (eq (first (first forms)) 'declare))))
(cond ((or (rest forms) (starts-with-declare))
(list* (if (starts-with-declare) 'locally 'progn) forms))
(t
(first forms)))))
(defhandler declare (declare &rest body)
`(list ',declare
,@(mapcar (lambda (f) `',f) body)))
(defhandler block (block name &rest body)
`(list ',block ',name
,@(e-list body)))
(defhandler return-from (return-from name &optional (value nil value-p))
`(list ',return-from ',name
,@(when value-p
`(,(e value)))))
(defhandler catch (catch tag &rest body)
`(list ',catch ,(e tag) ,@(e-list body)))
(defhandler load-time-value (load-time-value form &optional (read-only-p nil rop-p))
`(list ',load-time-value ,(e form)
,@(when rop-p
`(',read-only-p))))
(defhandler
(macrolet
symbol-macrolet
compiler-let ; mostly for Lispworks
)
(macrolet bindings &rest body)
`(maybe-locally
(with-imposed-bindings
(,macrolet ,bindings
(m-list ,@body)))))
(defun clean-fbindings (bindings)
"Return a set of bindings that always defaults to nil"
(flet ((clean-argument-bindings (bindings)
(loop for binding in bindings
collect
(destructuring-bind (var &optional default present-var)
(force-list binding)
(declare (ignore default))
(if present-var
`(,var nil ,present-var)
var)))))
(loop for (func lambda-list) in bindings
for clean-lambda-list = (clean-argument-bindings lambda-list)
collect `(,func ,clean-lambda-list
,(declare-lambda-list-ignorable clean-lambda-list)))))
(defhandler flet (flet bindings &rest body)
`(list* ',flet
,(dump-fbindings bindings)
(with-imposed-bindings
(,flet ,(clean-fbindings bindings)
,(declare-fbindings-ignorable bindings)
(m-list ,@body)))))
(defhandler labels (labels bindings &rest body)
`(with-imposed-bindings
(,labels ,(clean-fbindings bindings)
,(declare-fbindings-ignorable bindings)
(list* ',labels
,(dump-fbindings bindings)
(m-list ,@body)))))
(defhandler let* (let* bindings &rest body)
(if (not bindings)
(e `(locally ,@body))
(destructuring-bind (first &rest rest)
bindings
(e `(let (,first)
,@(if rest
`((,let* ,rest (locally ,@body)))
body))))))
(defhandler eval-when (eval-when situation &rest body)
`(list ',eval-when ',situation
,@(e-list body)))
#+sbcl
(defhandler sb-int:named-lambda (named-lambda name lambda-list &rest body)
`(list* ',named-lambda ,(apply 'dump-fbinding name lambda-list body)))
(defhandler defun (defun name lambda-list &rest body)
`(list* ',defun ,(apply 'dump-fbinding name lambda-list body)))
(defhandler lambda (lambda lambda-list &rest body)
(apply 'dump-fbinding lambda lambda-list body))
(defun tagbody-restore-tags (list)
(loop for f in list
collect
(cond ((or (symbolp f) (integerp f))
`(progn ,f))
((and (listp f) (eq 'tagbody-restore-tag (first f)))
(second f))
(t
f))))
(defhandler tagbody (tagbody &rest tags-and-forms)
`(list* ',tagbody
(tagbody-restore-tags
(list
,@(loop for f in tags-and-forms
collect
(if (or (symbolp f) (integerp f))
`(list 'tagbody-restore-tag ',f)
(e f)))))))
(defhandler setq (setq &rest pairs)
(declare (ignore setq))
(let ((vars (loop for s in pairs by #'cddr collect (macroexpand s *env*))))
(let ((expanded (loop for n in vars for r in (rest pairs) by #'cddr
collect n collect r)))
(if (some 'listp vars)
(e `(setf ,@expanded))
`(list 'setq ,@(e-list expanded))))))
(defun function-name-p (name)
(or (symbolp name)
(and (listp name) (eq (first name) 'setf) (symbolp (second name)) (not (cddr name)))))
(defhandler function (function name)
`(list ',function
,(if (function-name-p name)
`',name
(e name))))
(defhandler the (the value-type form)
`(list ',the ',value-type ,(e form)))
(defhandler go (go tag)
`(list ',go ',tag))
(defhandler unwind-protect (unwind-protect protected-form &rest cleanup)
`(list ',unwind-protect ,(e protected-form) ,@(e-list cleanup)))
(defhandler progv (progv symbols values &rest body)
`(list ',progv
(list ,@(e-list symbols))
(list ,@(e-list values))
,@(e-list body)))
(defhandler quote (quote object)
`(list ',quote ',object))
(defun default-form-handler (first &rest rest)
`(list ,(if (symbolp first)
`',first
(e first)) ,@(e-list rest)))
(defun form-handler (first)
(gethash first *form-handler*
'default-form-handler))
(defun compiler-macroexpand-1 (form &optional *env*)
(let ((cm
(and (listp form) (function-name-p (first form))
(compiler-macro-function (first form) *env*))))
(if cm
(funcall *macroexpand-hook* cm form *env*)
form)))
(defun e (form)
(flet ((handle (form)
(apply (form-handler (first form)) form)))
(cond ((and (listp form) (gethash (first form) *form-handler*))
(handle form))
(t
(multiple-value-bind (form expanded)
(macroexpand-1 form *env*)
(cond (expanded
(e form))
(t
(typecase form
(null nil)
(list
(let ((next (compiler-macroexpand-1 form)))
(if (eq form next)
(handle form)
(e next))))
(t
`',form)))))))))
(defmacro m (form &environment *env*)
(e form))
(defmacro m-list (&body body &environment *env*)
`(list ,@(e-list body)))
(defun walk-tree (fn tree &optional (cache (make-hash-table :test 'eq)))
(funcall fn tree
;; given as `cont'
(lambda (subforms)
(%walk-tree-rec subforms fn cache))))
(defun %walk-tree-rec (lst fn cache)
(if (endp lst) nil
(multiple-value-bind (value found) (gethash lst cache)
(if found value
(let* ((result (walk-tree fn (car lst) cache))
(cell (cons result nil)))
(setf (gethash lst cache) cell) ;; cdr is not computed
(setf (cdr cell) ;; this is not stack-free... but is necessary for circular list
(%walk-tree-rec (cdr lst) fn cache))
cell)))))
(defun macroexpand-all-except-macrobindings (body env)
(walk-tree
(lambda (subform cont)
(let ((expansion (macroexpand subform env)))
(if (consp expansion)
(case (first expansion)
((declare quote) expansion)
((macrolet symbol-macrolet)
;; ignore macrolet and symbol-macrolet
`(,(first expansion) ,(second expansion)
,@(funcall cont (cddr expansion))))
(function
(let ((fname (second expansion)))
(if (consp fname)
(case (first fname)
(lambda
`(lambda ,(second fname)
,@(funcall cont (cddr fname))))
#+sbcl
(sb-int:named-lambda
`(sb-int:named-lambda ,(second fname) ,(third fname)
,@(funcall cont (cdddr fname))))
(t expansion))
expansion)))
(t
(funcall cont expansion)))
expansion)))
body))
(defun macroexpand-dammit (form &optional *env*)
(let ((evalform (e form)))
(macroexpand-all-except-macrobindings
(eval evalform)
*env*)))
(defmacro macroexpand-dammit-as-macro (form)
`(m ,form))
(defun macroexpand-dammit-expansion (form &optional *env*)
(e form))
;;; Some shenanigans to support running with or without swank
(defun runtime-symbol (name package-name)
(or (find-symbol (symbol-name name)
(or (find-package package-name) (error "No package ~A" package-name)))
(error "No symbol ~A in package ~A" name package-name)))
(defun macroexpand-dammit-string (str)
(funcall (runtime-symbol 'apply-macro-expander 'swank) 'macroexpand-dammit str))