How Stack Editor works
Program with AST, not text, not files! (GitHub repo)
Stack Editor is a tree editor for Clojure subset. It's not a text editor but editing by manipulating expressions, which makes it convenient to change the structure of the code.
By "subset" I mean definitions must be started with defn
, def
or defonce
. Other top level expressions are partly supported. However, macros are not, because we need to use macros to generate code in text.
Here's a short overview of how Stack Editor is compiling code, which is done by stack-server, a Boot task.
.ir
file with EDN
In a project powered by Stack Editor, there will be a .ir
file containing all the data of the source code. It's a HashMap with three keywords. Read the demos below:
https://github.com/Cirru/stack-editor/blob/master/stack-sepal.ir
{:namespaces {"demo.core" ["ns" "demo.core"
[":require" ["[]" "demo.lib" ":refer" ["[]" "y"]]]]
"demo.lib" ["ns" "demo.core"]}
:definitions {"demo.core/x" ["def" "x" ["=" "y" "1"]]
"demo.lib/y" ["def" "y" "2"]}
:procedures {"demo.core" [["println" "x"]]}
"demo.lib" []}}
By reading this file you may already guessed what are the namespaces and the content of this project.
Cirru style EDN
The first phase of compilation is to transform the.ir
content into Vectors, which is used by Cirru Project. It concats code from :namespaces
, :definitions
and procedures
of the same namespace together. Then it's more or less a file we would write.
Definitions have an order which is not contained in the HashMap, so I have to analyze the vector to find out the right order. The algorithm is quite tricky. You can find it in stack-server.
So now we have 2 piece of data corresponding to demo/core.clj
and demo/lib.clj
:
[
["ns" "demo.core"
[":require" ["[]" "demo.lib" ":refer" ["[]" "y"]]]]
["def" "x" ["=" "y" "1"]]
["println" "x"]
]
[
["ns" "demo.core"]
["def" "y" "2"]
]
Generated code
To generate Clojure code, we use a library from Cirru Project and it's already introduced in my old posts somewhere. You may read the code here: https://github.com/Cirru/sepal.clj/ The main idea behind it is macros are capable of dispatching human readable code, so I need a transformer to build quoted expression it needs by transforming the vectors we already have. Here are the generated files:
(ns demo.core
(:require [demo.lib :refer [y]]))
(def x (+ y 1))
(println 1)
(ns demo.lib)
(def y)
Editor UI
Besides compilation, I spent quite some time to make editing trees faster. Manipulating vectors is a lot easier that manipulating Clojure code in text. Here are the features it supports by now:
- Goto definition
- Search and jump
- Add new definition based on current selection
There can be more extensions to transform the code in the future, for example, if I got an expression like ["hsl" "200" "90" "60"]
, then I can build an extension to read it as a color picker to choose colors. There can be a lot of possibilities.
Conclusion
By using Stack Editor, now I don't have to write Clojure code in text often. Hope it works for you too!