Creating a Scripting Language with ANTLR — Part 3

Written by thosakwe | Published 2016/05/13
Tech Story Tags: antlr | programming-languages | compilers

TLDRvia the TL;DR App

In Part 2, we wrote a grammar, generated a lexer and parser, and also wrote a simple function to generate an AST from input code. To put it all together, we need to walk that AST.

Our scripting language, supports two types of statements — variable assignments and function invocations. Variable assignments, as you can imagine, simply set the value of a variable to an expression. A function invocation calls the given function, with the given arguments. If the function’s name is “say,” then we call “alert” instead.

ANTLR doesn’t just generate lexers and parsers. It also generates listeners and visitors. For this tutorial, we will use a listener. Listeners walk an entire AST and fire an event of sorts upon entering a rule context of a given type. By extending the default listener generated by ANTLR, we will only have to write code to handle rules that play a part in the compilation process.

In this case, our listener will only need to handle assignments and invocations, as well as resolving the value of expressions.

function FooTranspiler {BaseListener.call(this);this.output = "";}

First, we will fill in the resolveExpr method. It will simply transform transform an expression context into a string containing the Javascript representation of that expression. For the most part, the expressions will transpile one-to-one.

You will notice that to access child tokens and rule contexts within the expression context, I invoke member functions of the ExprContext object. Save for named children, these should all be accessed via functions.

function resolveExpr(ctx) {if (ctx.ID() != null) {// full code in attached gist...}}

Next, we will implement assignment statements. This is very simple with ANTLR, as all we need to do is obtain the text of the target variable, and set it to the resolved expression value.

FooTranspiler.prototype.enterAssignStmt = function(ctx) {var target = ctx.ID().getText();var value = this.resolveExpr(ctx.expr());this.output += “var “ + target + “ = “ + value + “;”;};

Finally are invocation statements. With ANTLR, we can even iterate through every expression provided in the input code.

FooTranspiler.prototype.enterInvocationStmt = function(ctx) {var functionName = ctx.name.text;this.output += functionName + “(“;// full code in gist...}

Here is the completed code:

Now, we can bring it all together.

var antlr4 = require('antlr4');var buildAst = require('./build-ast');var FooTranspiler = require('./foo-transpiler');

function runScript(inputText) {var ast = buildAst(inputText);var transpiler = new FooTranspiler(); antlr4.tree.ParseTreeWalker.DEFAULT.walk(transpiler, ast);eval(transpiler.output);}

module.exports = runScript;

You can run this in your browser (after using something like browserify or webpack) and have your code compiled to Javascript and run on-the-fly.

Congratulations! You’ve just created a scripting language! However, this is just the beginning. High-level projects, like Twitter’s search API, use ANTLR. The sky really is the limit.

If you liked this tutorial, feel free to hit the heart below to recommend it to friends, or share it on social media.

Follow me on Twitter: @wapaa_

Hacker Noon is how hackers start their afternoons. We’re a part of the @AMIfamily. We are now accepting submissions and happy to discuss advertising &sponsorship opportunities.

To learn more, read our about page, like/message us on Facebook, or simply, tweet/DM @HackerNoon.

If you enjoyed this story, we recommend reading our latest tech stories and trending tech stories. Until next time, don’t take the realities of the world for granted!


Published by HackerNoon on 2016/05/13