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
defmacros acting like cl-annot's annotations such asexportanddoc. 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
nestmacro, 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
declaimand quoteddeclareform.
(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
declareinto 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
declaimand 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 5This 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-caseandrestart-case, or local macros bymacrolet. - These macros do not affect
defgeneric's method definitions by:methodoption. cl-annot-revisit:documentationandcl-annot-revisit:docdo not affect local functions or local macros. They do not affect slot's:documentationoption.
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.