Jak napisać gościa antlr4
Próbuję napisać dla gościa prostą gramatykę antlr4 - adaptuję z poniższego przykładu z książki:
* directory tour
* example: LabeledExpr.g4, EvalVisitor.java, Calc.java
Na podstawie kodu java napisałem następujący kod 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)
}
Pokazuję powyżej jednego z gości, inni odwiedzający zostaliby napisani podobnie. Otrzymuję błędy kompilacji, jak pokazano w komentarzach powyżej. Jak naprawić ten błąd?
Wydaje się również, że wystąpił błąd w wywołaniu gościa na najwyższym poziomie, ponieważ kiedy zakomentowałem linię „lewa + prawa”, dostałem błąd SIGSEGV.
W celach informacyjnych pokazuję poniżej oryginalny kod 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
}
Ponadto gramatyka wygląda tak:
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
UWAGA: Szukałem w pobliżu przykładowego kodu gościa, ale trafiłem na kilka negatywnych komentarzy pod adresem 54992660 , które są również publikowane w kwestiach antlr. Odpowiedź na to pytanie jest niekompletna i nie daje się skompilować. Czy zatem odwiedzający w ogóle pracują w celu Go antlr4? Czy jest do tego dostępny przykładowy kod?
Odpowiedzi
Wyszukałem trochę w Google i zhakowałem następującego gościa w Go:
Plik: ./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())
}
Plik: ./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];
Najpierw pobierz ANTLR 4.9 JAR, wygeneruj parser i pliki go odwiedzającego i przenieś je do antlr4demo
folderu:
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
Jeśli teraz uruchomisz następujący skrypt Go:
Plik: ./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)
}
zobaczysz wynik:
$ go run main.go
1000 25/5 (1 + 2) * -3.14159265 = -9.424777950000001
All results: map[0:1000 1:5 2:-9.424777950000001]
Zauważ, że nigdy nie programowałem niczego w Go: jestem pewien, że kod jest bałaganem, ale hej, „to działa”.