Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/guicho271828/recursive-macroexpansion

Provides another `macroexpand`
https://github.com/guicho271828/recursive-macroexpansion

Last synced: 29 days ago
JSON representation

Provides another `macroexpand`

Awesome Lists containing this project

README

        

* Recursive-Macroexpansion

Yet another macro-expansion system
which provides an easier compile-time error handling.

Recursive-Macroexpansion has the completely different expansion algorithm than
Common Lisp's macro expansion, which is based on =macroexpand-1= and =macroexpand=.

# *BIG NOTE*: This is my *FIRST* library written in controversial =CL21=. Thanks to
# [[https://github.com/fukamachi][Fukamachi]] !
#
# *BIG NOTE2*: This should be clearly noted for the people not familier with
# CL21. This library is written in CL21 but /using this library does not require
# doing so/. If you use RMACROEXPAND, it is just a normal common lisp
# function. Symbols are exported just as in Common Lisp.
#
# While dependency to CL21 exists, once installed, CL21 does not interfere
# you, nor does it break your environment (such as overwriting the readtable).
# CL21 introduces many custom readmacros, but one
# notable point in CL21 is its *package local readtable* (implemented with
# =named-readtable=). AFAIK it is safer than the previous readtable manipulation
# method. In CL21, a new =in-package= is introduced so that it automatically loads the
# =named-readtable= with the same name as that of the package.

* API

Four functions/macros are exported.

| | analogous to |
|---------------------------------+---------------------------------------------------------|
| defrmacro | defmacro |
| rmacroexpand | macroexpand , macroexpand-dammit , macroexpand-all etc. |
| recursive-macro-function | macro-function |
| (setf recursive-macro-function) | (setf macro-function) |

* Examples

Ordinary macro expands one layer only. Further expansions are implicitly done by
the other call to macroexpand-1, which users cannot control.
Therefore, we are not able to trap the conditions signalled
in the expansion of the subforms.

For example, the combination below is a rather stupid example:

#+BEGIN_SRC lisp
(defun stupid-error-handler (c)
(format *standard-output* "ignored a stupid error")
(continue c))

(defmacro with-let (var-form value-form &body body)
(handler-bind ((error #'stupid-error-handler))
`(let ((,var-form ,value-form))
,@body)))

(defmacro limited-progn (&body body)
;; stupid assertion
(unless (< (length body) 3)
(cerror "Ignore!" "the body of limited-progn should be of length less than 3 !"))
`(progn ,@body))

(is (tree-equal
'(let ((x 1)) (print x))
(macroexpand '(with-let x 1 (print x)))))

;; expansion works normally if nothing is signalled
(finishes
(with-let x 1 (limited-progn 1 2)))

;; the error is not trapped, so it is visible from the outside
(signals error
(with-let x 1 (limited-progn 1 2 3 4)))
#+END_SRC

On the other hand, with =defrmacro=, we can control the further expansion. What's
special here is =rmacroexpand=.

#+BEGIN_SRC lisp
(defrmacro with-let (var-form value-form &body body)
(handler-bind ((error #'stupid-error-handler))
(rmacroexpand
`(let ((,var-form ,value-form))
,@body))))

(is (tree-equal
'(let ((x 1)) (print x))
(rmacroexpand '(with-let x 1 (print x)))))

;; expansion works normally
(finishes
(rmacroexpand
'(with-let x 1 (limited-progn 1 2))))

;; the error is handled in compile time and ignored.
(finishes
(rmacroexpand
'(with-let x 1 (limited-progn 1 2 3 4))))
)
#+END_SRC

** Considering the surrounding environment

*Note* that the example above IGNORES the surrounding environment
in the expansion of the subforms,
so the subform expansion does not consider the declared information.

#+BEGIN_SRC lisp
(defmacro env-checker (&environment env)
`(quote ,(multiple-value-list (variable-information 'x env))))

(test recursive-macroexpansion-without-environment
(is (tree-equal
'(LET ((X 1))
(DECLARE (TYPE INTEGER X))
(LET ((Y X))
'(NIL NIL NIL)))
(rmacroexpand
'(let ((x 1))
(declare (type integer x))
(with-let y x
(env-checker)))))))
#+END_SRC

In order to pass the outer environment to the subform expander,
call =rmacroexpand= with optional second argument =env=.

#+BEGIN_SRC lisp
(defrmacro with-let-env (&environment env var-form value-form &body body)
(handler-bind ((error #'stupid-error-handler))
(rmacroexpand
`(let ((,var-form ,value-form))
,@body)
env)))

(test recursive-macroexpansion-without-environment
(is (tree-equal
'(LET ((X 1))
(DECLARE (TYPE INTEGER X))
(LET ((Y X))
'(:LEXICAL T ((TYPE . INTEGER)))))
(rmacroexpand
'(let ((x 1))
(declare (type integer x))
(with-let-env y x
(env-checker)))))))
#+END_SRC

** Dependencies

This library is at least tested on implementation listed below:

+ SBCL 1.1.14 on X86-64 Linux 3.2.0-58-generic (author's environment)

Also, it depends on the following libraries:

+ CL21 :: Common Lisp in the 21st Century. by Fukamachi

** Author

+ Masataro Asai

* Copyright

Copyright (c) 2014 Masataro Asai