Shelly — a programming language for drawing — is one of our side-projects that we work on in-between other engagements.
It features the challenge mode, where through a series of step-by-step tutorials you can learn both the Shelly language, as well as the basics of programming. There's also the creative mode, where you can draw whatever you like, and share it with others as an image, code (to allow modifications), or as a custom challenge — can others reproduce your painting?
Shelly delivers the well-known concept of turtle graphics in a modern package, with a friendly interface, instant drawing, rich sharing options, and gamification. You write the program, and Shelly the turtle immediately reflects the new code on the canvas — live programming!
The Shelly language is a mix between Logo and more modern programming languages. Due to its educational character, it's kept simple: we've got expressions, named functions, loops, conditionals, recursion, and that's about it! Still, even using these basic constructs, you can create some really interesting images.
You might be wondering: what's under the hood? What kind of technologies can one use to build such a live-coding environment?
Shelly itself is built with TypeScript. The types bring some sanity to the otherwise untyped JavaScript/browser domain, integrating nicely with the whole rest of the ecosystem.
For the skeleton of the application, we've made a popular choice, that is React. Nothing non-standard, though as the application has grown, we've seen that our reliance on Context API to manage state has reached its limits. That's why we're considering porting that aspect to Redux.
Second, we've got the editor. Here, we are using the Monaco Editor, which is the editor that's also used in VisualStudio Code. To configure the editor for a custom language, you have to provide the keywords, symbols, operators and configure the tokenizer. We also get auto-complete out-of-the-box — you just need to provide the completion function, which can be context-sensitive or not.
Monaco also gives us the possibility to manage errors (underlying them and showing glyphs) and provides contextual editor extensions. In Shelly, this is used to show a palette of available colors and pen patterns, whenever the user writes pen
or fill
. Try it!
Monaco has quite good documentation, though sometimes you'll end up digging through GitHub looking for usage examples to see how to best configure the parser, provide styling or manage the web workers.
Let's move to the canvas. Here, the main job is done by Konva, which provides us with a simple and performant layer on top of the raw HTML canvas
element. Shelly uses a rather basic subset of Konva, as Shelly's drawings are composed mainly of lines, arches, basic figures, and SVG patterns (hearts, stars, cars, etc.). One aspect that was especially challenging, however, was properly handling the zoom & pan, using both on-screen controls and the mouse wheel.
What about the drawing/programming language itself? Even though it's quite simple, we still need a tokenizer and an interpreter. We've chosen ANTLR to define the grammar, with antlr4ts as the target. That is, when running the ANTLR tool, the input is a file containing the grammar definition, and as the output, we get TypeScript files that handle the parsing of arbitrary text into an AST (Abstract Syntax Tree).
However, we are not using the ANTLR-defined AST directly when doing the interpretation (that is, actually running the program), but we're translating it to yet another representation, which is more flexible in the result type — and we need that flexibility to implement arbitrary-depth recursion. Here, we've used trampolining, thanks to which you can loop and loop without blowing the browser's JS stack.
The downside of using ANTLR with the TypeScript backend is that the resulting package is quite large — which means a large download, and a longer wait for the initial page load. That's why we might consider writing a parser by hand in the future, which won't have any third-party dependencies.
Shelly's frontend is built using webpack, tested with jest and playwright, and deployed using Netlify. The whole process of connecting a GitHub repository, configuring CI, pull request previews, custom domains, and, finally, the production build is really painless. And makes the development so much easier!
Finally, on the backend, we've got a simple Scala-based application, deployed on Heroku. The database is PostgreSQL, but the application is not a straightforward CRUD as you might expect, but instead uses SQL-based, transactional event sourcing. Probably overkill, but — if we ever want to create a new view basing on the events (which include e.g. program creation in creative mode, or solving a challenge) — for sure we'll have the data available!
And that's it. Give Shelly a try, solve the challenges or create a drawing in creative mode. Then, let us know what you think, what you'd improve, change or add!
Top comments (0)