- Notifications
You must be signed in to change notification settings - Fork17
Morgan And Grand Iron Clojure
nasser/magic
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
Morgan And Grand Iron Clojure
A functional Clojure compiler library and the start of a standalone Clojure implementation for the CLR.
For deeper analysis follow thedeveloper's blog.
The compiler is feature complete and can be considered beta quality software.
MAGIC is a compiler for Clojure written in Clojure targeting the Common Language Runtime. Its goals are to:
- Take full advantage of the CLR's features to produce better byte code
- Leverage Clojure's functional programming and data structures to be more tunable, composeable, and maintainable
To compile a clojure file or clojure project to .NET assemblies using Magic, you needNostrand.
Nostrand
was built using Magic and Magic is compiled using Nostrand (There is a cyclic dependency to achieve the compiler bootstrapping).
- CloneNostrand
Nostrand runs a clojure functions that will compile your files or projects.
git clone https://github.com/nasser/nostrand.git
- Download the latest Magic dlls from the magic project last build artifact.
Click on the last build in theactions
tab and then click on the artifact at the bottom to download the magic assemblies.
Copy the previously downloaded dlls in the
references
folder ofnostrand
.At the root of the nostrand project, build nostrand.
dotnet build
Nostrand uses the previously copied dll to build itself.
To compile with magic you can chose either dotnet (for net core) or mono (for net framework). Usingmono
is more stable at the moment. As mentioned in thenostrand
readme,nos
is a command that runs a function. for mono thenos
script is innostrand/bin/x64/Debug/net471
. Addnos
to your Path and just create a small bash script to access the command :
mono "/Users/.../nostrand/bin/x64/Debug/net471/Nostrand.exe" "$@"
You now need to create a clojure function that will compile your files and call it with nos, for more info check thenostrand readme. The clojure file with the build function can be placed at the root direcotry of your project.
- To compile clojure files
Here is an example of the build function to compile your files in the current folder.
(nsmytasks)(defnbuild [] (binding [*compile-path*"build"] (compile 'loic-exos)))
Then you call thebuild
function of themytasks
file withnos
:
nos mytasks/build
And your dll will be added in thebuild
folder in the current directory.
- To compile a clojure project
If your project has dependencies or has files in different folders (src, test etc), you need to provide aproject.edn
file. It is similar to adeps.edn
orproject.clj
so you can easily adapt the syntax.
Following, an example of aproject.edn
for a project with 2 files (one in src folder, one in test folder) and a dependency to specs.
{:name"loic exos":source-paths ["src""test"]:dependencies [[:github nasser/test.check"master"]]}
Thisproject.edn
file must be at the root of your clojure project (refer to project tree below).You can now update yourmytasks
file :
(nsmytasks)(defnbuild [] (binding [*compile-path*"build"] (compile 'loic-exos) (compile 'loic-exos-test)))
You can now compile using the same command as before :
nos mytasks/build
The 2 dlls with be added to thebuild
folder. The dependency is added to adeps
folder. Following the project tree to help you visualise what was created.
├── build│ ├── loic_exos.clj.dll│ └── loic_exos_test.clj.dll├── deps│ └── github│ └── nasser│ └── test.check-master│ ├── README.md│ └── clojure│ └── test│ ├── check│ │ ├── clojure_test.clj│ │ ├── generators.clj│ │ ├── properties.clj│ │ └── rose_tree.clj│ └── check.clj├── deps.edn├── project.edn├── src│ └── loic_exos.clj└── test └── loic_exos_test.clj
Note : If you want to recompile the files and some dlls are already present in the compile-path (build
in our example), it won't overwrite, so always delete thebuild
folder before running nos again.
Nostand allows you to run the test and provides a cli REPL.
To run all the tests, go to your magic repo and use the command
nos test/all
You can run tests from the REPL as well. Following an example to run the tests from the namespacemagic.test.logic
:
$ cd path/to/magic$ nos cli-repluser> (require 'magic.test.logic)niluser> (clojure.test/run-tests 'magic.test.logic)Testing magic.test.logicRan6 tests containing124 assertions.0 failures,0 errors.{:test6,:pass124,:fail0,:error0,:type:summary}
After you made changes to a file (for instancemagic.core
), just reload the file in the REPL :
user> (use 'magic.core:reload-all)
MAGIC consumes AST nodes fromclojure.tools.analyzer.clr
. It turns those AST nodes intoMAGE byte code to be compiled into executable MSIL. By using the existing ClojureCLR reader and building onclojure.tools.analyzer
, we avoid rewriting most of what is already a high performance, high quality code. By using MAGE, we are granted the full power of Clojure to reason about generating byte code without withholding any functionality of the CLR.
In MAGIC parlance, acompiler is a function that transforms a single AST node into MAGE byte code. Previous versions of MAGIC called thesesymbolizers but that term is no longer used. For example, a static property likeDateTime/Now
would be analyzed byclojure.tools.analyzer.clr
into a hash map with a:property
containing the correctPropertyInfo
object. The compiler looks like this:
(defnstatic-property-compiler"Symbolic bytecode for static properties" [{:keys [property]:as ast} compilers] (il/call (.GetGetMethod property)))
It extracts thePropertyInfo
from the:property
key in the AST, computes thegetter method, and returns theMAGE byte code for a method invocation of that method.
Note that this is not a side-effecting function, i.e. it does no actual byte code emission. It merely returns thesymbolic byte code to implement the semantics of static property retrieval as pure Clojure data, and MAGE will perform the actual generation of runnable code as a final step. This makes compilers easier to write and test interactively in a REPL.
Note also that the compiler takes an additional argumentcompilers
, though it makes no use of it.compilers
is a map of keywords identifying AST node types (the:op
key in the maptools.analyzer
produces) to compiler functions. The basic one built into MAGIC looks like
(defbase-compilers {:const #'const-compiler:do #'do-compiler:fn #'fn-compiler:let #'let-compiler:local #'local-compiler:binding #'binding-compiler ...
Every compiler is passed such a map, and is expected it pass it down when recursively compiling. The compiler for(do ...)
expressions does exactly this
(defndo-compiler [{:keys [statements ret]} compilers] [(map #(compile % compilers) statements) (compile ret compilers)])
do
expressions analyze to hash maps containing:statements
and:ret
keys referring to all expressions except the last, and the last expression respectively. Thedo
compiler recursively compiles all of these expression, passing itscompilers
argument to them.
Early versions of MAGIC used a multi method in place of this compiler map, but the map has several advantages. Emission can be controlled from the top level by passing in a different map. For example, compilers can be replaced:
(binding [magic/*initial-compilers* (merge magic/base-compilers:let #'my-namespace/other-let-compiler)](magic/compile-fn '(fn [a] (map inc a))))
or updated
(binding [magic/*initial-compilers* (update magic/base-compilers:let (fn [old-let-compiler] (fn [ast compilers] (if-not (condition? ast) (old-let-compiler ast compilers) (my-namespace/other-let-compiler ast compilers)))))](magic/compile-fn '(fn [a] (map inc a))))
Additionally, compilers can change this mapbefore they pass it to their children if they need to. This can be used to tersely implement optimizations, and some Clojure semantics depend on it.magic.core/let-compiler
implements symbol binding using this mechanism.
During the development ofArcadia, it was found that binaries produced by the ClojureCLR compiler did not survive Unity's AOT compilation process to its more restrictive export targets, particularly iOS, WebGL, and the PlayStation. While it is understood that certain more 'dynamic' features of C# are generally not supported on these platforms, the exact cause of the failures is difficult to pinpoint. Additionally, the byte code the standard compiler generates is not as good as it can be in situations where the JVM and CLR semantics do not match up, namely value types and generics. MAGIC was built primarily to support better control over byte code, and a well reasoned approach to Arcadia export.
MAGIC stands for "Morgan And Grand Iron Clojure" (originally "Morgan And Grand IL Compiler"). It is named after theMorgan Avenue and Grand Street intersection in Brooklyn, the location of theKitchen Table Coders studio where the library was developed. "Iron" is the prefix used for dynamic languages ported to the CLR (e.g.IronPython,IronRuby).
Copyright © 2015-2020 Ramsey Nasser and contributers
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.