If / else instruções em ANTLR usando ouvintes

Estou criando uma linguagem de programação simples para um projeto escolar. Estou usando o ANTLR 4 para gerar um lexer e um analisador da minha gramática. Até agora, tenho usado o padrão ouvinte ANTLRs para aplicar a funcionalidade real da linguagem de programação.

Agora gostaria de implementar instruções if / else, mas não tenho certeza de que elas possam ser implementadas ao usar o padrão de listener, já que ANTLR decide em qual ordem percorrer a tree de análise ao usar ouvintes e imagino que a implementação de if / Senão, as instruções exigirão que você salte pela tree de análise, dependendo de qual condição na instrução for satisfeita.

Alguém pode me dizer se será possível implementar instruções if / else usando ANTLR ou se eu vou ter que implementar o padrão de visitante eu mesmo? Além disso, alguém pode dar um exemplo extremamente simples da implementação das declarações?

   

    Por padrão, o ANTLR 4 gera ouvintes. Mas se você der org.antlr.v4.Tool o parâmetro de linha de comando -visitor , ANTLR gera classs de visitante para você. Estes funcionam muito como ouvintes, mas dão a você mais controle sobre quais (sub) trees são percorridas / visitadas. Isso é particularmente útil se você quiser excluir determinadas (sub) trees (como em caso de mais / if blocos, como no seu caso). Embora isso possa ser feito usando ouvintes, é muito mais fácil fazer isso com um visitante. Usando ouvintes, você precisará introduzir variables ​​globais que acompanhem se uma tree (sub) precisa ser avaliada e quais não.

    Por coincidência, estou trabalhando em um pequeno tutorial do ANTLR 4. Ainda não foi feito, mas vou postar um pequeno exemplo de trabalho que demonstra o uso dessas classs de visitantes e uma construção if statement.


    1. Gramática

    Aqui está uma gramática simples que suporta expressões básicas, if -, while – e log -statements:

    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 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 : '"' (~["\r\n] | '""')* '"' ; COMMENT : '#' ~[\r\n]* -> skip ; SPACE : [ \t\r\n] -> skip ; OTHER : . ; 

    Agora, digamos que você gostaria de analisar e avaliar, insira assim:

    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. Visitante I

    Comece gerando as classs do analisador e do visitante:

     java -cp antlr-4.0-complete.jar org.antlr.v4.Tool Mu.g4 -visitor 

    O comando acima teria gerado, entre outros, o arquivo MuBaseVisitor . Esta é a class que vamos estender sem nossa própria lógica:

    EvalVisitor.java

     public class EvalVisitor extends MuBaseVisitor { // ... } 

    onde Value é apenas um wrapper para qualquer um dos tipos de linguagem ( String , Boolean , Double ):

    Value.java

     public 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. Teste I

    Para testar as classs, use a seguinte class Main :

    Main.java

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

    e compile e execute os arquivos de origem:

     javac -cp antlr-4.0-complete.jar *.java java -cp .:antlr-4.0-complete.jar Main 

    (no Windows, o último comando seria: java -cp .;antlr-4.0-complete.jar Main )

    Depois de executar o Main , nada acontece (claro?). Isso ocorre porque não implementamos nenhuma das regras da nossa class EvalVisitor . Para poder avaliar o arquivo test.mu adequadamente, precisamos fornecer uma implementação adequada para as seguintes regras:

    • if_stat
    • andExpr
    • orExpr
    • plusExpr
    • assignment
    • idAtom
    • booleanAtom
    • stringAtom
    • log

    4. Visitante II e Teste II

    Aqui está uma implementação dessas regras:

     import org.antlr.v4.runtime.misc.NotNull; import java.util.HashMap; import java.util.List; import java.util.Map; public class EvalVisitor extends MuBaseVisitor { // used to compare floating point numbers public static final double SMALL_VALUE = 0.00000000001; // store variables (there's only one global scope!) private Map memory = new HashMap(); // 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 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 code block this.visit(ctx.stat_block()); // evaluate the expression value = this.visit(ctx.expr()); } return Value.VOID; } } 

    Quando você re-compilar e executar o Main , o seguinte será impresso em seu console:

     2 :: a=true, b=false Done! 

    Para uma implementação de todas as outras regras, consulte: https://github.com/bkiers/Mu

    EDITAR

    De @pwwpche, nos comentários:

    para aqueles que usam o jdk1.8 e encontram o IndexOutOfBoundsException , o antlr 4.0 não é compatível de alguma forma com o jdk1.8. Faça o download de antlr-4.6-complete.jar e substitua expr POW expr com expr POW expr eliminará o erro e os avisos.