| 609 |      1 | // A Small Compiler for the WHILE Language
 | 
|  |      2 | // (it does not use a parser and lexer)
 | 
|  |      3 | 
 | 
|  |      4 | // the abstract syntax trees
 | 
|  |      5 | abstract class Stmt
 | 
|  |      6 | abstract class AExp
 | 
|  |      7 | abstract class BExp 
 | 
|  |      8 | type Block = List[Stmt]
 | 
|  |      9 | 
 | 
|  |     10 | // statements
 | 
|  |     11 | case object Skip extends Stmt
 | 
|  |     12 | case class If(a: BExp, bl1: Block, bl2: Block) extends Stmt
 | 
|  |     13 | case class While(b: BExp, bl: Block) extends Stmt
 | 
|  |     14 | case class Assign(s: String, a: AExp) extends Stmt
 | 
|  |     15 | case class Write(s: String) extends Stmt
 | 
|  |     16 | case class Read(s: String) extends Stmt
 | 
|  |     17 | 
 | 
|  |     18 | // arithmetic expressions
 | 
|  |     19 | case class Var(s: String) extends AExp
 | 
|  |     20 | case class Num(i: Int) extends AExp
 | 
|  |     21 | case class Aop(o: String, a1: AExp, a2: AExp) extends AExp
 | 
|  |     22 | 
 | 
|  |     23 | // boolean expressions
 | 
|  |     24 | case object True extends BExp
 | 
|  |     25 | case object False extends BExp
 | 
|  |     26 | case class Bop(o: String, a1: AExp, a2: AExp) extends BExp
 | 
|  |     27 | 
 | 
|  |     28 | 
 | 
|  |     29 | // compiler headers needed for the JVM
 | 
|  |     30 | // (contains an init method, as well as methods for read and write)
 | 
|  |     31 | val beginning = """
 | 
|  |     32 | .class public XXX.XXX
 | 
|  |     33 | .super java/lang/Object
 | 
|  |     34 | 
 | 
|  |     35 | .method public <init>()V
 | 
|  |     36 |    aload_0
 | 
|  |     37 |    invokenonvirtual java/lang/Object/<init>()V
 | 
|  |     38 |    return
 | 
|  |     39 | .end method
 | 
|  |     40 | 
 | 
|  |     41 | .method public static write(I)V 
 | 
|  |     42 |     .limit locals 1 
 | 
|  |     43 |     .limit stack 2 
 | 
|  |     44 |     getstatic java/lang/System/out Ljava/io/PrintStream; 
 | 
|  |     45 |     iload 0
 | 
|  |     46 |     invokevirtual java/io/PrintStream/println(I)V 
 | 
|  |     47 |     return 
 | 
|  |     48 | .end method
 | 
|  |     49 | 
 | 
|  |     50 | .method public static read()I 
 | 
|  |     51 |     .limit locals 10 
 | 
|  |     52 |     .limit stack 10
 | 
|  |     53 | 
 | 
|  |     54 |     ldc 0 
 | 
|  |     55 |     istore 1  ; this will hold our final integer 
 | 
|  |     56 | Label1: 
 | 
|  |     57 |     getstatic java/lang/System/in Ljava/io/InputStream; 
 | 
|  |     58 |     invokevirtual java/io/InputStream/read()I 
 | 
|  |     59 |     istore 2 
 | 
|  |     60 |     iload 2 
 | 
|  |     61 |     ldc 10   ; the newline delimiter 
 | 
|  |     62 |     isub 
 | 
|  |     63 |     ifeq Label2 
 | 
|  |     64 |     iload 2 
 | 
|  |     65 |     ldc 32   ; the space delimiter 
 | 
|  |     66 |     isub 
 | 
|  |     67 |     ifeq Label2
 | 
|  |     68 | 
 | 
|  |     69 |     iload 2 
 | 
|  |     70 |     ldc 48   ; we have our digit in ASCII, have to subtract it from 48 
 | 
|  |     71 |     isub 
 | 
|  |     72 |     ldc 10 
 | 
|  |     73 |     iload 1 
 | 
|  |     74 |     imul 
 | 
|  |     75 |     iadd 
 | 
|  |     76 |     istore 1 
 | 
|  |     77 |     goto Label1 
 | 
|  |     78 | Label2: 
 | 
|  |     79 |     ;when we come here we have our integer computed in local variable 1 
 | 
|  |     80 |     iload 1 
 | 
|  |     81 |     ireturn 
 | 
|  |     82 | .end method
 | 
|  |     83 | 
 | 
|  |     84 | .method public static main([Ljava/lang/String;)V
 | 
|  |     85 |    .limit locals 200
 | 
|  |     86 |    .limit stack 200
 | 
|  |     87 | 
 | 
|  |     88 | """
 | 
|  |     89 | 
 | 
|  |     90 | val ending = """
 | 
|  |     91 | 
 | 
|  |     92 |    return
 | 
|  |     93 | 
 | 
|  |     94 | .end method
 | 
|  |     95 | """
 | 
|  |     96 | 
 | 
|  |     97 | println("Start compilation")
 | 
|  |     98 | 
 | 
|  |     99 | 
 | 
|  |    100 | // for generating new labels
 | 
|  |    101 | var counter = -1
 | 
|  |    102 | 
 | 
|  |    103 | def Fresh(x: String) = {
 | 
|  |    104 |   counter += 1
 | 
|  |    105 |   x ++ "_" ++ counter.toString()
 | 
|  |    106 | }
 | 
|  |    107 | 
 | 
|  |    108 | // environments and instructions
 | 
|  |    109 | type Env = Map[String, String]
 | 
|  |    110 | type Instrs = List[String]
 | 
|  |    111 | 
 | 
|  |    112 | // arithmetic expression compilation
 | 
|  |    113 | def compile_aexp(a: AExp, env : Env) : Instrs = a match {
 | 
|  |    114 |   case Num(i) => List("ldc " + i.toString + "\n")
 | 
|  |    115 |   case Var(s) => List("iload " + env(s) + "\n")
 | 
|  |    116 |   case Aop("+", a1, a2) => 
 | 
|  |    117 |     compile_aexp(a1, env) ++ 
 | 
|  |    118 |     compile_aexp(a2, env) ++ List("iadd\n")
 | 
|  |    119 |   case Aop("-", a1, a2) => 
 | 
|  |    120 |     compile_aexp(a1, env) ++ compile_aexp(a2, env) ++ List("isub\n")
 | 
|  |    121 |   case Aop("*", a1, a2) => 
 | 
|  |    122 |     compile_aexp(a1, env) ++ compile_aexp(a2, env) ++ List("imul\n")
 | 
|  |    123 | }
 | 
|  |    124 | 
 | 
|  |    125 | // boolean expression compilation
 | 
|  |    126 | def compile_bexp(b: BExp, env : Env, jmp: String) : Instrs = b match {
 | 
|  |    127 |   case True => Nil
 | 
|  |    128 |   case False => List("goto " + jmp + "\n")
 | 
|  |    129 |   case Bop("=", a1, a2) => 
 | 
|  |    130 |     compile_aexp(a1, env) ++ compile_aexp(a2, env) ++ 
 | 
|  |    131 |     List("if_icmpne " + jmp + "\n")
 | 
|  |    132 |   case Bop("!=", a1, a2) => 
 | 
|  |    133 |     compile_aexp(a1, env) ++ compile_aexp(a2, env) ++ 
 | 
|  |    134 |     List("if_icmpeq " + jmp + "\n")
 | 
|  |    135 |   case Bop("<", a1, a2) => 
 | 
|  |    136 |     compile_aexp(a1, env) ++ compile_aexp(a2, env) ++ 
 | 
|  |    137 |     List("if_icmpge " + jmp + "\n")
 | 
|  |    138 | }
 | 
|  |    139 | 
 | 
|  |    140 | // statement compilation
 | 
|  |    141 | def compile_stmt(s: Stmt, env: Env) : (Instrs, Env) = s match {
 | 
|  |    142 |   case Skip => (Nil, env)
 | 
|  |    143 |   case Assign(x, a) => {
 | 
|  |    144 |     val index = if (env.isDefinedAt(x)) env(x) else 
 | 
|  |    145 |                     env.keys.size.toString
 | 
|  |    146 |     (compile_aexp(a, env) ++ 
 | 
|  |    147 |      List("istore " + index + "\n"), env + (x -> index))
 | 
|  |    148 |   } 
 | 
|  |    149 |   case If(b, bl1, bl2) => {
 | 
|  |    150 |     val if_else = Fresh("If_else")
 | 
|  |    151 |     val if_end = Fresh("If_end")
 | 
|  |    152 |     val (instrs1, env1) = compile_block(bl1, env)
 | 
|  |    153 |     val (instrs2, env2) = compile_block(bl2, env1)
 | 
|  |    154 |     (compile_bexp(b, env, if_else) ++
 | 
|  |    155 |      instrs1 ++
 | 
|  |    156 |      List("goto " + if_end + "\n") ++
 | 
|  |    157 |      List("\n" + if_else + ":\n\n") ++
 | 
|  |    158 |      instrs2 ++
 | 
|  |    159 |      List("\n" + if_end + ":\n\n"), env2)
 | 
|  |    160 |   }
 | 
|  |    161 |   case While(b, bl) => {
 | 
|  |    162 |     val loop_begin = Fresh("Loop_begin")
 | 
|  |    163 |     val loop_end = Fresh("Loop_end")
 | 
|  |    164 |     val (instrs1, env1) = compile_block(bl, env)
 | 
|  |    165 |     (List("\n" + loop_begin + ":\n\n") ++
 | 
|  |    166 |      compile_bexp(b, env, loop_end) ++
 | 
|  |    167 |      instrs1 ++
 | 
|  |    168 |      List("goto " + loop_begin + "\n") ++
 | 
|  |    169 |      List("\n" + loop_end + ":\n\n"), env1)
 | 
|  |    170 |   }
 | 
|  |    171 |   case Write(x) => 
 | 
|  |    172 |     (List("iload " + env(x) + "\n" + 
 | 
|  |    173 |            "invokestatic XXX/XXX/write(I)V\n"), env)
 | 
|  |    174 |   case Read(x) => {
 | 
|  |    175 |     val index = if (env.isDefinedAt(x)) env(x) else 
 | 
|  |    176 |                     env.keys.size.toString
 | 
|  |    177 |     (List("invokestatic XXX/XXX/read()I\n" + 
 | 
|  |    178 |           "istore " + index + "\n"), env + (x -> index))
 | 
|  |    179 |   }
 | 
|  |    180 | }
 | 
|  |    181 | 
 | 
|  |    182 | // compilation of a block (i.e. list of instructions)
 | 
|  |    183 | def compile_block(bl: Block, env: Env) : (Instrs, Env) = bl match {
 | 
|  |    184 |   case Nil => (Nil, env)
 | 
|  |    185 |   case s::bl => {
 | 
|  |    186 |     val (instrs1, env1) = compile_stmt(s, env)
 | 
|  |    187 |     val (instrs2, env2) = compile_block(bl, env1)
 | 
|  |    188 |     (instrs1 ++ instrs2, env2)
 | 
|  |    189 |   }
 | 
|  |    190 | }
 | 
|  |    191 | 
 | 
|  |    192 | // main compilation function for blocks
 | 
|  |    193 | def compile(bl: Block, class_name: String) : String = {
 | 
|  |    194 |   val instructions = compile_block(bl, Map.empty)._1
 | 
|  |    195 |   (beginning ++ instructions.mkString ++ ending).replaceAllLiterally("XXX", class_name)
 | 
|  |    196 | }
 | 
|  |    197 | 
 | 
|  |    198 | 
 | 
|  |    199 | // compiling and running files
 | 
|  |    200 | //
 | 
|  |    201 | // JVM files can be assembled with 
 | 
|  |    202 | //
 | 
|  |    203 | //    java -jar jvm/jasmin-2.4/jasmin.jar fib.j
 | 
|  |    204 | //
 | 
|  |    205 | // and started with
 | 
|  |    206 | //
 | 
|  |    207 | //    java fib/fib
 | 
|  |    208 | 
 | 
|  |    209 | 
 | 
|  |    210 | 
 | 
|  |    211 | import scala.util._
 | 
|  |    212 | import scala.sys.process._
 | 
|  |    213 | import scala.io
 | 
|  |    214 | 
 | 
|  |    215 | def compile_tofile(bl: Block, class_name: String) = {
 | 
|  |    216 |   val output = compile(bl, class_name)
 | 
|  |    217 |   val fw = new java.io.FileWriter(class_name + ".j") 
 | 
|  |    218 |   fw.write(output) 
 | 
|  |    219 |   fw.close()
 | 
|  |    220 | }
 | 
|  |    221 | 
 | 
|  |    222 | def compile_all(bl: Block, class_name: String) : Unit = {
 | 
|  |    223 |   compile_tofile(bl, class_name)
 | 
|  |    224 |   println("compiled ")
 | 
|  |    225 |   val test = ("java -jar jvm/jasmin-2.4/jasmin.jar " + class_name + ".j").!!
 | 
|  |    226 |   println("assembled ")
 | 
|  |    227 | }
 | 
|  |    228 | 
 | 
|  |    229 | def time_needed[T](i: Int, code: => T) = {
 | 
|  |    230 |   val start = System.nanoTime()
 | 
|  |    231 |   for (j <- 1 to i) code
 | 
|  |    232 |   val end = System.nanoTime()
 | 
|  |    233 |   (end - start)/(i * 1.0e9)
 | 
|  |    234 | }
 | 
|  |    235 | 
 | 
|  |    236 | 
 | 
|  |    237 | def compile_run(bl: Block, class_name: String) : Unit = {
 | 
|  |    238 |   println("Start compilation")
 | 
|  |    239 |   compile_all(bl, class_name)
 | 
|  |    240 |   println("Time: " + time_needed(1, ("java " + class_name + "/" + class_name).!))
 | 
|  |    241 | }
 | 
|  |    242 | 
 | 
|  |    243 | 
 | 
|  |    244 | // Fibonacci numbers as a test-case
 | 
|  |    245 | val fib_test = 
 | 
|  |    246 |   List(Assign("n", Num(10)),            //  n := 10;                     
 | 
|  |    247 |        Assign("minus1",Num(0)),         //  minus1 := 0;
 | 
|  |    248 |        Assign("minus2",Num(1)),         //  minus2 := 1;
 | 
|  |    249 |        Assign("temp",Num(0)),           //  temp := 0;
 | 
|  |    250 |        While(Bop("<",Num(0),Var("n")),  //  while n > 0 do  {
 | 
|  |    251 |           List(Assign("temp",Var("minus2")),    //  temp := minus2;
 | 
|  |    252 |                Assign("minus2",Aop("+",Var("minus1"),Var("minus2"))), 
 | 
|  |    253 |                                         //  minus2 := minus1 + minus2;
 | 
|  |    254 |                Assign("minus1",Var("temp")), //  minus1 := temp;
 | 
|  |    255 |                Assign("n",Aop("-",Var("n"),Num(1))))), //  n := n - 1 };
 | 
|  |    256 |        Write("minus1"))                 //  write minus1
 | 
|  |    257 | 
 | 
|  |    258 | 
 | 
|  |    259 | compile_run(fib_test, "fib")
 | 
|  |    260 | 
 | 
|  |    261 | 
 |