Se / else istruzioni in ANTLR utilizzando ascoltatori

Sto creando un linguaggio di programmazione semplice per un progetto scolastico. Sto usando ANTLR 4 per generare un lexer e un parser dalla mia grammatica. Fino ad ora, ho utilizzato pattern listener ANTLR per applicare le effettive funzionalità del linguaggio di programmazione.

Ora vorrei implementare if / else, ma non sono sicuro che questi possano essere implementati quando si usa il pattern listener mentre ANTLR decide in quale ordine attraversare l’albero di analisi quando si usano gli ascoltatori e immagino che l’implementazione di if / altrimenti le istruzioni richiedono di saltare attorno all’albero di analisi a seconda di quale condizione nell’istruzione è soddisfatta.

Qualcuno può dirmi se sarà ansible implementare if / else statement usando ANTLR o se dovrò implementare il pattern visitor da solo? Inoltre, qualcuno può dare un esempio estremamente semplice dell’implementazione delle affermazioni?

Di default, ANTLR 4 genera ascoltatori. Ma se org.antlr.v4.Tool al parametro della riga di comando -visitor , ANTLR genera per te le classi visitatore. Funzionano molto come ascoltatori, ma ti danno un maggiore controllo su quali (sotto) alberi vengono visitati / visitati. Questo è particolarmente utile se vuoi escludere alcuni (sotto) alberi (come altri / se blocchi, come nel tuo caso). Mentre questo può essere fatto usando gli ascoltatori, è molto più pulito farlo con un visitatore. Utilizzando gli ascoltatori, è necessario introdurre variabili globali che tengano traccia di se un (sotto) albero deve essere valutato, e quali no.

Come è successo, sto lavorando su un piccolo tutorial ANTLR 4. Non è ancora stato fatto, ma pubblicherò un piccolo esempio di lavoro che dimostra l’uso di queste classi di visitatori e un costrutto if statement.


1. Grammatica

Ecco una semplice grammatica che supporta le espressioni di base, 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 : . ; 

Ora diciamo che ti piacerebbe analizzare e valutare l’input in questo modo:

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

Inizia generando le classi parser e visitor:

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

Il comando precedente avrebbe generato, tra gli altri, il file MuBaseVisitor . Questa è la class che estenderemo con la nostra logica:

EvalVisitor.java

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

dove Value è solo un wrapper per qualsiasi tipo di lingua ( 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. Test I

Per testare le classi, utilizzare la seguente 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 compila ed esegui i file sorgente:

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

(su Windows, l’ultimo comando sarà: java -cp .;antlr-4.0-complete.jar Main )

Dopo aver eseguito Main , non succede nulla (ovviamente?). Questo perché non abbiamo implementato nessuna delle regole nella nostra class EvalVisitor . Per poter valutare correttamente il file test.mu , è necessario fornire una corretta implementazione per le seguenti regole:

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

4. Visitor II & Test II

Ecco una implementazione di queste regole:

 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 ricompilate ed eseguite Main , il seguente testo verrà stampato sulla vostra console:

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

Per un’implementazione di tutte le altre regole, consultare: https://github.com/bkiers/Mu

MODIFICARE

Da @pwwpche, nei commenti:

per chi usa jdk1.8 e incontra IndexOutOfBoundsException , antlr 4.0 non è in qualche modo compatibile con jdk1.8. Scarica antlr-4.6-complete.jar e sostituisci expr POW expr con expr POW expr eliminerà l’errore e gli avvertimenti.