This library provides advanced facilities to work with declaration forms and can be useful for writing macroses.
I wasn't able to imagine a good enough example, but luckily, this library has great documentation and I stole this example from it.
It is an analogue of the standard
let* form which expands into multiple
let forms. Maybe you know,
let* can have declarations inside. But to process them correctly, you need to place a declaration related to each variable into its own
Here is how this can be done with
;; System and package names are different: POFTHEDAY> (ql:quickload :parse-declarations-1.0) POFTHEDAY> (import '(tcr.parse-declarations-1.0::parse-body tcr.parse-declarations-1.0:filter-declaration-env tcr.parse-declarations-1.0:build-declarations tcr.parse-declarations-1.0:check-declaration-env tcr.parse-declarations-1.0:parse-declarations)) POFTHEDAY> (defmacro custom-let* (bindings &body body &environment macro-env) (flet ((normalize-binding (binding) (cond ((symbolp binding) `(,binding nil)) ((null (cdr binding)) `(,(car binding) nil)) (t binding)))) (multiple-value-bind (real-body decls) (parse-body body :documentation nil) (let ((decl-env (parse-declarations decls macro-env))) (check-declaration-env decl-env :unknown-allowed nil :warn-only t) (labels ((generate-nested-lets (bindings &optional used-binding-names) (if (null bindings) `(locally ,@(build-declarations 'declare (filter-declaration-env decl-env :include :free) (filter-declaration-env decl-env :include :bound :not-affecting used-binding-names)) ,@real-body) (destructuring-bind ((var value) . more-bindings) bindings `(let ((,var ,value)) ,@(build-declarations 'declare (filter-declaration-env decl-env :affecting `(,var))) ,(generate-nested-lets more-bindings (cons var used-binding-names))))))) (generate-nested-lets (mapcar #'normalize-binding bindings)))))))
Pay attention that
parse-body function is intentionally is not exported to not conflict with
alexandria:parse-body. Author considered that many lispers do import all alexandria's symbols into their packages.
By the way, Alexandria implements only a simple version of the
parse-body function and does not support all advanced features of
But let's see how our macro will expand!
POFTHEDAY> (custom-let* ((a 1) (b (* a 0.01))) (declare (type fixnum a) (type single-float b)) (list a b)) ;; It expands to -> (let ((a 1)) (declare (type fixnum a)) (let ((b (* a 0.01))) (declare (type single-float b)) (locally (list a b))))
As you can see, our single declaration form was torn apart and each part placed into the correct place inside nested
There are three phases:
parse-declarationsparses whole body passed to the
custom-let*and extracts forms related to type declarations. It stores them in a special
filter-declaration-envallows us to select only declarations related to the needed variable.
build-declarationstransforms one or many
envobjects into the lists of symbols so that they can be used for the macro-expansion.