If/else-instructies in ANTLR met luisteraars

Ik ben een eenvoudige programmeertaal aan het maken voor een schoolproject. Ik gebruik ANTLR 4 om een ​​lexer en een parser uit mijn grammatica te genereren. Tot nu toe heb ik het listenerpatroon van ANTLR gebruikt om de daadwerkelijke functionaliteit van de programmeertaal toe te passen.

Nu zou ik if/else-statements willen implementeren, maar ik weet niet zeker of deze daadwerkelijk kunnen worden geïmplementeerd wanneer het listenerpatroon wordt gebruikt, aangezien ANTLR beslist in welke volgorde de ontledingsboom moet worden doorkruist bij het gebruik van listeners en ik stel me voor dat de implementatie van if/else-statements moet door de ontledingsboom worden gesprongen, afhankelijk van aan welke voorwaarde in het statement is voldaan.

Kan iemand mij vertellen of het mogelijk is om if/else-statements te implementeren met ANTLR of dat ik het bezoekerspatroon zelf moet implementeren? Kan iemand ook een extreem eenvoudig voorbeeld geven van de implementatie van de verklaringen?


Antwoord 1, autoriteit 100%

Standaard genereert ANTLR 4 luisteraars. Maar als u org.antlr.v4.Toolde opdrachtregelparameter -visitorgeeft, genereert ANTLR bezoekersklassen voor u. Deze werken net als luisteraars, maar geven je meer controle over welke (sub)bomen worden gelopen/bezocht. Dit is vooral handig als je bepaalde (sub)bomen wilt uitsluiten (zoals else/if-blokken, zoals in jouw geval). Hoewel dit kanworden gedaan met behulp van luisteraars, is het veel schoner om dit met een bezoeker te doen. Met behulp van listeners moet je globale variabelen introduceren die bijhouden of een (sub)boom moet worden geëvalueerd, en welke niet.

Ik werk toevallig aan een kleine ANTLR 4-tutorial. Het is nog niet klaar, maar ik zal een klein werkend voorbeeld plaatsen dat het gebruik van deze bezoekersklassen en een if-statementconstructie demonstreert.


1. Grammatica

Hier is een eenvoudige grammatica die basisuitdrukkingen ondersteunt, if-, while– en 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<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
 : '"' (~["\r\n] | '""')* '"'
 ;
COMMENT
 : '#' ~[\r\n]* -> skip
 ;
SPACE
 : [ \t\r\n] -> skip
 ;
OTHER
 : . 
 ;

Stel nu dat u invoer als volgt wilt ontleden en evalueren:

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. Bezoeker i

Begin met het genereren van de parser en bezoekersklassen:

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

De bovenstaande opdracht zou onder meer het bestand MuBaseVisitor<T>hebben gegenereerd. Dit is de klas die we gaan uitstrekken zonder eigen logica:

EVALISITOR.JAVA

public class EvalVisitor extends MuBaseVisitor<Value> {
    // ...
}

Waar Valueis slechts een wikkel voor de typen van onze taal (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() != this.getClass()) {
            return false;
        }
        Value that = (Value)o;
        return this.value.equals(that.value);
    }
    @Override
    public String toString() {
        return String.valueOf(value);
    }
}

3. Test I

Gebruik de volgende MainKlasse:

om de klassen te testen.

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

en compileer en voer de bronbestanden uit:

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

(op Windows, de laatste opdracht zou zijn: java -cp .;antlr-4.0-complete.jar Main)

Na het uitvoeren van Main, er gebeurt niets (natuurlijk?). Dit komt omdat we geen van de regels in onze EvalVisitorClass implementeren. Om het bestand te kunnen evalueren test.muJUISTE, moeten we een goede implementatie bieden voor de volgende regels:

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

# 4. Bezoeker II & AMP; Test II

Hier is een implementatie van deze regels:

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 code block
            this.visit(ctx.stat_block());
            // evaluate the expression
            value = this.visit(ctx.expr());
        }
        return Value.VOID;
    }
}

Als u Mainopnieuw compileert en uitvoert, wordt het volgende afgedrukt op uw console:

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

Voor een implementatie van alle andere regels, zie: https://github.com/bkiers/Mu

BEWERKEN

Van @pwwpche, in de reacties:

voor degenen die jdk1.8 gebruiken en IndexOutOfBoundsExceptiontegenkomen, antlr 4.0
is op de een of andere manier niet compatibel met jdk1.8. Download antlr-4.6-complete.jar, en
vervang expr POW<assoc=right> exprmet <assoc=right>expr POW exprzal
elimineer de fout en waarschuwingen.

Other episodes