My FeedDiscussionsHeadless CMS
New
Sign in
Log inSign up
Learn more about Hashnode Headless CMSHashnode Headless CMS
Collaborate seamlessly with Hashnode Headless CMS for Enterprise.
Upgrade ✨Learn more

How Stack Editor works

Jon's photo
Jon
·Oct 20, 2016

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!