Skip to content

Feature: Aliases

John Ed Quinn edited this page Apr 15, 2023 · 1 revision

To provide Kanonic users with ease-of-use functions while visiting the AST, Kanonic allows for aliases on references (of rules or tokens) and homogenous collections of references.

For example, if we look at the Calculator example in the /examples directory, we use aliases quite frequently when specifying the syntax for binary arithmetic operators. Here's an example:

expr
	: expr @op=PLUS atomic --> expr_plus
	;

If you have used aliases in the past, this specific example might seem like overkill, but, for the purpose of portrayal, a user can leverage this feature to simplify their implemented KanonicVisitor.

For the Calculator example, we vend a CalculatorInterpreter, which takes in the parsed Kanonic AST and traverses the AST while calculating the result of the operation. With the specific rule variant displayed above (specifying the syntactic structure of the binary plus operator), our code might look like:

override fun visitExprPlus(node: CalculatorNode.ExprNode.ExprPlusNode, ctx: Unit): Int {
    val op = when (node.op().type) {  
        KanonicSpecification.Tokens.PLUS -> { a: Int, b: Int -> a + b }  
        else -> error("Didn't understand op type: ${node.op().type}")  
    }  
    val lhs = visitExpr(node.expr(), ctx)  
    val rhs = visitMathOp(node.mathOp(), ctx)  
    return op(lhs, rhs)  
}

If you look closely, we see that the CalculatorNode.ExprNode.ExprPlusNode has a function called op(). This generated helper function gives you the ability to reference the token PLUS that was specified in the grammar.

Now, as we've seen how we can actually use aliases -- let's take a look at a more practical example -- one where the alias refers to a homogenous collection of item references. See the below Kanonic example:

expr
	: @lhs=expr @op=(PLUS|MINUS) @rhs=atomic --> expr_binary
	;

Now, in the above example, we've specified three aliases:

  • lhs refers to the left-hand side of the binary operation
  • rhs refers to the right-hand side of the binary operation
  • op refers to one of the allowable tokens (PLUS or MINUS).

Our interpreter might look like:

override fun visitExprBinary(node: CalculatorNode.ExprNode.ExprBinaryNode, ctx: Unit): Int {
    val op = when (node.op().type) {  
        KanonicSpecification.Tokens.PLUS -> { a: Int, b: Int -> a + b }  
        KanonicSpecification.Tokens.MINUS -> { a: Int, b: Int -> a - b }  
        else -> error("Didn't understand op type: ${node.op().type}")  
    }  
    val lhs = visitExpr(node.lhs(), ctx)  
    val rhs = visitMathOp(node.rhs(), ctx)  
    return op(lhs, rhs)  
}

As you can see in the above code-snippet, we can now, with a single hand-written rule, specify a single visitor to produce the result for either the binary addition and binary subtraction operations.

If we hadn't used aliases, our Kanonic grammar would look like:

expr
	: expr PLUS atomic  --> expr_plus
	| expr MINUS atomic --> expr_minus
	;

Then, we'd need to override two visitor methods to provide both operations.

Clone this wiki locally