cl-annot-revisit
2022-11-07
Re-implementation of 'cl-annot', an annotation syntax library for Common Lisp.
Upstream URL
Author
License
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.