Introducing Visual and Interactive-Syntax realized (VISr) for ClojureScript (and JavaScript)
Visual and interactive-syntax is a type of language-oriented programming that allows developers to use, view, and edit portions of a textual program with graphics. Using interactive-syntax provides the benefits of a graphical programming language, while keeping all of the benefits of a purely textual language. For example, the following is an example of a small network embedded in a program:
Interactive-syntax is backed by human readable code; the visual components exists purely when writing and editing code. This backing means all of the tools involved in software development work with interactive-syntax extensions. For example:
- version control, such as git, works with interactive-syntax;
- programs using interactive-syntax can be written and edited with your favorite text editor or IDE;
- cut/copy/paste works with interactive-syntax using your operating system’s native clipboard;
- code analysis tools, like diff and refactor, still work with interactive-syntax; and
- you can use interactive-syntax in any language or environment that supports language-oriented programming.
To learn more about interactive-syntax, watch this video or read the accompanying paper.
VISr (Visual and Interactive-Syntax realized) for ClojureScript is a practical implementation of interactive-syntax in web browsers. The VISr environment is a full-featured IDE that supports interactive-syntax components called VISrs. Additionally, the VISr environment comes with a package manager that supports NPM packages.
This article is a brief introduction to both the VISr environment and the components that make up a VISrs. It discusses how to insert a VISr into code, how to manipulate a VISr, and how to create a new types of VISr. Future articles will discuss more advanced uses such as integrating NPM packages and using VISrs in other languages.
Getting started with VISr
Start by going to visr.pl, which is a web-based IDE that directly supports VISrs. Once in the IDE, press Insert VISr
to place a VISr at the current cursor position. This VISr contains two buttons:
- clicking the first displays the VISr’s visual representation, and
- clicking the second shows its textual representation.
Opening the code shows that the new VISr is an instance of visr.core/empty-visr
, a default VISr provided by the IDE. This VISr expects a map with the key :message
to display in the visual view. Changing the value associated with :message
changes what is displayed, in this case “Endless Possibility”:
Remember that, although this VISr is displayed graphically, it still exists as human-readable text. One way to see this text is by copying and pasting the VISr. A copy of the same VISr will appear when it is placed back into the IDE. However, pasting it into other text editors that do not natively support VISrs yields the following human readable, and editable, text:
1 2 |
^{:editor visr.core/empty-visr}(visr.core/empty-visr+elaborate {:message "Endless Possibility"}) |
This operation works in reverse too. Writing out similar text and pasting it into visr.pl yields its visual representation.
Making a new VISr
The defvisr
form creates a VISr type. This form expects two methods:
- a
render
method that provides visualization and interaction when code is edited, and - an
elaborate
/elaborate-fn
method that gives the VISr compile-time and run-time semantics.
The following is the signature for a simple VISr type:
1 2 3 4 5 |
(ns example.core) (defvisr Counter (elaborate-fn [this] "TODO-elaborate") (render [this] "TODO-render")) |
This example uses elaborate-fn
, a simplified version of elaborate
that gives the VISr
the same semantics as a function application. It also allows the defvisr
form to work in the same file as the VISr itself.
The Render Method for Edit-Time Semantics
The render
method is given the VISr state as an atom; updating this atom also updates the code to reflect the new state. The return value for render
must be a Reagent form that is the visual view for the VISr. A render method for a counter VISr might look as follows:
1 2 |
(render [this] [:button {:on-click #(swap! this inc)} @this]) |
And in action:
This VISr doesn’t match the theme of the page; it also requires the state to be a single number. Using React Bootstrap and Reagent cursors fixes both of these issues:
1 2 3 4 5 6 7 8 9 10 |
(ns example.core (:require [reagent.core :refer [cursor]] [react-bootstrap :refer [Button]])) (defvisr Counter (elaborate-fn [this] "TODO-elaborate") (render [this] (let [count (cursor this [:count])] (when-not @count (reset! count 0)) [:> Button {:on-click #(swap! count inc)} @count]))) |
Elaboration and Run-Time Semantics
The elaborate method takes the VISr state, and is expected to provide a compile-time or run-time semantics. In the simplified case of elaborate-fn
, the VISr semantics takes the form of a function application:
1 |
(elaborate-fn [{:keys [count]}] count) |
This elaborate
method expects a dictionary with the key :count
and returns the value associated with that key. It makes use of ClojureScript’s Destructuring for brevity. The following code is equivalent:
1 |
(elaborate-fn [this] (get this :count)) |
Putting it all together
The final result is:
1 2 3 4 5 6 7 8 9 10 |
(ns test.core (:require [reagent.core :refer [cursor]] [react-bootstrap :refer [Button]])) (defvisr Counter (elaborate-fn [{:keys [count]}] count) (render [this] (let [count (cursor this [:count])] (when-not @count (reset! count 0)) [:> Button {:on-click #(swap! count inc)} @count]))) |
Here is the VISr in action:
That’s all there is to it. From here, you can go to visr.pl to make your own programs using VISr. You can also take this survey, which contains more advanced example uses for VISr. If you find any bugs or want to contribute, you can also head to the visr project page.
Thanks for reading, happy coding!