{:check ["true"]}

Index

An example of a simple calculator

Visitor Pattern

Let's import the dependencies.

In [ ]:
@file:DependsOn("/data/shared/antlr-4.9.1-complete.jar")
@file:DependsOn(".")

We import the generated classes including the new BaseVisitor which will allow us to specify the data type and functions to annotate nodes of the parse tree.

In [2]:
import org.antlr.v4.runtime.*;
import mygrammar.CalcParser
import mygrammar.CalcLexer
import mygrammar.CalcBaseVisitor

We first implement a function that converse an input string to a parse tree.

Note, a parse tree is represented as an ExprContext, which is actually just the root node data structure used by ANTLR.

In [ ]:
fun parsetreeOf(source: String): CalcParser.ExprContext {
    val input = ANTLRInputStream(source)
    val lexer = CalcLexer(input)
    val tokens = CommonTokenStream(lexer)
    val parser = CalcParser(tokens)

    return parser.expr()
}

For this simple example, we will annotate each node with the NodeValue class, which has only one attribute.

In [ ]:
data class NodeValue(val value: Int)

Now, we can construct a visitor implementation. It overrides several methods of the generated BaseVisitor class. Note we also specify the generic data type NodeValue when extending the BaseVisitor class.

Kotlin allows us to create an anonymous class, and instantiate an object directly.

In [19]:
val visitor = object: CalcBaseVisitor<NodeValue>() {
    override fun visitAddition(ctx: CalcParser.AdditionContext): NodeValue {
        val xValue = this.visit(ctx.x)
        val yValue = this.visit(ctx.y)
        return NodeValue(xValue.value + yValue.value)
    }
    override fun visitMultiplication(ctx: CalcParser.MultiplicationContext): NodeValue =
        NodeValue(this.visit(ctx.x).value * this.visit(ctx.y).value)
        
    override fun visitBracket(ctx: CalcParser.BracketContext): NodeValue = this.visit(ctx.expr())
    
    override fun visitValue(ctx: CalcParser.ValueContext): NodeValue {
        val lexeme = ctx.Number().getText()
        return NodeValue(lexeme.toInt())
    }
}

Putting everything together, we have a complete pipeline from source code, to token streams, to parse tree, to SDD.

In [21]:
visitor.visit(parsetreeOf("(3 + 4) * 7"))
Out[21]:
NodeValue(value=49)