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, , replace expr pow<assoc=right> expr <assoc=right>expr pow expr eliminate error , warnings.


Comments

Popular posts from this blog

java - Date formats difference between yyyy-MM-dd'T'HH:mm:ss and yyyy-MM-dd'T'HH:mm:ssXXX -

c# - Get rid of xmlns attribute when adding node to existing xml -