Now we have a Compiler written in Clojure (ClojureScript) we should explore making compilation phases pluggable. This will allow the development of more powerful tools like source analyzers and type checkers.
Analysis phase seems the most useful phase to expose.
Racket achieves this via macros. http://docs.racket-lang.org/reference/syntax.html
For example, #%module-begin (a macro) can be overridden to perform some custom analysis.
Here is how #%module-begin is used by the compiler.
macroexpands to ...
We can see that module-begin wraps every form in the body of a module.
We can override #%module-begin to perform custom analysis.
This example first expands each form after passing them to #%plain-module-begin, then performs some type checking at compile time with type-check-module-body.
(This example is from Typed Racket, see: Advanced Macrology and the Implementation of Typed Scheme)
- hiredman (paraphrased from #clojure): Shouldn't use "binding" form to effect compilation of forms, because it depends on how the "binding" macro expands. Bindings might stop working if the form is wrapped in a "try"
- hiredman: Perhaps extend the "ns" declaration to allow compiler extensions
Proposal (Won't work, see above)
We represent compiler customization points with dynamic vars.
An example use case, similar to the Racket example, is performing some analysis for each top level form when compiling a file.
Using the ClojureScript Compiler, we define a dynamic var *top-level-analyse* that is called for each top level form in a file.
We then modify cljs.compiler/compile-file* to call *top-level-analyze* instead of `analyze`. (See commented line)
We customize compilation by rebinding the dynamic var in user code.
Example implementation of top-level-analyze: