Como escrever um visitante antlr4
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
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 antlr4demo
pasta:
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".