|      1 // Some more interesting testcases for the  |         | 
|      2 // WHILE compiler with arrays, it includes:  |         | 
|      3 // |         | 
|      4 //  - a small parser for WHILE programs |         | 
|      5 // |         | 
|      6 //  - transpiles BF programs into WHILE programs |         | 
|      7 //    and then compiles and runs them |         | 
|      8 // |         | 
|      9 //  - the power-example is the Mandelbrot set: |         | 
|     10 //        |         | 
|     11 //       * 65k for the transpiled WHILE program  |         | 
|     12 //       * parsing uses around 30 secs using fastparse |         | 
|     13 //       * the jasmin assembly file is 236k |         | 
|     14 //       * the resulting Java program takes about 20 secs  |         | 
|     15 // |         | 
|     16 // Call with (X being 0,1,..,4) |         | 
|     17 // |         | 
|     18 //  amm compile_bfc.sc all |         | 
|     19 //  amm compile_bfc.sc bfcX |         | 
|     20  |         | 
|     21  |         | 
|     22 // load the compiler |         | 
|     23 import $file.compile_arrays |         | 
|     24 import compile_arrays._  |         | 
|     25  |         | 
|     26 def time_needed[T](i: Int, code: => T) = { |         | 
|     27   val start = System.nanoTime() |         | 
|     28   for (j <- 2 to i) code |         | 
|     29   val result = code |         | 
|     30   val end = System.nanoTime() |         | 
|     31   ((end - start) / (i * 1.0e9), result) |         | 
|     32 } |         | 
|     33  |         | 
|     34 // for BF we have to change the write library-function, |         | 
|     35 // because in BF integers are written out as characters |         | 
|     36 val beginning = """ |         | 
|     37 .class public XXX.XXX |         | 
|     38 .super java/lang/Object |         | 
|     39  |         | 
|     40 .method public static write(I)V  |         | 
|     41     .limit locals 1  |         | 
|     42     .limit stack 2  |         | 
|     43     getstatic java/lang/System/out Ljava/io/PrintStream;  |         | 
|     44     iload 0 |         | 
|     45     i2c                                           ; Int => Char |         | 
|     46     invokevirtual java/io/PrintStream/print(C)V   ; println(I)V => print(C)V     |         | 
|     47     return  |         | 
|     48 .end method |         | 
|     49  |         | 
|     50 .method public static main([Ljava/lang/String;)V |         | 
|     51    .limit locals 200 |         | 
|     52    .limit stack 200 |         | 
|     53  |         | 
|     54 ; COMPILED CODE STARTS    |         | 
|     55  |         | 
|     56 """ |         | 
|     57  |         | 
|     58  |         | 
|     59 // modified main compilation function for blocks |         | 
|     60 def compile(bl: Block, class_name: String) : String = { |         | 
|     61   val instructions = compile_block(bl, Map())._1 |         | 
|     62   (beginning ++ instructions ++ ending).replace("XXX", class_name) |         | 
|     63 } |         | 
|     64  |         | 
|     65 // automating the above |         | 
|     66 import ammonite.ops._ |         | 
|     67  |         | 
|     68 def compile_to_file(bl: Block, class_name: String) : Unit =  |         | 
|     69   write.over(pwd / s"$class_name.j", compile(bl, class_name))   |         | 
|     70  |         | 
|     71 def compile_and_run(bl: Block, class_name: String) : Unit = { |         | 
|     72   println(s"Start of compilation") |         | 
|     73   compile_to_file(bl, class_name) |         | 
|     74   println(s"generated $class_name.j file") |         | 
|     75   val (jasmin_time, _) =  |         | 
|     76     time_needed(1, os.proc("java", "-jar", "jasmin.jar", s"$class_name.j").call()) |         | 
|     77   println(s"generated $class_name.class file (in $jasmin_time secs).") |         | 
|     78   val (running_time, output) =  |         | 
|     79     time_needed(1, os.proc("java", s"${class_name}/${class_name}").call().out.text()) |         | 
|     80   println(output) |         | 
|     81   println(s"done (in $running_time secs).") |         | 
|     82 } |         | 
|     83  |         | 
|     84  |         | 
|     85 //===================================== |         | 
|     86 // Grammar Rules for WHILE with arrays |         | 
|     87 //===================================== |         | 
|     88  |         | 
|     89 import fastparse._ |         | 
|     90 import MultiLineWhitespace._ |         | 
|     91  |         | 
|     92 def lowercase [_ : P] = P( CharIn("a-z") ) |         | 
|     93 def uppercase[_ : P]  = P( CharIn("A-Z") ) |         | 
|     94 def letter[_ : P]     = P( lowercase | uppercase ) |         | 
|     95 def digit [_ : P]     = P( CharIn("0-9") ) |         | 
|     96  |         | 
|     97 def Number[_ : P]: P[Int] =  P( digit.rep(1) ).!.map(_.toInt) |         | 
|     98 def Ident[_ : P]: P[String] = P( letter ~ (letter | digit | "_").rep ).! |         | 
|     99  |         | 
|    100 // arithmetic expressions |         | 
|    101 def AExp[_ : P]: P[AExp] =  |         | 
|    102   P(  P(Te ~ "+" ~ AExp).map{ case (l, r) => Aop("+", l, r)}  |         | 
|    103     | P(Te ~ "-" ~ AExp).map{ case (l, r) => Aop("-", l, r)} |         | 
|    104     | Te ) |         | 
|    105 def Te[_ : P]: P[AExp] =  |         | 
|    106   P(  P(Fa ~ "*" ~ Te).map{ case (l, r) => Aop("*", l, r)}  |         | 
|    107     | Fa )    |         | 
|    108 def Fa[_ : P]: P[AExp] =  |         | 
|    109   P( "(" ~ AExp ~ ")"  |         | 
|    110      | P (Ident ~ "[" ~ AExp ~ "]").map{Ref.tupled} |         | 
|    111      | P(Number).map{Num}  |         | 
|    112      | P(Ident).map{Var} ) |         | 
|    113  |         | 
|    114 // boolean expressions |         | 
|    115 def BExp[_ : P]: P[BExp] =  |         | 
|    116   P(  P(AExp ~ "=" ~ AExp).map{ case (x, z) => Bop("=", x, z)}  |         | 
|    117     | P(AExp ~ "!=" ~ AExp).map{ case (x, z) => Bop("!=", x, z)}   |         | 
|    118     | P(AExp ~ "<" ~ AExp).map{ case (x, z) => Bop("<", x, z)}   |         | 
|    119     | P(AExp ~ ">" ~ AExp).map{ case (x, z) => Bop("<", z, x)}   |         | 
|    120     | P("true").map{ _ => True}  |         | 
|    121     | P("false").map{ _ => False}  |         | 
|    122     | "(" ~ BExp ~ ")" ) |         | 
|    123  |         | 
|    124 // statements and blocks |         | 
|    125 def Stmt[_ : P]: P[Stmt] = |         | 
|    126   P(  P("skip").map( _ => Skip)  |         | 
|    127     | P(Ident ~ ":=" ~ AExp).map{Assign.tupled}  |         | 
|    128     | P(Ident ~ "[" ~ AExp ~ "]" ~ ":=" ~ AExp).map{AssignA.tupled}  |         | 
|    129     | P("if" ~ BExp ~ "then" ~ Block ~ "else" ~ Block).map{If.tupled}  |         | 
|    130     | P("while" ~ BExp ~ "do" ~ Block).map{While.tupled}  |         | 
|    131     | P("new(" ~ Ident ~ "[" ~ Number ~ "])").map{ArrayDef.tupled}  |         | 
|    132     | P("write(" ~ Ident ~ ")").map{Write} )  |         | 
|    133  |         | 
|    134 def Stmts[_ : P]: P[Block] = |         | 
|    135   P(  P(Stmt ~ ";" ~ Stmts).map{ case (x, z) => x :: z }  |         | 
|    136     | P(Stmt).map{s => List(s)} )  |         | 
|    137  |         | 
|    138 def Block[_ : P]: P[Block] = |         | 
|    139   P(  "{" ~ Stmts ~ "}"  |         | 
|    140     | P(Stmt).map(s => List(s)) ) |         | 
|    141  |         | 
|    142 // some test cases for the parser |         | 
|    143  |         | 
|    144 //println(fastparse.parse("(1 + (2 + 5)) + 4", AExp(_)).get) |         | 
|    145 //println(fastparse.parse("1 + 2 + 3 + 45", AExp(_))) |         | 
|    146 //println(fastparse.parse("1 + 2 * 3", AExp(_))) |         | 
|    147 //println(fastparse.parse("x + 2 * 3", AExp(_))) |         | 
|    148 //println(fastparse.parse("x2 := 5 + a", Stmts(_))) |         | 
|    149 //println(fastparse.parse("x2 := 5 + a[3+a]", Stmts(_))) |         | 
|    150 //println(fastparse.parse("a[x2+3] := 5 + a[3+a]", Stmts(_))) |         | 
|    151 //println(fastparse.parse("{x := 5; y := 8}", Block(_))) |         | 
|    152 //println(fastparse.parse("if (false) then {x := 5} else {x := 10}", Block(_))) |         | 
|    153  |         | 
|    154 val fib =  |         | 
|    155  """n := 10; |         | 
|    156     minus1 := 0; |         | 
|    157     minus2 := 1; |         | 
|    158     temp:=0; |         | 
|    159     while (n > 0) do { |         | 
|    160       temp := minus2;  |         | 
|    161       minus2 := minus1 + minus2; |         | 
|    162       minus1 := temp; |         | 
|    163       n := n - 1}; |         | 
|    164     result := minus2; |         | 
|    165     write(result) |         | 
|    166    """ |         | 
|    167  |         | 
|    168 //println(fastparse.parse(fib, Stmts(_)).get.value) |         | 
|    169  |         | 
|    170  |         | 
|    171  |         | 
|    172 //====================================== |         | 
|    173 // BF transpiler into WHILE with arrays |         | 
|    174 //====================================== |         | 
|    175  |         | 
|    176 // simple BF instructions translation |         | 
|    177 def instr(c: Char) : String = c match { |         | 
|    178   case '>' => "ptr := ptr + 1;" |         | 
|    179   case '<' => "ptr := ptr - 1;" |         | 
|    180   case '+' => "mem[ptr] := mem[ptr] + 1;" |         | 
|    181   case '-' => "mem[ptr] := mem[ptr] - 1;" |         | 
|    182   case '.' => "x := mem[ptr]; write x;" |         | 
|    183   //case ',' => "XXX" // "ptr = getchar();\n" |         | 
|    184   case '['  => "while (mem[ptr] != 0) do {" |         | 
|    185   case ']'  => "skip};" |         | 
|    186   case _ => "" |         | 
|    187 } |         | 
|    188  |         | 
|    189 def instrs(prog: String) : String = |         | 
|    190   prog.toList.map(instr).mkString |         | 
|    191  |         | 
|    192  |         | 
|    193 // Note: Unfortunately, the transpiled mandelbrot.bf program  |         | 
|    194 // is so large that it does not fit inside the limitations of  |         | 
|    195 // what the JVM imposes on methods (only 64K of instructions). |         | 
|    196 // Therefore some optimisations are first applied to  |         | 
|    197 // BF programs before WHILE programs are created. The |         | 
|    198 // optimisations are  |         | 
|    199 //   |         | 
|    200 //  - replacing BF-loops of the form [-] with a new 0-instruction  |         | 
|    201 //  - combining single increment/decrement instructions |         | 
|    202 // |         | 
|    203 // The size of the resulting .j-file is 270K. |         | 
|    204  |         | 
|    205  |         | 
|    206 def splice(cs: List[Char], acc: List[(Char, Int)]) : List[(Char, Int)] = (cs, acc) match { |         | 
|    207   case (Nil, acc) => acc |         | 
|    208   case (c :: cs, Nil) => splice(cs, List((c, 1))) |         | 
|    209   case (c :: cs, (d, n) :: acc) =>  |         | 
|    210     if (c == d) splice(cs, (c, n + 1) :: acc) |         | 
|    211     else splice(cs, (c, 1) :: (d, n) :: acc) |         | 
|    212 } |         | 
|    213  |         | 
|    214 def spl(s: String) = splice(s.toList, Nil).reverse |         | 
|    215  |         | 
|    216 def instr2(c: Char, n: Int) : String = c match { |         | 
|    217   case '>' => s"ptr := ptr + $n;" |         | 
|    218   case '<' => s"ptr := ptr - $n;" |         | 
|    219   case '0' => s"mem[ptr] := 0;" |         | 
|    220   case '+' => s"mem[ptr] := mem[ptr] + $n;" |         | 
|    221   case '-' => s"mem[ptr] := mem[ptr] - $n;" |         | 
|    222   case '.' => s"x := mem[ptr]; write(x);"  |         | 
|    223   case '['  => "while (mem[ptr] != 0) do {" * n  |         | 
|    224   case ']'  => "skip};" * n |         | 
|    225   case _ => "" |         | 
|    226 } |         | 
|    227  |         | 
|    228 def instrs2(prog: String) : String = |         | 
|    229   spl(prog.replaceAll("""\[-\]""", "0")).map{ case (c, n) => instr2(c, n) }.mkString |         | 
|    230  |         | 
|    231 // adding the "header" to the BF program |         | 
|    232 def bf_str(prog: String) : String = { |         | 
|    233   "new(mem[30000]);" ++ |         | 
|    234   "ptr := 15000;" ++ |         | 
|    235   instrs2(prog) ++ |         | 
|    236   "skip" |         | 
|    237 } |         | 
|    238  |         | 
|    239  |         | 
|    240 def bf_run(prog: String, name: String) = { |         | 
|    241   println(s"BF pre-processing of $name") |         | 
|    242   val bf_string = bf_str(prog) |         | 
|    243   println(s"BF parsing (program length ${bf_string.length} characters)") |         | 
|    244   val (time, bf_prog) =  |         | 
|    245     time_needed(1, fastparse.parse(bf_string, Stmts(_)).get.value) |         | 
|    246   println(s"BF generated WHILE program (needed $time secs for parsing)") |         | 
|    247   compile_and_run(bf_prog, name) |         | 
|    248 } |         | 
|    249  |         | 
|    250 // a benchmark program (counts down from 'Z' to 'A') |         | 
|    251 @doc(" Benchmark 'Z' to 'A'.") |         | 
|    252 @main |         | 
|    253 def bfc0() = bf_run(read(pwd / "benchmark.bf"), "bench") |         | 
|    254  |         | 
|    255  |         | 
|    256 @doc(" Sierpinski triangle.") |         | 
|    257 @main |         | 
|    258 def bfc1() = bf_run(read(pwd / "sierpinski.bf"), "sier") |         | 
|    259  |         | 
|    260 // Hello World |         | 
|    261 val bf2 = """++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-] |         | 
|    262       >>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.""" |         | 
|    263  |         | 
|    264 @doc(" Hello world.") |         | 
|    265 @main |         | 
|    266 def bfc2() = bf_run(bf2, "hello") |         | 
|    267  |         | 
|    268 // Fibonacci  |         | 
|    269 val bf3 = """+++++++++++ |         | 
|    270       >+>>>>++++++++++++++++++++++++++++++++++++++++++++ |         | 
|    271       >++++++++++++++++++++++++++++++++<<<<<<[>[>>>>>>+> |         | 
|    272       +<<<<<<<-]>>>>>>>[<<<<<<<+>>>>>>>-]<[>++++++++++[- |         | 
|    273       <-[>>+>+<<<-]>>>[<<<+>>>-]+<[>[-]<[-]]>[<<[>>>+<<< |         | 
|    274       -]>>[-]]<<]>>>[>>+>+<<<-]>>>[<<<+>>>-]+<[>[-]<[-]] |         | 
|    275       >[<<+>>[-]]<<<<<<<]>>>>>[+++++++++++++++++++++++++ |         | 
|    276       +++++++++++++++++++++++.[-]]++++++++++<[->-<]>++++ |         | 
|    277       ++++++++++++++++++++++++++++++++++++++++++++.[-]<< |         | 
|    278       <<<<<<<<<<[>>>+>+<<<<-]>>>>[<<<<+>>>>-]<-[>>.>.<<< |         | 
|    279       [-]]<<[>>+>+<<<-]>>>[<<<+>>>-]<<[<+>-]>[<+>-]<<<-] |         | 
|    280       [-]++++++++++.""" |         | 
|    281  |         | 
|    282 @doc(" Fibonacci numbers.") |         | 
|    283 @main |         | 
|    284 def bfc3() = bf_run(bf3, "fibs") |         | 
|    285  |         | 
|    286 // Mandelbrot Set |         | 
|    287 //---------------- |         | 
|    288 // |         | 
|    289 // Note: Parsing of the generated WHILE program (around 60K in size) |         | 
|    290 // takes approximately 10 minutes to parse with our parser combinators, |         | 
|    291 // and approximately 30 seconds with Ammonite's fastparse. |         | 
|    292  |         | 
|    293 @doc(" Mandelbrot set.") |         | 
|    294 @main |         | 
|    295 def bfc4() = bf_run(read(pwd / "mandelbrot.bf"), "mandelbrot") |         | 
|    296  |         | 
|    297  |         | 
|    298 // this unfortunately hits the capacity of the JVM, even with optimisations |         | 
|    299 //@doc(" Coolatz serries up to 30.") |         | 
|    300 //@main |         | 
|    301 //def bfc5() = bf_run(read(pwd / "collatz.bf"), "coll") |         | 
|    302  |         | 
|    303  |         | 
|    304 // |         | 
|    305 @doc(" All benchmarks.") |         | 
|    306 @main |         | 
|    307 def all() = { bfc0(); bfc1(); bfc2(); bfc3(); bfc4() }  |         | 
|    308  |         | 
|    309  |         | 
|    310  |         |