In this article I build an interpreter for the DBN language from the parsed AST generated by the grammar we defined in the previous article in PEG.js. If you haven’t read the first part, I strongly recommend to do so, otherwise this will make little sense to you.
It should be quite easy to follow by just looking at the code and its comments. Still, I will be explaining how the interpreter works as I put the code examples in.
Choices and assumptions
Due to little time and given the lack of any reference of the language, I made some assumptions and I left out some advanced features of the language. I hope I can include them soon into the project.
Scope
Since I don’t know how scope works in DBN, I assumed Lexical scoping, given that it is the most common among modern programming languages (JavaScript has lexical scoping as well, for example).
The interpreter
The interpreter takes care of evaluating the arguments for each statement and then execute the statement with its parsed arguments and the given scope, if any:
1 | var Interpreter = function(canvas) { |
The global variables will reside in the vars property, and the most important methods here are obviously evalType and evalExpression.
Evaluating types
evalType receives the type object and its scope and calls the proper static method in Interpreter.typeTable with the same parameters. Interpreter.typeTable holds the functions that interpret the different types in DBN:
1 | // In DBN, everything is an integer or a set of integers, and the developer has |
The types are the following: Command A user-defined function in DBN jargon. The user can define custom commands with parameters that execute a block of code when called. String Used mainly for variable names. Given a string from a generated AST, I check whether the variable is defined in its local scope (in case there is one) or in the global scope. In case it is in neither of them, I assume that it is the name of a parameter and doesn’t have an associated value. Integer The basic type. Its value is returned right away. Point A point refers to a coordinate in the canvas. It contains x and y values that can be integers, variables or arithmetic expressions. Returns an object with the evaluated x and y values.
Evaluating expressions
Expressions are DBN functions. In this interpreter, everything that computes is considered a function, including the arithmetic operators. evalExpression takes an AST (or a portion of one, as it is done for code blocks) and loops through every statement, resolving the types of its arguments using evalType and calling the proper static method in Interpreter.expTable with them. Interpreter.expTable is a dictionary that stores the supported DBN expressions’ equivalents in JavaScript (same as with types). Inside the Interpreter.expTable we can find the following kinds of expressions:
Arithmetic expressions
1 | "*": function(a, b) { return parseInt(a * b) }, |
Arithmetic expressions are always used inside other expressions parameters, never by themselves. The functions are called always with 2 arguments that have been already resolved by evalType, so they are integers when they get to the expression.
Drawing expressions
1 | /* |
The main difference between the DBN ‘canvas’ and HTML5 canvas is that the Y coordinate is inverted; in DBN the Y coordinate starts at the bottom whereas in HTML5 canvas it starts at the top. The other important difference is that DBN’s pixels color can only be a gray value from 0 (black) to 100 (white), so we have to use the very simple gray2rgb method to translate them to canvas rgb syntax:
1 | /* |
With these two changes in place it is quite straightforward to translate drawing functions using the basic HTML5 canvas functions and passing the arguments properly.
Block expressions
A block expression is a statement that contains a block of nested expressions. DBN’s repeat and command statements are the ones implemented here that belong to this kind. The peculiarity of block expressions is that they have a local scope:
1 | repeat: function(p) { |
The local scope here is implemented in a naive (and a bit inefficient) way. Basically and if it exists, the parent scope of the block expression is cloned and then the local variables of the block are copied on it. After that, the block of nested expressions is executed with the cloned context. repeat is a ‘for’ loop that gets as arguments the ‘counter’ variable, the lower bound of the range and the higher bound of the range. From that it creates a loop that assigns the counter variable to the local scope and executes the block with this scope. command builds a function from the given name and arguments and the block expression that forms the body of the function. In that case I create a function and store it in Interpreter.expTable so it can be called anywhere in the code. I do the same trick with the scope as in repeat, with the difference that in this case I associate the name of the command arguments with the value passed to the call of the function.
Demo
That’s it, you can try a quick demo here. In the demo page you can find some examples and you can play around to better understand how DBN works and what its powers and limitations are.
Conclusion
As you can see this is a very simple implementation, but it is enough to run most of the DBN programs around. Some things like arrays, timers and mouse position and events were left out and will be implemented at some point when I have time, but the actual ‘meat’ of the interpreter won’t change much anyway. You can find the complete code for this interpreter here and the complete repository here. If I missed something or yo have some suggestions of how to better implement DBN drop me a line, or even better: fork it!