Search code examples
goantlr4

How to write a antlr4 visitor


I am trying to write a visitor for a simple antlr4 grammar - I am adapting from the following example from the book:

* directory tour
* example: LabeledExpr.g4, EvalVisitor.java, Calc.java

Based on the java code, I have written the following go code:

package main
import (
    "os"
    "./parser"
    "github.com/antlr/antlr4/runtime/Go/antlr"
)

type evalVisitor struct {
    *parser.BaseLabeledExprVisitor
}

func (v *evalVisitor) VisitAddSub(c *parser.AddSubContext) int {
    left := v.Visit(c.Expr(0))
    right := v.Visit(c.Expr(1))
    if(c.GetOp().GetTokenType() == parser.LabeledExprParserADD) {
        return left + right //error: invalid operation: left + right (operator + not defined on interface)
    } else {
        return  left - right
    }
}

func main() {
    input, _ := antlr.NewFileStream(os.Args[1])
    lexer := parser.NewLabeledExprLexer(input)
    stream := antlr.NewCommonTokenStream(lexer, antlr.TokenDefaultChannel)
    p := parser.NewLabeledExprParser(stream)
    tree := p.Prog()
    var visitor evalVisitor
    visitor.Visit(tree)
}

I am showing one of the visitors above, the other visitors would be written similarly. I am getting some compile errors as shown in comments above. How to fix this error?

There also seems to be an error in the top level calling of the visitor, because when I commented out the "left+right" line, I got a SIGSEGV fault.

For your reference, I am showing below the original java code:

public Integer visitAddSub(LabeledExprParser.AddSubContext ctx) {
    int left = visit(ctx.expr(0));  // get value of left subexpression
    int right = visit(ctx.expr(1)); // get value of right subexpression
    if ( ctx.op.getType() == LabeledExprParser.ADD ) return left + right;
    return left - right; // must be SUB
}

Also, the grammar is like this:

grammar LabeledExpr;
prog:   stat+ ;

stat:   expr NEWLINE                # printExpr
    |   ID '=' expr NEWLINE         # assign
    |   NEWLINE                     # blank
    ;

expr:   expr op=('*'|'/') expr      # MulDiv
    |   expr op=('+'|'-') expr      # AddSub
    |   INT                         # int
    |   ID                          # id
    |   '(' expr ')'                # parens
    ;

MUL :   '*' ; // assigns token name to '*' used above in grammar
DIV :   '/' ;
ADD :   '+' ;
SUB :   '-' ;
ID  :   [a-zA-Z]+ ;      // match identifiers
INT :   [0-9]+ ;         // match integers
NEWLINE:'\r'? '\n' ;     // return newlines to parser (is end-statement signal)
WS  :   [ \t]+ -> skip ; // toss out whitespace

NOTE: I searched around for a sample visitor code, but I hit some negative comments at 54992660, and that is also posted on antlr issues. That question has an answer that is incomplete and does not compile. So, do visitors at all work in the Go target of antlr4? And is there a sample code available for that?


Solution

  • I Googled a bit, and hacked the following Go visitor together:

    File: ./antlr4demo/eval_visitor.go

    package antlr4demo
    
    import (
        "strconv"
    
        "github.com/antlr/antlr4/runtime/Go/antlr"
    )
    
    type EvalVisitor struct {
        BaseExpressionVisitor
        Results map[int]float64
    }
    
    func (v *EvalVisitor) Visit(tree antlr.ParseTree) float64 {
        switch val := tree.(type) {
        case *ParseContext:
            return v.VisitParse(val)
        case *MultDivExprContext:
            return v.VisitMultDivExpr(val)
        case *NumberExprContext:
            return v.VisitNumberExpr(val)
        case *PlusSubExprContext:
            return v.VisitPlusSubExpr(val)
        case *NestedExprContext:
            return v.VisitNestedExpr(val)
        case *UnaryExprContext:
            return v.VisitUnaryExpr(val)
        default:
            panic("Unknown context")
        }
    }
    
    func (v *EvalVisitor) VisitParse(ctx *ParseContext) float64 {
        for index, expr := range ctx.expr_list {
            v.Results[index] = v.Visit(expr)
        }
        return v.Results[len(v.Results)-1]
    }
    
    func (v *EvalVisitor) VisitMultDivExpr(ctx *MultDivExprContext) float64 {
        lhs := v.Visit(ctx.lhs)
        rhs := v.Visit(ctx.rhs)
    
        if ctx.op.GetTokenType() == ExpressionLexerMULT {
            return lhs * rhs
        } else {
            return lhs / rhs
        }
    }
    
    func (v *EvalVisitor) VisitPlusSubExpr(ctx *PlusSubExprContext) float64 {
        lhs := v.Visit(ctx.lhs)
        rhs := v.Visit(ctx.rhs)
    
        if ctx.op.GetTokenType() == ExpressionLexerPLUS {
            return lhs + rhs
        } else {
            return lhs - rhs
        }
    }
    
    func (v *EvalVisitor) VisitNumberExpr(ctx *NumberExprContext) float64 {
        val, _ := strconv.ParseFloat(ctx.NUMBER().GetText(), 10)
        return val
    }
    
    func (v *EvalVisitor) VisitNestedExpr(ctx *NestedExprContext) float64 {
        return v.Visit(ctx.Expr())
    }
    
    func (v *EvalVisitor) VisitUnaryExpr(ctx *UnaryExprContext) float64 {
        return -v.Visit(ctx.Expr())
    }
    

    File: ./Expression.g4

    grammar Expression;
    
    parse
     : expr_list+=expr+ EOF
     ;
    
    expr
     : '(' expr ')'                        #NestedExpr
     | SUB expr                            #UnaryExpr
     | lhs=expr op=( MULT | DIV ) rhs=expr #MultDivExpr
     | lhs=expr op=( PLUS | SUB ) rhs=expr #PlusSubExpr
     | NUMBER                              #NumberExpr
     ;
    
    MULT : '*';
    DIV  : '/';
    PLUS : '+';
    SUB  : '-';
    
    NUMBER
     : ( D* '.' )? D+
     ;
    
    SPACES
     : [ \t\r\n] -> skip
     ;
    
    fragment D : [0-9];
    

    First download the ANTLR 4.9 JAR, generate the parser and visitor Go files and move them to the antlr4demo folder:

    wget https://www.antlr.org/download/antlr-4.9-complete.jar
    java -cp antlr-4.9-complete.jar org.antlr.v4.Tool -Dlanguage=Go -o antlr4demo -package antlr4demo -visitor -no-listener Expression.g4
    

    If you now run the following Go script:

    File: ./main.go

    package main
    
    import (
        "fmt"
    
        "./antlr4demo"
        "github.com/antlr/antlr4/runtime/Go/antlr"
    )
    
    func main() {
        expression := "1000 25/5 (1 + 2) * -3.14159265"
        input := antlr.NewInputStream(expression)
        lexer := antlr4demo.NewExpressionLexer(input)
        stream := antlr.NewCommonTokenStream(lexer, 0)
        parser := antlr4demo.NewExpressionParser(stream)
        parser.BuildParseTrees = true
        tree := parser.Parse()
    
        visitor := antlr4demo.EvalVisitor{
            Results: make(map[int]float64),
        }
    
        var result = visitor.Visit(tree)
    
        fmt.Println(expression, "=", result)
        fmt.Println("All results: ", visitor.Results)
    }
    

    you'll see the output:

    $ go run main.go
    1000 25/5 (1 + 2) * -3.14159265 = -9.424777950000001
    All results:  map[0:1000 1:5 2:-9.424777950000001]
    

    Note that I have never programed anything in Go: I'm sure the code is a mess, but hey, "it works".