| author | Christian Urban <urbanc@in.tum.de> | 
| Wed, 09 Oct 2019 21:37:17 +0100 | |
| changeset 650 | b34efa58f7d5 | 
| parent 649 | 12c4957c15a9 | 
| child 653 | 0b26a7a0556b | 
| permissions | -rw-r--r-- | 
| 625 | 1 | // A Small Compiler for a Simple Functional Language | 
| 644 | 2 | // (includes an external lexer and parser) | 
| 645 | 3 | // | 
| 4 | // call with | |
| 5 | // | |
| 6 | // scala fun.scala fact | |
| 7 | // | |
| 8 | // scala fun.scala defs | |
| 9 | // | |
| 10 | // this will generate a .j file and run the jasmin | |
| 11 | // assembler (installed at jvm/jasmin-2.4/jasmin.jar) | |
| 12 | // it runs the resulting JVM file twice for timing | |
| 13 | // purposes. | |
| 220 
141041fc76b5
added
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: diff
changeset | 14 | |
| 645 | 15 | |
| 16 | ||
| 625 | 17 | |
| 649 | 18 | object Compiler {
 | 
| 220 
141041fc76b5
added
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: diff
changeset | 19 | |
| 645 | 20 | import java.io._ | 
| 21 | import scala.util._ | |
| 22 | import scala.sys.process._ | |
| 23 | ||
| 644 | 24 | // Abstract syntax trees for the Fun language | 
| 25 | abstract class Exp extends Serializable | |
| 26 | abstract class BExp extends Serializable | |
| 27 | abstract class Decl extends Serializable | |
| 626 | 28 | |
| 29 | case class Def(name: String, args: List[String], body: Exp) extends Decl | |
| 30 | case class Main(e: Exp) extends Decl | |
| 31 | ||
| 32 | case class Call(name: String, args: List[Exp]) extends Exp | |
| 33 | case class If(a: BExp, e1: Exp, e2: Exp) extends Exp | |
| 34 | case class Write(e: Exp) extends Exp | |
| 35 | case class Var(s: String) extends Exp | |
| 36 | case class Num(i: Int) extends Exp | |
| 37 | case class Aop(o: String, a1: Exp, a2: Exp) extends Exp | |
| 38 | case class Sequence(e1: Exp, e2: Exp) extends Exp | |
| 39 | case class Bop(o: String, a1: Exp, a2: Exp) extends BExp | |
| 40 | ||
| 41 | ||
| 220 
141041fc76b5
added
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: diff
changeset | 42 | // compiler - built-in functions | 
| 
141041fc76b5
added
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: diff
changeset | 43 | // copied from http://www.ceng.metu.edu.tr/courses/ceng444/link/jvm-cpm.html | 
| 
141041fc76b5
added
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: diff
changeset | 44 | // | 
| 
141041fc76b5
added
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: diff
changeset | 45 | |
| 
141041fc76b5
added
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: diff
changeset | 46 | |
| 
141041fc76b5
added
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: diff
changeset | 47 | // for generating new labels | 
| 
141041fc76b5
added
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: diff
changeset | 48 | var counter = -1 | 
| 
141041fc76b5
added
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: diff
changeset | 49 | |
| 
141041fc76b5
added
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: diff
changeset | 50 | def Fresh(x: String) = {
 | 
| 
141041fc76b5
added
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: diff
changeset | 51 | counter += 1 | 
| 
141041fc76b5
added
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: diff
changeset | 52 | x ++ "_" ++ counter.toString() | 
| 
141041fc76b5
added
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: diff
changeset | 53 | } | 
| 
141041fc76b5
added
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: diff
changeset | 54 | |
| 648 | 55 | |
| 56 | ||
| 57 | // Abstract syntax trees for the Fun language | |
| 58 | abstract class KExp | |
| 59 | ||
| 60 | case class KVar(s: String) extends KExp | |
| 61 | case class KNum(i: Int) extends KExp | |
| 62 | case class KAop(o: String, x1: String, x2: String) extends KExp | |
| 649 | 63 | case class KIfeq(x1: String, x2: String, e1: KExp, e2: KExp) extends KExp {
 | 
| 64 | override def toString = s"KIf $x1 == $x2 \nIF\n$e1\nELSE\n$e2" | |
| 65 | ||
| 66 | } | |
| 648 | 67 | case class KCall(o: String, vrs: List[String]) extends KExp | 
| 68 | case class KLet(x: String, e1: KExp, e2: KExp) extends KExp {
 | |
| 69 | override def toString = s"let $x = $e1 in \n$e2" | |
| 70 | } | |
| 71 | ||
| 72 | def K(e: Exp) : KExp = e match {
 | |
| 73 | case Var(s) => KVar(s) | |
| 74 | case Num(i) => KNum(i) | |
| 75 |   case Aop(o, a1, a2) => {
 | |
| 76 |     val x1 = Fresh("tmp")
 | |
| 77 |     val x2 = Fresh("tmp") 
 | |
| 78 | KLet(x1, K(a1), KLet(x2, K(a2), KAop(o, x1, x2))) | |
| 79 | } | |
| 80 |   case Call(name: String, args: List[Exp]) => {
 | |
| 649 | 81 |     val args_new = args.map{a => (Fresh("tmp"), K(a))}
 | 
| 648 | 82 |     def aux(as: List[(String, KExp)]) : KExp = as match {
 | 
| 83 | case Nil => KCall(name, args_new.map(_._1)) | |
| 84 | case (x, a)::rest => KLet(x, a, aux(rest)) | |
| 85 | } | |
| 86 | aux(args_new) | |
| 87 | } | |
| 649 | 88 |   case If(Bop("==", b1, b2), e1, e2) => {
 | 
| 89 |     val x1 = Fresh("tmp")
 | |
| 90 |     val x2 = Fresh("tmp") 
 | |
| 91 | KLet(x1, K(b1), KLet(x2, K(b2), KIfeq(x1, x2, K(e1), K(e2)))) | |
| 92 | } | |
| 648 | 93 | } | 
| 94 | ||
| 95 | def Denest(e: KExp) : KExp = e match {
 | |
| 96 |   case KLet(xt, e1, e2) => {
 | |
| 97 |     def insert(e: KExp) : KExp = e match {
 | |
| 98 | case KLet(yt, e3, e4) => KLet(yt, e3, insert(e4)) | |
| 99 | case e => KLet(xt, e, Denest(e2)) | |
| 100 | } | |
| 101 | insert(Denest(e1)) | |
| 102 | } | |
| 649 | 103 | case KIfeq(x1, x2, e1, e2) => KIfeq(x1, x2, Denest(e1), Denest(e2)) | 
| 648 | 104 | case _ => e | 
| 105 | } | |
| 106 | ||
| 649 | 107 | |
| 108 | val e = Aop("*", Aop("+", Num(1), Call("foo", List(Var("a"), Num(3)))), Num(4))
 | |
| 648 | 109 | println(K(e)) | 
| 649 | 110 | println() | 
| 648 | 111 | println(Denest(K(e))) | 
| 649 | 112 | println() | 
| 648 | 113 | |
| 114 | ||
| 115 | ||
| 625 | 116 | // convenient string interpolations | 
| 117 | // for instructions, labels and methods | |
| 118 | import scala.language.implicitConversions | |
| 119 | import scala.language.reflectiveCalls | |
| 120 | ||
| 121 | implicit def sring_inters(sc: StringContext) = new {
 | |
| 122 | def i(args: Any*): String = " " ++ sc.s(args:_*) ++ "\n" | |
| 123 | def l(args: Any*): String = sc.s(args:_*) ++ ":\n" | |
| 124 | def m(args: Any*): String = sc.s(args:_*) ++ "\n" | |
| 125 | } | |
| 126 | ||
| 220 
141041fc76b5
added
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: diff
changeset | 127 | |
| 625 | 128 | type Env = Map[String, Int] | 
| 129 | ||
| 648 | 130 | |
| 131 | ||
| 649 | 132 | // compile K expressions | 
| 133 | def compile_exp(a: KExp) : String = a match {
 | |
| 134 | case KNum(i) => s"?$i?" | |
| 135 | case KVar(s) => s"?$s?" | |
| 136 |   case KAop("+", x1, x2) => s"add i32 %$x1, i32 %$x2"
 | |
| 137 |   case KAop("-", x1, x2) => s"sub i32 %$x1, i32 %$x2"
 | |
| 138 |   case KAop("*", x1, x2) => s"mul i32 %$x1, i32 %$x2"
 | |
| 139 |   case KLet(x: String, e1: KExp, e2: KExp) => {
 | |
| 140 | val is1 = compile_exp(e1) | |
| 141 | val is2 = compile_exp(e2) | |
| 142 | i"%$x = $is1" ++ is2 | |
| 648 | 143 | } | 
| 650 | 144 |   case KLet(x: String, e1: KExp, e2: KExp) => {
 | 
| 145 | val is1 = compile_exp(e1) | |
| 146 | val is2 = compile_exp(e2) | |
| 147 | i"%$x = $is1" ++ is2 | |
| 148 | } | |
| 649 | 149 |   case KIfeq(x1, x2, a1, a2) => {
 | 
| 150 |     val if_br = Fresh("if_br")
 | |
| 151 |     val else_br = Fresh("else_br")
 | |
| 152 |     val x = Fresh("tmp")
 | |
| 153 | i"%$x = icmp eq i32 %$x1, i32 %$x2" ++ | |
| 154 | i"br i1 %$x, label %$if_br, label %$else_br" ++ | |
| 155 | l"\n$if_br" ++ | |
| 156 | compile_exp(a1) ++ | |
| 157 | l"\n$else_br" ++ | |
| 158 | compile_exp(a2) | |
| 648 | 159 | } | 
| 649 | 160 |   case KCall(x1, args) => {
 | 
| 161 | s"Call $x1 ($args)" | |
| 220 
141041fc76b5
added
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: diff
changeset | 162 | } | 
| 649 | 163 | /* | 
| 625 | 164 |   case Call(name, args) => {
 | 
| 221 
824ffbf66ab4
added fun tail
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
220diff
changeset | 165 | val is = "I" * args.length | 
| 625 | 166 | args.map(a => compile_exp(a, env)).mkString ++ | 
| 167 | i"invokestatic XXX/XXX/$name($is)I" | |
| 221 
824ffbf66ab4
added fun tail
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
220diff
changeset | 168 | } | 
| 625 | 169 |   case Sequence(a1, a2) => {
 | 
| 170 | compile_exp(a1, env) ++ i"pop" ++ compile_exp(a2, env) | |
| 221 
824ffbf66ab4
added fun tail
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
220diff
changeset | 171 | } | 
| 
824ffbf66ab4
added fun tail
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
220diff
changeset | 172 |   case Write(a1) => {
 | 
| 
824ffbf66ab4
added fun tail
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
220diff
changeset | 173 | compile_exp(a1, env) ++ | 
| 625 | 174 | i"dup" ++ | 
| 175 | i"invokestatic XXX/XXX/write(I)V" | |
| 221 
824ffbf66ab4
added fun tail
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
220diff
changeset | 176 | } | 
| 648 | 177 | */ | 
| 220 
141041fc76b5
added
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: diff
changeset | 178 | } | 
| 
141041fc76b5
added
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: diff
changeset | 179 | |
| 649 | 180 | /* | 
| 625 | 181 | // compile boolean expressions | 
| 182 | def compile_bexp(b: BExp, env : Env, jmp: String) : String = b match {
 | |
| 221 
824ffbf66ab4
added fun tail
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
220diff
changeset | 183 |   case Bop("==", a1, a2) => 
 | 
| 625 | 184 | compile_exp(a1, env) ++ compile_exp(a2, env) ++ i"if_icmpne $jmp" | 
| 220 
141041fc76b5
added
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: diff
changeset | 185 |   case Bop("!=", a1, a2) => 
 | 
| 625 | 186 | compile_exp(a1, env) ++ compile_exp(a2, env) ++ i"if_icmpeq $jmp" | 
| 220 
141041fc76b5
added
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: diff
changeset | 187 |   case Bop("<", a1, a2) => 
 | 
| 625 | 188 | compile_exp(a1, env) ++ compile_exp(a2, env) ++ i"if_icmpge $jmp" | 
| 221 
824ffbf66ab4
added fun tail
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
220diff
changeset | 189 |   case Bop("<=", a1, a2) => 
 | 
| 625 | 190 | compile_exp(a1, env) ++ compile_exp(a2, env) ++ i"if_icmpgt $jmp" | 
| 220 
141041fc76b5
added
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: diff
changeset | 191 | } | 
| 649 | 192 | */ | 
| 220 
141041fc76b5
added
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: diff
changeset | 193 | |
| 625 | 194 | // compile function for declarations and main | 
| 195 | def compile_decl(d: Decl) : String = d match {
 | |
| 649 | 196 |   case Def(name, args, body) => { 
 | 
| 197 | println(s"DEF\n $name ($args) = \nBODY:") | |
| 198 | println(Denest(K(body))) | |
| 199 | println() | |
| 200 | counter = -1 | |
| 201 |     m"define i32 @$name (${args.mkString("i32 %", ", i32 %", "")}) {" ++
 | |
| 202 | compile_exp(Denest(K(body))) ++ | |
| 203 | m"}\n" | |
| 221 
824ffbf66ab4
added fun tail
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
220diff
changeset | 204 | } | 
| 649 | 205 |   case Main(body) => {
 | 
| 206 |     m"define i32 @main() {" ++
 | |
| 207 | compile_exp(Denest(K(body))) ++ | |
| 208 | i"ret i32 0" ++ | |
| 209 | m"}\n" | |
| 221 
824ffbf66ab4
added fun tail
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: 
220diff
changeset | 210 | } | 
| 220 
141041fc76b5
added
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: diff
changeset | 211 | } | 
| 
141041fc76b5
added
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: diff
changeset | 212 | |
| 626 | 213 | // main compiler functions | 
| 220 
141041fc76b5
added
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: diff
changeset | 214 | |
| 
141041fc76b5
added
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: diff
changeset | 215 | def time_needed[T](i: Int, code: => T) = {
 | 
| 
141041fc76b5
added
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: diff
changeset | 216 | val start = System.nanoTime() | 
| 
141041fc76b5
added
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: diff
changeset | 217 | for (j <- 1 to i) code | 
| 
141041fc76b5
added
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: diff
changeset | 218 | val end = System.nanoTime() | 
| 
141041fc76b5
added
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: diff
changeset | 219 | (end - start)/(i * 1.0e9) | 
| 
141041fc76b5
added
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: diff
changeset | 220 | } | 
| 
141041fc76b5
added
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: diff
changeset | 221 | |
| 645 | 222 | def deserialise[T](fname: String) : Try[T] = {
 | 
| 223 | import scala.util.Using | |
| 224 |   Using(new ObjectInputStream(new FileInputStream(fname))) {
 | |
| 225 | in => in.readObject.asInstanceOf[T] | |
| 226 | } | |
| 644 | 227 | } | 
| 228 | ||
| 229 | def compile(class_name: String) : String = {
 | |
| 645 | 230 | val ast = deserialise[List[Decl]](class_name ++ ".prs").getOrElse(Nil) | 
| 649 | 231 | println(ast(0).toString ++ "\n") | 
| 232 | val instructions = List(ast(0), ast(2)).map(compile_decl).mkString | |
| 233 | instructions | |
| 626 | 234 | } | 
| 235 | ||
| 649 | 236 | /* | 
| 644 | 237 | def compile_to_file(class_name: String) = {
 | 
| 238 | val output = compile(class_name) | |
| 626 | 239 |   scala.tools.nsc.io.File(s"${class_name}.j").writeAll(output)
 | 
| 240 | } | |
| 241 | ||
| 645 | 242 | def compile_and_run(class_name: String) : Unit = {
 | 
| 644 | 243 | compile_to_file(class_name) | 
| 626 | 244 |   (s"java -jar jvm/jasmin-2.4/jasmin.jar ${class_name}.j").!!
 | 
| 625 | 245 |   println("Time: " + time_needed(2, (s"java ${class_name}/${class_name}").!))
 | 
| 220 
141041fc76b5
added
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: diff
changeset | 246 | } | 
| 649 | 247 | */ | 
| 220 
141041fc76b5
added
 Christian Urban <christian dot urban at kcl dot ac dot uk> parents: diff
changeset | 248 | |
| 626 | 249 | // some examples of .fun files | 
| 645 | 250 | //compile_to_file("fact")
 | 
| 251 | //compile_and_run("fact")
 | |
| 252 | //compile_and_run("defs")
 | |
| 253 | ||
| 644 | 254 | |
| 649 | 255 | def main(args: Array[String]) : Unit = | 
| 256 | println(compile(args(0))) | |
| 644 | 257 | |
| 258 | ||
| 259 | } |