Or see the list of project sponsors.
Documentation | 😀 |
Docstrings | 😀 |
Tests | 😀 |
Examples | 😀 |
RepositoryActivity | 🥺 |
CI | 🥺 |
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 let
form.
Here is how this can be done with parse-declarations
:
;; 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 parse-declarations
.
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 lets
. Great!
There are three phases:
parse-declarations
parses whole body passed to the custom-let*
and extracts forms related to type declarations. It stores them in a special env
object.filter-declaration-env
allows us to select only declarations related to the needed variable.build-declarations
transforms one or many env
objects into the lists of symbols so that they can be used for the macro-expansion.