cl-annot-revisit

2022-11-07

Re-implementation of 'cl-annot', an annotation syntax library for Common Lisp.

Upstream URL

github.com/y2q-actionman/cl-annot-revisit

Author

YOKOTA Yuki <y2q.actionman@gmail.com>

License

WTFPL
README

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:

  1. Normal defmacros acting like cl-annot's annotations such as export and doc. Conceptually, form overriding and rewriting can be implemented just with defmacro.
  2. @ 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:

These motivations are described in this article (Japanese) also.

Before Using This...

I encourage you to read the following articles;

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.

  1. 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)
  1. 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))
  1. If the first arg is a name or a list of names and BODY is null, it is expanded to declaim and quoted declare 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.

  1. 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))
  1. If BODY is null, it is expanded to declaim and quoted declare.
(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 by flet, labels, handler-case and restart-case, or local macros by macrolet.
  • These macros do not affect defgeneric's method definitions by :method option.
  • cl-annot-revisit:documentation and cl-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.

Dependencies (4)

  • 1am
  • alexandria
  • named-readtables
  • trivial-macroexpand-all

Dependents (0)

    • GitHub
    • Quicklisp