java - If/else statements in ANTLR using listeners -
i'm creating simple programming language school project. i'm using antlr 4 generate lexer , parser grammar. until now, have been using antlrs listener pattern apply actual functionality of programming language.
now implement if/else statements i'm not sure these can implemented when using listener pattern antlr decides in order traverse parse tree when using listeners , imagine implementation of if/else statements require jumping around parse tree depending on condition in statement satisfied.
can tell me if possible implement if/else statements using antlr or if have implement visitor pattern myself? also, can give extremely simple example of implementation statements?
by default, antlr 4 generates listeners. if give org.antlr.v4.tool
command line parameter -visitor
, antlr generates visitor classes you. these work listeners, give more control on (sub) trees walked/visited. particularly useful if want exclude (sub) trees (like else/if blocks, in case). while can done using listeners, it's cleaner visitor. using listeners, you'll need introduce global variables keep track if (sub) tree needs evaluated, , not.
as happens be, i'm working on small antlr 4 tutorial. it's not done yet, i'll post small working example demonstrates use of these visitor classes , if
statement construct.
1. grammar
here's simple grammar supporting basic expressions, if
-, while
- , 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 , 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 : '||'; , : '&&'; 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 : . ;
now let's parse, , evaluate, input this:
test.mu
a = true; b = false; if && b { log "1 :: a=" + +", b=" + b; } else if || b { log "2 :: a=" + +", b=" + b; } else { log "3 :: a=" + +", b=" + b; } log "done!";
2. visitor i
start generating parser , visitor classes:
java -cp antlr-4.0-complete.jar org.antlr.v4.tool mu.g4 -visitor
the command above have generated, among others file mubasevisitor<t>
. class we're going extend out own logic:
evalvisitor.java
public class evalvisitor extends mubasevisitor<value> { // ... }
where value
wrapper of our language's types (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 = (value)o; return this.value.equals(that.value); } @override public string tostring() { return string.valueof(value); } }
3. test i
to test classes, use following main
class:
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); } }
and compile , run source files:
javac -cp antlr-4.0-complete.jar *.java java -cp .:antlr-4.0-complete.jar main
(on windows, last command be: java -cp .;antlr-4.0-complete.jar main
)
after running main
, nothing happens (of course?). because didn't implement of rules in our evalvisitor
class. able evaluate file test.mu
properly, need provide proper implementation following rules:
if_stat
andexpr
orexpr
plusexpr
assignment
idatom
booleanatom
stringatom
log
4. visitor ii & test ii
here's implementation of these rules:
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 compare floating point numbers public static final double small_value = 0.00000000001; // store variables (there's 1 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 block expr==true this.visit(condition.stat_block()); break; } } if(!evaluatedblock && ctx.stat_block() != null) { // evaluate 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 code block this.visit(ctx.stat_block()); // evaluate expression value = this.visit(ctx.expr()); } return value.void; } }
when re-compile , run main
, following printed console:
2 :: a=true, b=false done!
for implementation of other rules, see: https://github.com/bkiers/mu
edit
from @pwwpche, in comments:
for using jdk1.8 , encounter
indexoutofboundsexception
, antlr 4.0 somehow not compatible jdk1.8. download antlr-4.6-complete.jar, , replaceexpr pow<assoc=right> expr
<assoc=right>expr pow expr
eliminate error , warnings.
Comments
Post a Comment