默认情况下,ANTLR 4生成侦听器。但是,如果你提供
org.antlr.v4.Tool命令行参数
-visitor,则ANTLR会为你生成访问者类。它们的工作方式与侦听器非常相似,但是可以让你更好地控制要行走/访问的树(子树)。如果要排除某些(子)树(如你的情况那样,则else / if块),这特别有用。尽管可以使用侦听器来完成此操作,但是与访问者一起执行此操作会更加干净。使用侦听器,你需要引入全局变量,以跟踪是否需要评估(子)树,而不需要。
碰巧的是,我正在研究一个小的ANTLR 4教程。还没有完成,但是我将发布一个小的工作示例,演示这些访问者类和if语句构造的用法。
1.语法
这是一个支持基本表达式
if-,
while-和- 语句的简单语法
log:
Mu.g4
grammar Mu;parse : block EOF ;block : stat* ;stat : assignment | if_stat | while_stat | log | OTHER {System.err.println("unknown char: " + $OTHER.text);} ;assignment : ID ASSIGN expr SCOL ;if_stat : IF condition_block (ELSE IF condition_block)* (ELSE stat_block)? ;condition_block : expr stat_block ;stat_block : OBRACE block CBRACE | stat ;while_stat : WHILE expr stat_block ;log : LOG expr SCOL ;expr : expr POW<assoc=right> expr#powExpr | MINUS expr #unaryMinusExpr | NOT expr #notExpr | expr op=(MULT | DIV | MOD) expr #multiplicationExpr | expr op=(PLUS | MINUS) expr #additiveExpr | expr op=(LTEQ | GTEQ | LT | GT) expr #relationalExpr | expr op=(EQ | NEQ) expr #equalityExpr | expr AND expr #andExpr | expr OR expr #orExpr | atom#atomExpr ;atom : OPAR expr CPAR #parExpr | (INT | FLOAT) #numberAtom | (TRUE | FALSE) #booleanAtom | ID #idAtom | STRING #stringAtom | NIL #nilAtom ;OR : '||';AND : '&&';EQ : '==';NEQ : '!=';GT : '>';LT : '<';GTEQ : '>=';LTEQ : '<=';PLUS : '+';MINUS : '-';MULT : '*';DIV : '/';MOD : '%';POW : '^';NOT : '!';SCOL : ';';ASSIGN : '=';OPAR : '(';CPAR : ')';OBRACE : '{';CBRACE : '}';TRUE : 'true';FALSE : 'false';NIL : 'nil';IF : 'if';ELSE : 'else';WHILE : 'while';LOG : 'log';ID : [a-zA-Z_] [a-zA-Z_0-9]* ;INT : [0-9]+ ;FLOAT : [0-9]+ '.' [0-9]* | '.' [0-9]+ ;STRING : '"' (~["rn] | '""')* '"' ;COMMENT : '#' ~[rn]* -> skip ;SPACE : [ trn] -> skip ;OTHER : . ;现在,假设你要分析和评估如下输入:
test.mu
a = true;b = false;if a && b { log "1 :: a=" + a +", b=" + b;}else if a || b { log "2 :: a=" + a +", b=" + b;}else { log "3 :: a=" + a +", b=" + b;}log "Done!";2.访客我
首先生成解析器和访问者类:
java -cp antlr-4.0-complete.jar org.antlr.v4.Tool Mu.g4 -visitor
上面的命令会生成文件,等等MubaseVisitor
evalVisitor.javapublic class evalVisitor extends MubaseVisitor<Value> { // ...}这里
Value只是我们的任何语言的类型的包装(
String,
Boolean,
Double):
Value.javapublic class Value { public static Value VOID = new Value(new Object()); final Object value; public Value(Object value) { this.value = value; } public Boolean asBoolean() { return (Boolean)value; } public Double asDouble() { return (Double)value; } public String asString() { return String.valueOf(value); } public boolean isDouble() { return value instanceof Double; } @Override public int hashCode() { if(value == null) { return 0; } return this.value.hashCode(); } @Override public boolean equals(Object o) { if(value == o) { return true; } if(value == null || o == null || o.getClass() != value.getClass()) { return false; } Value that = (Value)o; return this.value.equals(that.value); } @Override public String toString() { return String.valueOf(value); }}3.测试一
要测试这些类,请使用以下Main类:
Main.javaimport org.antlr.v4.runtime.ANTLRFileStream;import org.antlr.v4.runtime.CommonTokenStream;import org.antlr.v4.runtime.tree.ParseTree;public class Main { public static void main(String[] args) throws Exception { MuLexer lexer = new MuLexer(new ANTLRFileStream("test.mu")); MuParser parser = new MuParser(new CommonTokenStream(lexer)); ParseTree tree = parser.parse(); evalVisitor visitor = new evalVisitor(); visitor.visit(tree); }}并编译并运行源文件:
javac -cp antlr-4.0-complete.jar *.javajava -cp .:antlr-4.0-complete.jar Main
(在Windows上,最后的命令是:
java -cp .;antlr-4.0-complete.jar Main)
运行后
Main,什么都没有发生(当然?)。这是因为我们没有在
evalVisitor类中实现任何规则。为了能够
test.mu正确评估文件,我们需要为以下规则提供正确的实现:
if_stat
andExpr
orExpr
plusExpr
assignment
idAtom
booleanAtom
stringAtom
log
4.访客II和测试II
这是这些规则的实现:
import org.antlr.v4.runtime.misc.NotNull;import java.util.HashMap;import java.util.List;import java.util.Map;public class evalVisitor extends MubaseVisitor<Value> { // used to compare floating point numbers public static final double SMALL_VALUE = 0.00000000001; // store variables (there's only one global scope!) private Map<String, Value> memory = new HashMap<String, Value>(); // assignment/id overrides @Override public Value visitAssignment(MuParser.AssignmentContext ctx) { String id = ctx.ID().getText(); Value value = this.visit(ctx.expr()); return memory.put(id, value); } @Override public Value visitIdAtom(MuParser.IdAtomContext ctx) { String id = ctx.getText(); Value value = memory.get(id); if(value == null) { throw new RuntimeException("no such variable: " + id); } return value; } // atom overrides @Override public Value visitStringAtom(MuParser.StringAtomContext ctx) { String str = ctx.getText(); // strip quotes str = str.substring(1, str.length() - 1).replace("""", """); return new Value(str); } @Override public Value visitNumberAtom(MuParser.NumberAtomContext ctx) { return new Value(Double.valueOf(ctx.getText())); } @Override public Value visitBooleanAtom(MuParser.BooleanAtomContext ctx) { return new Value(Boolean.valueOf(ctx.getText())); } @Override public Value visitNilAtom(MuParser.NilAtomContext ctx) { return new Value(null); } // expr overrides @Override public Value visitParExpr(MuParser.ParExprContext ctx) { return this.visit(ctx.expr()); } @Override public Value visitPowExpr(MuParser.PowExprContext ctx) { Value left = this.visit(ctx.expr(0)); Value right = this.visit(ctx.expr(1)); return new Value(Math.pow(left.asDouble(), right.asDouble())); } @Override public Value visitUnaryMinusExpr(MuParser.UnaryMinusExprContext ctx) { Value value = this.visit(ctx.expr()); return new Value(-value.asDouble()); } @Override public Value visitNotExpr(MuParser.NotExprContext ctx) { Value value = this.visit(ctx.expr()); return new Value(!value.asBoolean()); } @Override public Value visitMultiplicationExpr(@NotNull MuParser.MultiplicationExprContext ctx) { Value left = this.visit(ctx.expr(0)); Value right = this.visit(ctx.expr(1)); switch (ctx.op.getType()) { case MuParser.MULT: return new Value(left.asDouble() * right.asDouble()); case MuParser.DIV: return new Value(left.asDouble() / right.asDouble()); case MuParser.MOD: return new Value(left.asDouble() % right.asDouble()); default: throw new RuntimeException("unknown operator: " + MuParser.tokenNames[ctx.op.getType()]); } } @Override public Value visitAdditiveExpr(@NotNull MuParser.AdditiveExprContext ctx) { Value left = this.visit(ctx.expr(0)); Value right = this.visit(ctx.expr(1)); switch (ctx.op.getType()) { case MuParser.PLUS: return left.isDouble() && right.isDouble() ? new Value(left.asDouble() + right.asDouble()) : new Value(left.asString() + right.asString()); case MuParser.MINUS: return new Value(left.asDouble() - right.asDouble()); default: throw new RuntimeException("unknown operator: " + MuParser.tokenNames[ctx.op.getType()]); } } @Override public Value visitRelationalExpr(@NotNull MuParser.RelationalExprContext ctx) { Value left = this.visit(ctx.expr(0)); Value right = this.visit(ctx.expr(1)); switch (ctx.op.getType()) { case MuParser.LT: return new Value(left.asDouble() < right.asDouble()); case MuParser.LTEQ: return new Value(left.asDouble() <= right.asDouble()); case MuParser.GT: return new Value(left.asDouble() > right.asDouble()); case MuParser.GTEQ: return new Value(left.asDouble() >= right.asDouble()); default: throw new RuntimeException("unknown operator: " + MuParser.tokenNames[ctx.op.getType()]); } } @Override public Value visitEqualityExpr(@NotNull MuParser.EqualityExprContext ctx) { Value left = this.visit(ctx.expr(0)); Value right = this.visit(ctx.expr(1)); switch (ctx.op.getType()) { case MuParser.EQ: return left.isDouble() && right.isDouble() ? new Value(Math.abs(left.asDouble() - right.asDouble()) < SMALL_VALUE) : new Value(left.equals(right)); case MuParser.NEQ: return left.isDouble() && right.isDouble() ? new Value(Math.abs(left.asDouble() - right.asDouble()) >= SMALL_VALUE) : new Value(!left.equals(right)); default: throw new RuntimeException("unknown operator: " + MuParser.tokenNames[ctx.op.getType()]); } } @Override public Value visitAndExpr(MuParser.AndExprContext ctx) { Value left = this.visit(ctx.expr(0)); Value right = this.visit(ctx.expr(1)); return new Value(left.asBoolean() && right.asBoolean()); } @Override public Value visitOrExpr(MuParser.OrExprContext ctx) { Value left = this.visit(ctx.expr(0)); Value right = this.visit(ctx.expr(1)); return new Value(left.asBoolean() || right.asBoolean()); } // log override @Override public Value visitLog(MuParser.LogContext ctx) { Value value = this.visit(ctx.expr()); System.out.println(value); return value; } // if override @Override public Value visitIf_stat(MuParser.If_statContext ctx) { List<MuParser.Condition_blockContext> conditions = ctx.condition_block(); boolean evaluatedBlock = false; for(MuParser.Condition_blockContext condition : conditions) { Value evaluated = this.visit(condition.expr()); if(evaluated.asBoolean()) { evaluatedBlock = true; // evaluate this block whose expr==true this.visit(condition.stat_block()); break; } } if(!evaluatedBlock && ctx.stat_block() != null) { // evaluate the else-stat_block (if present == not null) this.visit(ctx.stat_block()); } return Value.VOID; } // while override @Override public Value visitWhile_stat(MuParser.While_statContext ctx) { Value value = this.visit(ctx.expr()); while(value.asBoolean()) { // evaluate the pre block this.visit(ctx.stat_block()); // evaluate the expression value = this.visit(ctx.expr()); } return Value.VOID; }}重新编译并运行时Main,将在控制台上显示以下内容:
2 :: a=true, b=falseDone!
有关所有其他规则的实现,请参见:https : //github.com/bkiers/Mu
编辑
来自@pwwpche,在注释中:
对于使用jdk1.8并遇到的用户
IndexOutOfBoundsException,antlr 4.0某种程度上与jdk1.8不兼容。下载
antlr-4.6-complete.jar,并替换
expr POW<assoc=right> expr为
<assoc=right>expr POW expr将会消除错误和警告。



