• Stars
    star
    366
  • Rank 116,547 (Top 3 %)
  • Language
    Clojure
  • Created over 9 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Morgan And Grand Iron Clojure

MAGIC

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 the developer's blog.

Status

The compiler is feature complete and can be considered beta quality software.

Overview

MAGIC is a compiler for Clojure written in Clojure targeting the Common Language Runtime. Its goals are to:

  1. Take full advantage of the CLR's features to produce better byte code
  2. Leverage Clojure's functional programming and data structures to be more tunable, composeable, and maintainable

Getting Started

To compile a clojure file or clojure project to .NET assemblies using Magic, you need Nostrand.

Nostrand was built using Magic and Magic is compiled using Nostrand (There is a cyclic dependency to achieve the compiler bootstrapping).

Setup

  1. Clone Nostrand

Nostrand runs a clojure functions that will compile your files or projects.

git clone https://github.com/nasser/nostrand.git
  1. Download the latest Magic dlls from the magic project last build artifact.

Click on the last build in the actions tab and then click on the artifact at the bottom to download the magic assemblies.

  1. Copy the previously downloaded dlls in the references folder of nostrand.

  2. At the root of the nostrand project, build nostrand.

dotnet build

Nostrand uses the previously copied dll to build itself.

Compiling

To compile with magic you can chose either dotnet (for net core) or mono (for net framework). Using mono is more stable at the moment. As mentioned in the nostrand readme, nos is a command that runs a function. for mono the nos script is in nostrand/bin/x64/Debug/net471. Add nos 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 the nostrand 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.

(ns mytasks)

(defn build []
  (binding [*compile-path* "build"]
    (compile 'loic-exos)))

Then you call the build function of the mytasks file with nos:

nos mytasks/build

And your dll will be added in the build 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 a project.edn file. It is similar to a deps.edn or project.clj so you can easily adapt the syntax.

Following, an example of a project.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"]]}

This project.edn file must be at the root of your clojure project (refer to project tree below). You can now update your mytasks file :

(ns mytasks)

(defn build []
  (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 the build folder. The dependency is added to a deps 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 the build folder before running nos again.

Nostand allows you to run the test and provides a cli REPL.

Testing

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 namespace magic.test.logic :

$ cd path/to/magic
$ nos cli-repl
user> (require 'magic.test.logic)
nil
user> (clojure.test/run-tests 'magic.test.logic)

Testing magic.test.logic

Ran 6 tests containing 124 assertions.
0 failures, 0 errors.
{:test 6, :pass 124, :fail 0, :error 0, :type :summary}

Debugging

After you made changes to a file (for instance magic.core), just reload the file in the REPL :

user> (use 'magic.core :reload-all)

Strategy

MAGIC consumes AST nodes from clojure.tools.analyzer.clr. It turns those AST nodes into MAGE byte code to be compiled into executable MSIL. By using the existing ClojureCLR reader and building on clojure.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.

Compilers

In MAGIC parlance, a compiler is a function that transforms a single AST node into MAGE byte code. Previous versions of MAGIC called these symbolizers but that term is no longer used. For example, a static property like DateTime/Now would be analyzed by clojure.tools.analyzer.clr into a hash map with a :property containing the correct PropertyInfo object. The compiler looks like this:

(defn static-property-compiler
  "Symbolic bytecode for static properties"
  [{:keys [property] :as ast} compilers]
  (il/call (.GetGetMethod property)))

It extracts the PropertyInfo from the :property key in the AST, computes the getter method, and returns the MAGE 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 the symbolic 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 argument compilers, though it makes no use of it. compilers is a map of keywords identifying AST node types (the :op key in the map tools.analyzer produces) to compiler functions. The basic one built into MAGIC looks like

(def base-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

(defn do-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. The do compiler recursively compiles all of these expression, passing its compilers 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 map before 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.

Rationale and History

During the development of Arcadia, 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.

Name

MAGIC stands for "Morgan And Grand Iron Clojure" (originally "Morgan And Grand IL Compiler"). It is named after the Morgan Avenue and Grand Street intersection in Brooklyn, the location of the Kitchen Table Coders studio where the library was developed. "Iron" is the prefix used for dynamic languages ported to the CLR (e.g. IronPython, IronRuby).

Legal

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.

More Repositories

1

---

قلب: لغة برمجة
JavaScript
707
star
2

zajal

Experimental creative coding framework
JavaScript
160
star
3

clojurescript-npm

NPM module for the the ClojureScript programming language
Clojure
145
star
4

arduino.tmbundle

The TextMate Arduino Bundle
Makefile
116
star
5

nostrand

Clojure on Mono
C#
69
star
6

ajeeb

Patterns for Playful Systems
JavaScript
69
star
7

mage

Symbolic MSIL bytecode generation for ClojureCLR
Clojure
63
star
8

symba

The Symbolic Assembler
Clojure
55
star
9

pltjs

A programming language design prototyping tool
JavaScript
44
star
10

god.js

New Gods Through Computer Science for Art Hack Day 2013
JavaScript
41
star
11

thixels

PICO8 inspired live coded thick pixel visual instrument
JavaScript
37
star
12

boards

Infinite whiteboards
JavaScript
28
star
13

rejoyce

A concatenative programming language inspired by Manfred von Thun's Joy
JavaScript
24
star
14

stopwork

Minimal HTML 5 Presentation Platform
CSS
23
star
15

rekindle

Hackable E-ink Tablets For Everyone
C
22
star
16

ajeeb-coroutines

Coroutines for the Ajeeb Game Engine
TypeScript
20
star
17

Socket

Sublime Socket REPLs
Python
18
star
18

Magic.Unity

Unity integration for the MAGIC compiler
C#
17
star
19

governingdynamics

The Governing Dynamics of Software - ITP Class on Language Design
13
star
20

sfpc-talk

Source code to the various slideshows used during my September 16, 2013 talk at the SFPC opening
JavaScript
11
star
21

malaf

Markdown + CSS = PDF
CSS
11
star
22

illness

HTML Mono MSIL Visualizer
C#
9
star
23

line

إعادة تخيل النص الرقمي
JavaScript
9
star
24

stop-emailing-me

just stop
JavaScript
9
star
25

ajeeb-ecs

Entity Component System for the Ajeeb Game EngineCoroutines
TypeScript
8
star
26

graffitidrone

Exactly what it sounds like
Ruby
7
star
27

twelve

Experimental Game Engine/Entity Component System
JavaScript
7
star
28

oddball

In-memory Content Addressable Store
JavaScript
6
star
29

Magic.Unity-Example

An example Magic.Unity project
Clojure
6
star
30

blenderhyrepl

Blender Hy REPL
Python
6
star
31

bctp

The Bytecode Transfer Protocol
JavaScript
5
star
32

teaching

Repository for my slides
HTML
5
star
33

dtplayfulsystems

Creative Coding class at Parsons Design + Tech
C
5
star
34

shapeserver

Harfbuzz shaping server
JavaScript
5
star
35

arz

a parser generator that makes nice trees
JavaScript
5
star
36

tools.analyzer.clr

CLR Specific Analysis Passes for clojure.tools.analyzer
Clojure
4
star
37

harfbuzz-js-demo

Correctly shaped 3D text in Three.js via HarfBuzz and OpenType
JavaScript
4
star
38

everyunicode

Twittering every graphical character in the Unicode 6.2 Standard. Task will complete in 2076.
Ruby
3
star
39

rdclcmps

Radical Computer Science at SFPC, Abbreviated
3
star
40

Magic.Runtime

C#
3
star
41

occupied.land

JavaScript
2
star
42

sponge

The Slowest Computer On Earth
HTML
2
star
43

hiccup-magic

A port of the hiccup library to Clojure on the CLR (the MAGIC compiler)
Clojure
2
star
44

gozer

Social media stream aggregation
JavaScript
2
star
45

cathoristic-logic

F#/Clojure implementation of Cathoristic Logic
F#
2
star
46

nopenotarabic

An Archive of Things That Are Not Arabic
HTML
2
star
47

-

بونج: اللعبة الكلاسيكية مبرمجة بلغة البرمجة "قلب" لمتصفح الويب
JavaScript
2
star
48

zajal-book

The Zajal Programming Book
2
star
49

coding-in-depth

Playable Fashion Coding in Depth Lesson Plan
HTML
2
star
50

tetroid

Arduino Tetris clone
Java
2
star
51

sfpc-fall14

Radical Computer Science at the School For Poetic Computation Fall 2014
1
star
52

playablefashion

C++
1
star
53

electron-p5

P5 in an Electron App
JavaScript
1
star
54

syntax-canvas

Language design experimentation tool
JavaScript
1
star
55

guillotine

Painless code execution framework
1
star
56

buzzkill

Flies are so annoying
JavaScript
1
star
57

terminalstate

maybe got carried away with this
HTML
1
star
58

npwd

Command-line password management for your various accounts.
JavaScript
1
star
59

resume

1
star
60

fsbook

an f# program that is also a book?
JavaScript
1
star
61

roll-a-ball

Adaptation of mlakewood's Roll_a_Ball
Clojure
1
star
62

hiramsey

Kaho's C64 Code
Visual Basic
1
star
63

magic-project

Top level build orchestration repository for MAGIC and all related technologies
1
star