This is an incomplete proof-of-concept implementation of a Common Lisp codewalker.
Invoke with:
(walk-subforms FUNCTION FORM)
This is in many ways analogous to mapcar
or map
, except that FUNCTION
should
accept two arguments, a keyword SUBFORM-TYPE
and a SUBFORM
.
As of writing, [2021-09-03], the SUBFORM-TYPE
argument to a walk-subforms
visitor
may take on the following values:
The name of a block, as in block
or return-from
.
A Common Lisp expression, suitable for eval
.
A symbol used in catch
or throw
.
One of the keywords :compile-toplevel
, :load-toplevel
or :execute
, as provided to
eval-when
.
A symbol which names a new function, to be bound by flet
, labels
, defun
, etc.
A symbol which names a new variable, to be bound by let
, let*
, lambda
,
multiple-value-bind
, etc.
A form suitable for the function
special form or the #'~ reader macro, i.e. a symbol
which names a function or a ~lambda
form.
A symbol used in tagbody
or go
.
An unevaluated constant object, e.g. in quote
.
An unquoted type, as in the
or check-type
.
A constant string, to be used as a documentation string.
A constant string or symbol, to be used as a package name.
A form suitable for setf
et.al.
(walk-subforms (lambda (type form)
(format t "~&~a is a(n) ~a~%" form type)
(values form t))
'(let ((a (foo))) (bar a)))
;; LET is a(n) FORM-HEAD
;; A is a(n) VARIABLE-BINDING
;; (FOO) is a(n) EXPR
;; FOO is a(n) FUNCTION
;; (BAR A) is a(n) EXPR
;; BAR is a(n) FUNCTION
;; A is a(n) EXPR
;;
;; => (LET ((A (FOO)))
;; (BAR A))
(walk-subforms (lambda (type form)
(form-typecase (type form)
((:variable-binding :expr)
(if (eq form 'rename-me)
(values 'different-name nil)
(values form t)))))
'(let ((rename-me (rename-me but-not-as-a-function)))
(different-function rename-me)))
;; => (LET ((DIFFERENT-NAME (RENAME-ME BUT-NOT-AS-A-FUNCTION)))
;; (DIFFERENT-FUNCTION DIFFERENT-NAME))
If possible, you should view this list in Emacs with org-mode, rather than in the GitHub viewer, since it has annotations that GitHub seems to discard.
Possibly just ignore these? You still have to detect them, though.Will likely require altering some existing templates to properly handle bodies.
Simple version: instead of treating &body
and &rest
as equivalent, have &body
call alexandria:parse-body
on the provided form, while &rest
retains its current
behavior. This would require some amount of updating existing templates, since some use
&body
in places that do not accept docstrings or declarations.
like, have a set of templates for subforms of ~:type~s, and another for subforms of ~:declaration~s.
If we even want to do this… it’d be quite hard to make matching functions work.
What we’d likely want to do is, when codewalking a form that we believe has type (or
:declaration :expr)
, decide whether it’s a declaration or not, and invoke the visitor
function on either :declaration
or :expr
appropriately, not just pass the form (or
:declaration :expr)
to the visitor function.
Like &key
arguments, only they’re alists instead of plists.
Likely, the template syntax for this will be &alist
, like:
(register-subform-types defgeneric (&rest :variable-binding)
&alist
(:documentation :docstring)
(:method-combination :method-combination)
(:generic-function-class :class-name)
(:method-class :class-name)
(:argument-precedence-order &rest :constant)
(declare &rest :declaration)
(:method etc))
Note that I’m not actually clear on how to parse :method
forms, but whatever.
Like method qualifiers on defmethod
.
At present, lambda
et.al. are treated as accepting only required positional
arguments, not &optional
, &key
or &rest
arguments.
This will likely involve getting the capability to define special form types which have different behavior than just “visit or recurse.”
As in setf
, whose template should be &body :place :expr
.
Needs fix: support for multiple alternatives to match, like in (case foo ((a b)
(print 'early-letter)))
Needs improvement: support for complex lambda lists.