At Botsquad, we are building a conversational app platform. It is aimed at developers who would like to get quickly started with writing conversational experiences and chatbots.

Our distinguishing feature is that our bots are built using a special language, called Bubblescript. (Such a specialized language is usually called a DSL, a Domain Specific language).

💡 This article is based on my lightning talk at ElixirconfEU, last monday.

The challenge

It all started when I, working as a freelancer, was approached to create 3 chatbots for the https://www.amsterdammuseum.nl/. The chatbots would guide you through the exhibition in different ways, each one telling its own story from a certain historic perspective.

As those chatbots were quite story-driven, and the story was being written by the museum curators, I, lazy programmer as I am, did not want to incorporate the contents directly into a programming language (such as Microsoft Bot Framework), because that would mean I would need to maintain it as well. Also, I did not like creating an entire CMS for the curators to use, to fill the bots with content, as that was quite some work, especially since the conversations were not all linear; they could branch depending on what the visitor was experiencing and how he reacted to the bot.

Briefly said, the challenge I faced, was to allow the domain experts (in this case the museum curators), to write conversational experiences.

After briefly considering trying to teach them JSON or YAML, I decided to create a basic DSL using Elixir. We named it Bubblescript.

Bubblescript

This is how a basic script looks:

Bubblescript example code

The distinctive features of Bubblescript are:

→ It is dedicated to modelling conversations — Special statements say, ask and show are all you need to know to get started and feel very natural to create a dialog with a user.

→ It has a beginner-friendly syntax — We are aiming at beginning developers and tech-savvy users who are not afraid to learn a bit of coding.

The basic building block of Bubblescript is the dialog. Within a dialog, all interactions run sequentually, much like a function in a normal programming language. But the sequence of actions can be interrupted, for instance when a user starts talking about another topic. In that case, Bubblescript “pushes” the stack and is able to switch to a different topic (in a different dialog). When that dialog is finished, the stack is “popped” and the previous dialog continues where it left off.


⚠️ From this point on, the article assumes some knowledge about Elixir. Feel free to skip to the demo movie at the end :-)


Not a regular DSL

It’s important to realize that Bubblescript, although it uses the Elixir syntax, does not compile to Elixir (BEAM) bytecode. In that sense, it is not a “normal” DSL as you’re used to when writing Elixir code.

Not a regular DSL

Instead of compiling to bytecode, Bubblescript is interpreted. The reason why an interpreter is used are the following:

→ Globals — The variable scope is global. Once declared, a variable exists in every dialog. This makes it very easy to get started.

→ Deep assignments — It is possible to assign to a variable deeply nested inside a map or a list. This is made possible by extending the Access pattern to allow setting as well as getting, both in maps and lists. (The module responsible for this, Bubble.AutoMap, is probably worth a blogpost on its own).

→ Interruptible control flow — as already mentioned, the control flow and can be interrupted by the user sending a message, and even by external events (webhooks, other bots sending an event, etc.)

The parser

The basic “trick” that Bubblescript uses, is to use the Code.string_to_quoted/1 function to transform the script into a structure that the interpreter can work with.

Bubblescript parser

The resulting data structure, called the AST (Abstract Syntax Tree), is then validated to make sure that the code does not contain invalid constructions (invalid statements, or e.g. a say command outside a dialog, et cetera).

Note: Several people have pointed out to me that using Code.string_to_quoted/1 causes atoms to be created, and effectively, by allowing users to write scripts, we allow atoms to be created from user input. As there is no “atom leak free” parser available for Elixir, yet, we mitigated this problem by delegating the script parsing to a dedicated Elixir node.

The interpreter

The parsed and validated code is then used to create the interpreter, which is basically just a struct.

Bubblescript interpreter

The %Bubble.Interpreter.State{} struct contains the following:

  • The parsed code — Which script(s) am I currently executing? Which dialogs do I have available?
  • The current call stack — Which dialog am I in right now?
  • The current statement, as a queue — which statements am I going to execute next?
  • The variable scope - which variables are available?

These components together form the interpreter’s state, and Bubble.Interpreter exposes several functions which manipulate this state. As such, the interpreter is a purely functional data structure:

Bubblescript interpreter functions

This shows example code of how the script is executed in steps by the interpreter.

The Interpreter.* functions return an action, which is supposed to be sent to FB messenger or pushed over a socket. The action also incorporates timing information, which instructs you how long to wait before calling next(). Lastly, it returns the updated struct.

After waiting the instructed amount of time, next() is called again, this time with the new state.

The BotServer

The code above is tedious to write, so, luckily, a GenServer is provided, dubbed the BotServer, which takes care of calling the right functions, at the right time.

BotServer diagram

It is a GenServer which executes the code on the previous slide, manipulating the interpreter’s state while incorporating the timing and the handling incoming messages and events.

Outgoing messages, the “action” side effects that are returned, are handled by a callback module which is supposed to be implemented by the user. These callback functions are called whenever something needs to be done (e.g. a message sent).

Demo time

So… let’s finish of with how this all comes together in the Botsquad studio.

In the video, you see me creating a new, empty bot, and adding a (very basic) introductionary conversation to it. On the right side, the simulator is used to run the bot’s code. While it is running, you see on which line the code is currently executing, providing direct feedback on how the conversation flows. At 1:01 minute, a new dialog is created, which will be run when one of the triggers fire (in this case, the words hello, welcome or hi). A random reply is sent when the user sends one of those words.

That’s it for now! I hoped you enjoyed this explanation of Bubblescript and the Botsquad platform. Feel free to sign up for a Botsquad account, it’s free to get started.

Arjan Scherpenisse

Arjan Scherpenisse

Co-founder Botsquad

email me