Cómo escribir un visitante antlr4

Nov 27 2020

Estoy tratando de escribir un visitante para una gramática antlr4 simple; estoy adaptando el siguiente ejemplo del libro:

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

Basado en el código de Java, he escrito el siguiente código de 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)
}

Estoy mostrando uno de los visitantes arriba, los otros visitantes estarían escritos de manera similar. Recibo algunos errores de compilación como se muestra en los comentarios anteriores. ¿Cómo arreglar este error?

También parece haber un error en la llamada de nivel superior del visitante, porque cuando comenté la línea "izquierda + derecha", obtuve una falla SIGSEGV.

Para su referencia, estoy mostrando a continuación el código original de Java:

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
}

Además, la gramática es así:

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: Busqué un código de visitante de muestra, pero encontré algunos comentarios negativos en 54992660 , y eso también está publicado sobre problemas de antlr. Esa pregunta tiene una respuesta que está incompleta y no se compila. Entonces, ¿los visitantes trabajan en el objetivo Go de antlr4? ¿Y hay un código de muestra disponible para eso?

Respuestas

1 BartKiers Nov 28 2020 at 13:16

Busqué un poco en Google y pirateé al siguiente visitante de Go juntos:

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

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

Primero descargue el ANTLR 4.9 JAR, genere el analizador y los archivos Go del visitante y muévalos a la antlr4democarpeta:

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

Si ahora ejecuta el siguiente script de Go:

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

verá la salida:

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

Tenga en cuenta que nunca he programado nada en Go: estoy seguro de que el código es un desastre, pero bueno, "funciona".