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.
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.
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)
This library depends following libraries:
- alexandria
- named-readtables
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)
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 ()))
Just a shorthand of (eval-when (:compile-toplevel) ...)
Just a shorthand of (eval-when (:load-toplevel) ...)
Just a shorthand of (eval-when (:execute) ...)
Just a shorthand of (declaim (declaration ...))
.
(cl-annot-revisit:declaration (hoge fuga))
It is equivalent to:
(declaim (declaration hoge fuga))
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!")
Adds cl:ignorable
declaration into the BODY.
Check cl-annot-revisit:ignore
to see how it works.
Adds cl:dynamic-extent
declaration into the BODY.
Check cl-annot-revisit:ignore
to see how it works.
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)
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)
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)))
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)))
Adds notinline
declaration or proclamation into BODY.
How this is expanded is described in cl-annot-revisit:notinline
description.
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.
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
.
Just an alias of (cl-annot-revisit:documentation ...)
.
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
.
For defclass
and define-condition
, cl-annot-revisit:export
exports its name.
You can use following macros for exporting slots or accessors.
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.
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
.
Exports the class name, slot names, and accessors in each defclass
and define-condition
form in FORMS.
Adds (:metaclass CLASS-NAME)
option to each defclass
and define-condition
form in FORMS.
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.
Exports constructor names made by defstruct
form in 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
).
These macros are designed to be embed with #.
(read-time eval).
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)))
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.)
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.
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.)
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@
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"
.
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!)
defannotation
is in cl-annot-revisit-compat
.
See REAMDE_cl-annot-revisit-compat about that.
- 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.
Copyright © 2021-2022 YOKOTA Yuki <[email protected]>
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.