Awesome
Abstract
cl-annot-revisit is a re-implementation of cl-annot, an annotation library for Common Lisp.
My main motivation for implementing it again is to split its concept into two parts:
- Normal
defmacro
s acting like cl-annot's annotations such asexport
anddoc
. Conceptually, form overriding and rewriting can be implemented just withdefmacro
. @
reader macro which just wraps forms with()
, like@foo bar
→(foo bar)
.
For instance, consider this example:
(named-readtables:in-readtable cl-annot-revisit:at-syntax-readtable)
@cl-annot-revisit:export
@(cl-annot-revisit:optimize ((speed 3) (safety 0)))
(cl-annot-revisit:inline
(defun foo ()
"Hello, World!")
(defun bar (x)
(1+ x)))
@
reader macro expand it to a nested form:
(cl-annot-revisit:export
(cl-annot-revisit:optimize ((speed 3) (safety 0))
(cl-annot-revisit:inline
(defun foo ()
"Hello, World!")
(defun bar (x)
(1+ x)))))
The export
, optimize
, and inline
macros rewrite the defun
form working like below (The actual expansion is more complicated.):
(progn
(eval-when (:compile-toplevel :load-toplevel :execute)
(export '(foo bar))) ; by `cl-annot-revisit:export'
(declaim (inline foo bar)) ; by `cl-annot-revisit:inline'
(defun foo ()
(declare (optimize (speed 3) (safety 0))) ; by `cl-annot-revisit:optimize'
"Hello, World!")
(defun bar (x)
(declare (optimize (speed 3) (safety 0))) ; by `cl-annot-revisit:optimize'
(1+ x)))
Other motiviations are:
- Fix many bugs of cl-annot (bugs are described in this page (in Japanese)).
- Show the funny infinite annotation I found. See
#@
syntax below.
These motivations are described in this article (Japanese) also.
Before Using This...
I encourage you to read the following articles;
- Comments in Reader Macros | Common Lisp - Bad Examples, discussing this kind of reader macro.
- Why I don't like eval-always and I Still Don't Like EVAL-ALWAYS by Nikodemus Siivola.
Please consider these alternatives:
- The
nest
macro, introduced in A tale of many nests by @fare, to flatten nested macros. - How to Check Slots Types at make-instance, to make CLOS slots "optional" or "required".
- Simply enclose your forms with
()
, instead of@
reader macro. One good thing to use()
is it specifies arguments explicitly.@
reader macro implicitly affects some forms after that.
Loading
cl-annot-revisit is not Quicklisp-ready now.
At this time, clone this repository, locate it into
~/quicklisp/local-projects/
, and:
(ql:quickload "cl-annot-revisit")
or
(asdf:load-asd "cl-annot-revisit.asd")
(asdf:load-system :cl-annot-revisit)
Dependency
This library depends following libraries:
- alexandria
- named-readtables
Running Tests
Test codes are in :cl-annot-revisit-test
defsystem. You can call them by below:
(ql:quickload :cl-annot-revisit-test)
(asdf:test-system :cl-annot-revisit)
or
(asdf:load-asd "cl-annot-revisit.asd")
(asdf:load-asd "cl-annot-revisit-compat.asd")
(asdf:load-asd "cl-annot-revisit-test.asd")
(asdf:load-system :cl-annot-revisit-test)
(asdf:test-system :cl-annot-revisit)
Macro usage
eval-when
shorthands
[Macro] cl-annot-revisit:eval-always
&body body
Just a shorthand of (eval-when (:compile-toplevel :load-toplevel :execute) ...)
.
(cl-annot-revisit:eval-always
(defun foo ()))
It is equivalent to:
(eval-when (:compile-toplevel :load-toplevel :execute)
(defun foo ()))
[Macro] cl-annot-revisit:eval-when-compile
&body body
Just a shorthand of (eval-when (:compile-toplevel) ...)
[Macro] cl-annot-revisit:eval-when-load
&body body
Just a shorthand of (eval-when (:load-toplevel) ...)
[Macro] cl-annot-revisit:eval-when-execute
&body body
Just a shorthand of (eval-when (:execute) ...)
Declarations
[Macro] cl-annot-revisit:declaration
((&rest names))
Just a shorthand of (declaim (declaration ...))
.
(cl-annot-revisit:declaration (hoge fuga))
It is equivalent to:
(declaim (declaration hoge fuga))
[Macro] cl-annot-revisit:ignore
name-or-names &body body
Adds cl:ignore
declaration into the BODY.
(cl-annot-revisit:ignore (x y z)
(defun foo (x y z)
"Hello, World!"))
It is equivalent to:
(defun foo (x y z)
(declare (ignore x y z))
"Hello, World!")
If BODY is null, this is expanded to a quoted (declare (ignore ...))
form, to embed declarations using #.
.
(This feature is to follow the original cl-annot semantics.)
(defun foo (x y z)
#.(cl-annot-revisit:ignore (x y z)) ; same as writing (declare (ignore x y z))
"Hello, World!")
[Macro] cl-annot-revisit:ignorable
name-or-names &body body
Adds cl:ignorable
declaration into the BODY.
Check cl-annot-revisit:ignore
to see how it works.
[Macro] cl-annot-revisit:dynamic-extent
name-or-names &body body
Adds cl:dynamic-extent
declaration into the BODY.
Check cl-annot-revisit:ignore
to see how it works.
[Macro] cl-annot-revisit:special
&optional vars-or-form &body body
Adds special
declaration or proclamation into BODY. This macro has three syntaxes.
- If the first arg is a variable name or a list of names and BODY is not null, it adds a
declare
.
(cl-annot-revisit:special *x*
(defun foo (*x*) 100))
It is equivalent to
(defun foo (*x*)
(declare (special *x*))
100)
- If the first arg is not names, it tries to add
declaim
.
(cl-annot-revisit:special
(defvar *x* 1)
(defvar *y* 2)
(defun foo (x) 100))
It is equivalent to
(progn (declaim (special *x*))
(defvar *x* 1)
(declaim (special *y*))
(defvar *y* 2)
(defun foo (x) 100))
- If the first arg is a name or a list of names and BODY is null, it is expanded to
declaim
and quoteddeclare
form.
(cl-annot-revisit:special (*x* *y*))
is expanded to
(progn (declaim (special *x* *y*))
'(declare (special *x* *y*)))
This works as declaim
at toplevel and can be embed as declarations using #.
.
(defun foo (*x*)
#.(cl-annot-revisit:special (*x*))
100)
It is equivalent to
(defun foo (*x*)
(declare (special *x*))
100)
[Macro] cl-annot-revisit:type
typespec &optional vars-or-form &body body
Adds type
declaration or proclamation into BODY.
How this is expanded is described in cl-annot-revisit:special
description.
The following example is "1. Adding a declaration" case:
(cl-annot-revisit:type integer x
(defun foo (x) 100))
It is equivalent to:
(defun foo (x)
(declare (type integer x))
100)
[Macro] cl-annot-revisit:ftype
typespec &optional vars-or-form &body body
Adds ftype
declaration or proclamation into BODY.
How this is expanded is described in cl-annot-revisit:special
description.
The following example is "2. Adding a proclamation" case:
(cl-annot-revisit:ftype (function (integer integer) integer)
(defun foo (x y) (+ x y)))
It is equivalent to:
(progn (declaim (ftype (function (integer integer) integer) foo))
(defun foo (x y)
(+ x y)))
[Macro] cl-annot-revisit:inline
&optional names-or-form &body body
Adds inline
declaration or proclamation into BODY. This macro has two syntaxes.
How this is expanded is described in cl-annot-revisit:special
description.
The following example is "3. Toplevel declamation" case:
(cl-annot-revisit:inline (foo))
It is equivalent to:
(progn (declaim (inline foo))
'(declare (inline foo)))
[Macro] cl-annot-revisit:notinline
&optional names-or-form &body body
Adds notinline
declaration or proclamation into BODY.
How this is expanded is described in cl-annot-revisit:notinline
description.
[Macro] cl-annot-revisit:optimize
&optional qualities &body body
Adds optimize
declaration or proclamation into BODY. This macro has two syntaxes.
- If BODY is not null, it add a
declare
into BODY.
(cl-annot-revisit:optimize (speed safety)
(defun foo (x) (1+ x)))
It is equivalent to:
(defun foo (x)
(declare (optimize speed safety))
(1+ x))
- If BODY is null, it is expanded to
declaim
and quoteddeclare
.
(cl-annot-revisit:optimize ((speed 3) (safety 0) (debug 0)))
It is equivalent to:
(progn (declaim (optimize (speed 3) (safety 0) (debug 0)))
'(declare (optimize (speed 3) (safety 0) (debug 0))))
Refer cl-annot-revisit:special
description to see why both declaim
and declare
appeared.
Docstrings
[Macro] cl-annot-revisit:documentation
docstring &body body
Adds docstring to things defined in the BODY.
(cl-annot-revisit:documentation "docstring"
(defun foo (x) (1+ x)))
This example will add "docstring" as a documentation to the function foo
.
[Macro] cl-annot-revisit:doc
docstring &body body
Just an alias of (cl-annot-revisit:documentation ...)
.
Export
[Macro] cl-annot-revisit:export
&body forms
export
symbols naming things defined in the BODY.
(cl-annot-revisit:export
(defun foo () t)
(defvar *bar*)
(defclass baz () ()))
This example will export foo
, *bar*
, and baz
.
Macros treating defclass
form
For defclass
and define-condition
, cl-annot-revisit:export
exports its name.
You can use following macros for exporting slots or accessors.
[Macro] cl-annot-revisit:export-slots
&body forms
Exports all slot-names in each defclass
and define-condition
form in FORMS.
(cl-annot-revisit:export-slots
(defclass foo ()
(slot1
(slot2))))
The above example will export slot1
and slot2
symbols.
[Macro] cl-annot-revisit:export-accessors
&body forms
Exports all accessors in each defclass
, defune-condifion
and defstruct
forms in FORMS.
(cl-annot-revisit:export-accessors
p (defclass foo ()
((slot1 :accessor foo-slot1-accessor)
(slot2 :reader foo-slot2-reader :writer foo-slot2-writer)))
(defstruct bar
slot1
slot2))
The above example will export five symbols; foo-slot1-accessor
, foo-slot2-writer
and bar-slot1-accessor
, bar-slot1
and bar-slot2
.
[Macro] cl-annot-revisit:export-class
&body forms
Exports the class name, slot names, and accessors in each defclass
and define-condition
form in FORMS.
[Macro] cl-annot-revisit:metaclass
class-name &body forms
Adds (:metaclass CLASS-NAME)
option to each defclass
and define-condition
form in FORMS.
Macros treating defstruct
form
For defstruct
, cl-annot-revisit:export
exports its name.
cl-annot-revisit:export-accessors
works for exporting accessor functions (see above).
You can use following macros for exporting other functions made by defstruct
form.
[Macro] cl-annot-revisit:export-constructors
&body forms
Exports constructor names made by defstruct
form in FORMS.
[Macro] cl-annot-revisit:export-structure
&body forms
Exports all names made by defstruct
form in FORMS.
(cl-annot-revisit:export-structure
(defstruct (foo-struct (:conc-name foo-))
slot1 slot2))
The above example will export its name (foo-struct
), constructor (make-foo-struct
), copier (copy-foo-struct
), predicate (foo-struct-p
), and accessors (foo-slot1
and foo-slot2
).
Macros treating defclass
slots
These macros are designed to be embed with #.
(read-time eval).
[Macro] cl-annot-revisit:optional
form slot-speficier
Inserts :initform FORM
into the SLOT-SPECIFIER.
(defclass foo ()
(#.(cl-annot-revisit:optional t slot1)
#.(cl-annot-revisit:optional nil (slot2 :initarg :slot2))))
It is equivalent to:
(defclass foo ()
((slot1 :initform t)
(slot2 :initform nil :initarg :slot2)))
[Macro] cl-annot-revisit:required
slot-speficier
Makes the slot to a kind of required one, by setting its :initform
to a form raises cl-annot-revisit:at-macro-error
.
This error is raised with use-value
restart.
You can fill the slot using the debugger. The following example is from SBCL's REPL.
* (defclass foo ()
(#.(cl-annot-revisit:required slot1)))
#<STANDARD-CLASS COMMON-LISP-USER::FOO>
* (make-instance 'foo)
debugger invoked on a CL-ANNOT-REVISIT-AT-MACRO:AT-REQUIRED-RUNTIME-ERROR in thread
#<THREAD "main thread" RUNNING {1004BF80A3}>:
Must supply SLOT1 slot with :initarg SLOT1
Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL.
restarts (invokable by number or by possibly-abbreviated name):
0: [USE-VALUE] Use a new value.
1: [ABORT ] Exit debugger, returning to top level.
(CL-ANNOT-REVISIT-AT-MACRO::RAISE-REQUIRED-SLOT-ERROR SLOT1 :SLOT1)
source: (ERROR 'AT-REQUIRED-RUNTIME-ERROR :SLOT-NAME SLOT-NAME :INITARG
INITARG-NAME)
0] use-value
Enter a new value: 12345
#<FOO {1001774323}>
* (slot-value * 'slot1)
12345
(Before using this, please see How to Check Slots Types at make-instance.)
'@' syntax usage
This library defines two reader macros, @
and #@
, into cl-annot-revisit:at-syntax-readtable
readtable.
Place (named-readtables:in-readtable cl-annot-revisit:at-syntax-readtable)
to use them.
@(list)
syntax
When a list appears after the @
reader macro, the next form is expanded to the end of the list.
The following example means same as the optimize
example above.
@(cl-annot-revisit:optimize (speed safety))
(defun foo (x) (1+ x))
@
can be used with the standard operators.
@(with-output-to-string (*standard-output*))
(format t "Hello, World!")
This example is expanded to below:
(with-output-to-string (*standard-output*)
(format t "Hello, World!"))
So, this returns a string "Hello, World!"
.
(You see this behavior resembles to the famous nest macro.)
@symbol
syntax
When a symbol appears after the @
reader macro, it reads some following forms and construct a form enclosing ()
.
@cl-annot-revisit:doc "docstring" ; 'doc' takes 2 forms.
@cl-annot-revisit:export ; 'export' takes 1 form.
(defun foo () t)
This example is expanded to below:
(cl-annot-revisit:doc "docstring"
(cl-annot-revisit:export
(defun foo () t)))
How many forms read is determined by the symbol, default is 1. You can change it by overriding cl-annot-revisit-at-syntax:find-at-syntax-arity
.
(This syntax is derived from the original cl-annot. I personally prefer @(list)
to this syntax.)
#n@(list)
and #n@symbol
syntax
#n@
syntax works like @
except overriding the number of form to be read with n
.
#n@symbol
exmaple is here.
#5@list 1 2 3 4 5
This means (list 1 2 3 4 5)
, so evaluated to (1 2 3 4 5)
.
#n@(list)
exmaple is here.
#3@(with-output-to-string (*standard-output*))
(format t "foo ")
(format t "bar ")
(format t "baz")
This example is expanded to below:
(with-output-to-string (*standard-output*)
(format t "foo ")
(format t "bar ")
(format t "baz"))
and evaluated to "foo bar baz"
.
infinite annotation
If the infix parameter of #@
is omitted, this macro attempts to collect as many forms as possible until )
appears or reached to EOF. Collected forms are expanded like @
syntax.
The following example is evaluated to T
:
(string= "abcABC123"
#@(concatenate 'string)
"abc"
"ABC"
"123") ; '#@' collects args until here.
Another example. By placing #@cl-annot-revisit:export
at toplevel, it exports everything after that until the end of file.
#@cl-annot-revisit:export
(defun foo ())
(defvar *bar*)
(defconstant +baz+ 100)
;; ...
The above example will export foo
, *bar*
, and +baz+
.
(This feature is just for fun... Don't use it seriously!)
cl-annot compatibility
defannotation
is in cl-annot-revisit-compat
.
See REAMDE_cl-annot-revisit-compat about that.
Known issues
- Macros about declaration (such as
cl-annot-revisit:inline
) do not affect local functions byflet
,labels
,handler-case
andrestart-case
, or local macros bymacrolet
. - These macros do not affect
defgeneric
's method definitions by:method
option. cl-annot-revisit:documentation
andcl-annot-revisit:doc
do not affect local functions or local macros. They do not affect slot's:documentation
option.
License
Copyright © 2021-2022 YOKOTA Yuki <y2q-actionman@users.noreply.github.com>
This work is free. You can redistribute it and/or modify it under the
terms of the Do What The Fuck You Want To Public License, Version 2,
as published by Sam Hocevar. See the COPYING file for more details.