Como escrever um visitante antlr4

Nov 27 2020

Estou tentando escrever um visitante para uma gramática antlr4 simples - estou me adaptando a partir do seguinte exemplo do livro:

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

Com base no código java, escrevi o seguinte código go:

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)
}

Estou mostrando um dos visitantes acima; os outros visitantes seriam escritos da mesma forma. Estou recebendo alguns erros de compilação, conforme mostrado nos comentários acima. Como consertar esse erro?

Também parece haver um erro na chamada de nível superior do visitante, porque quando comentei a linha "esquerda + direita", obtive uma falha de SIGSEGV.

Para sua referência, estou mostrando abaixo o código Java original:

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
}

Além disso, a gramática é assim:

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

NOTA: Eu procurei por um código de visitante de amostra, mas encontrei alguns comentários negativos em 54992660 , e isso também foi postado sobre problemas da Antlr. Essa pergunta tem uma resposta que está incompleta e não compila. Então, todos os visitantes trabalham no destino Go do antlr4? E há um código de amostra disponível para isso?

Respostas

1 BartKiers Nov 28 2020 at 13:16

Pesquisei um pouco no Google e hackeei o seguinte visitante Go juntos:

Arquivo: ./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())
}

Arquivo: ./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];

Primeiro faça download do ANTLR 4.9 JAR, gere os arquivos Go do analisador e do visitante e mova-os para a antlr4demopasta:

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

Se você agora executar o seguinte script Go:

Arquivo: ./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)
}

você verá a saída:

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

Observe que nunca programei nada em Go: tenho certeza que o código está uma bagunça, mas hey, "funciona".