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.Tool
de opdrachtregelparameter -visitor
geeft, 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 Value
is 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 Main
Klasse:
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 EvalVisitor
Class implementeren. Om het bestand te kunnen evalueren test.mu
JUISTE, 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 Main
opnieuw 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
IndexOutOfBoundsException
tegenkomen, antlr 4.0
is op de een of andere manier niet compatibel met jdk1.8. Download antlr-4.6-complete.jar, en
vervangexpr POW<assoc=right> expr
met<assoc=right>expr POW expr
zal
elimineer de fout en waarschuwingen.