|         |      1 // Part 2 about a "Compiler" for the Brainf*** language | 
|         |      2 //====================================================== | 
|         |      3  | 
|         |      4 //object CW10b { | 
|         |      5  | 
|         |      6 // !!! Copy any function you need from file bf.scala !!! | 
|         |      7 // | 
|         |      8 // If you need any auxiliary function, feel free to  | 
|         |      9 // implement it, but do not make any changes to the | 
|         |     10 // templates below. | 
|         |     11  | 
|         |     12  | 
|         |     13 def time_needed[T](n: Int, code: => T) = { | 
|         |     14   val start = System.nanoTime() | 
|         |     15   for (i <- 0 until n) code | 
|         |     16   val end = System.nanoTime() | 
|         |     17   (end - start)/(n * 1.0e9) | 
|         |     18 } | 
|         |     19  | 
|         |     20 type Mem = Map[Int, Int] | 
|         |     21  | 
|         |     22  | 
|         |     23 import io.Source | 
|         |     24 import scala.util._ | 
|         |     25  | 
|         |     26 def load_bff(name: String) : String =  | 
|         |     27   Try(Source.fromFile(name)("ISO-8859-1").mkString).getOrElse("") | 
|         |     28  | 
|         |     29 def sread(mem: Mem, mp: Int) : Int =  | 
|         |     30   mem.getOrElse(mp, 0) | 
|         |     31  | 
|         |     32 def write(mem: Mem, mp: Int, v: Int) : Mem = | 
|         |     33   mem.updated(mp, v) | 
|         |     34  | 
|         |     35 def jumpRight(prog: String, pc: Int, level: Int) : Int = { | 
|         |     36   if (prog.length <= pc) pc  | 
|         |     37   else (prog(pc), level) match { | 
|         |     38     case (']', 0) => pc + 1 | 
|         |     39     case (']', l) => jumpRight(prog, pc + 1, l - 1) | 
|         |     40     case ('[', l) => jumpRight(prog, pc + 1, l + 1) | 
|         |     41     case (_, l) => jumpRight(prog, pc + 1, l) | 
|         |     42   } | 
|         |     43 } | 
|         |     44  | 
|         |     45 def jumpLeft(prog: String, pc: Int, level: Int) : Int = { | 
|         |     46   if (pc < 0) pc  | 
|         |     47   else (prog(pc), level) match { | 
|         |     48     case ('[', 0) => pc + 1 | 
|         |     49     case ('[', l) => jumpLeft(prog, pc - 1, l - 1) | 
|         |     50     case (']', l) => jumpLeft(prog, pc - 1, l + 1) | 
|         |     51     case (_, l) => jumpLeft(prog, pc - 1, l) | 
|         |     52   } | 
|         |     53 } | 
|         |     54  | 
|         |     55 def compute(prog: String, pc: Int, mp: Int, mem: Mem) : Mem = { | 
|         |     56   if (0 <= pc && pc < prog.length) {  | 
|         |     57     val (new_pc, new_mp, new_mem) = prog(pc) match { | 
|         |     58       case '>' => (pc + 1, mp + 1, mem) | 
|         |     59       case '<' => (pc + 1, mp - 1, mem) | 
|         |     60       case '+' => (pc + 1, mp, write(mem, mp, sread(mem, mp) + 1)) | 
|         |     61       case '-' => (pc + 1, mp, write(mem, mp, sread(mem, mp) - 1)) | 
|         |     62       case '.' => { print(sread(mem, mp).toChar); (pc + 1, mp, mem) } | 
|         |     63       case ',' => (pc + 1, mp, write(mem, mp, Console.in.read().toByte)) | 
|         |     64       case '['  =>  | 
|         |     65 	if (sread(mem, mp) == 0) (jumpRight(prog, pc + 1, 0), mp, mem) else (pc + 1, mp, mem)  | 
|         |     66       case ']'  =>  | 
|         |     67 	if (sread(mem, mp) != 0) (jumpLeft(prog, pc - 1, 0), mp, mem) else (pc + 1, mp, mem)  | 
|         |     68       case _ => (pc + 1, mp, mem) | 
|         |     69     }		      | 
|         |     70     compute(prog, new_pc, new_mp, new_mem)	 | 
|         |     71   } | 
|         |     72   else mem | 
|         |     73 } | 
|         |     74  | 
|         |     75 def run(prog: String, m: Mem = Map()) = compute(prog, 0, 0, m) | 
|         |     76  | 
|         |     77  | 
|         |     78 // The baseline to what we can compare our "compiler" | 
|         |     79 // implemented below. It should require something like  | 
|         |     80 // 60 seconds for the calculation on my laptop | 
|         |     81 // | 
|         |     82 //time_needed(1, run(load_bff("benchmark.bf"))) | 
|         |     83  | 
|         |     84  | 
|         |     85  | 
|         |     86 // DEBUGGING INFORMATION!!! | 
|         |     87 // | 
|         |     88 // Compiler, even real ones, are fiedishly difficult to get | 
|         |     89 // to prduce correct code. The point is that for example for | 
|         |     90 // the sierpinski program, they need to still generate code | 
|         |     91 // that displays such a triangle. If yes, then one usually | 
|         |     92 // can take comfort that all is well. If not, then something | 
|         |     93 // went wrong during the optimisations. | 
|         |     94  | 
|         |     95  | 
|         |     96  | 
|         |     97 // (5) Write a function jtable that precomputes the "jump | 
|         |     98 //     table" for a bf-program. This function takes a bf-program  | 
|         |     99 //     as an argument and Returns a Map[Int, Int]. The  | 
|         |    100 //     purpose of this map is to record the information | 
|         |    101 //     that given on the position pc is a '[' or a ']', | 
|         |    102 //     then to which pc-position do we need to jump next? | 
|         |    103 //  | 
|         |    104 //     For example for the program | 
|         |    105 //     | 
|         |    106 //       "+++++[->++++++++++<]>--<+++[->>++++++++++<<]>>++<<----------[+>.>.<+<]" | 
|         |    107 // | 
|         |    108 //     we obtain the map | 
|         |    109 // | 
|         |    110 //       Map(69 -> 61, 5 -> 20, 60 -> 70, 27 -> 44, 43 -> 28, 19 -> 6) | 
|         |    111 //   | 
|         |    112 //     This states that for the '[' on position 5, we need to | 
|         |    113 //     jump to position 20, which is just after the corresponding ']'. | 
|         |    114 //     Similarly, for the ']' on position 19, we need to jump to | 
|         |    115 //     position 6, which is just after the '[' on position 5, and so | 
|         |    116 //     on. The idea is to not calculate this information each time | 
|         |    117 //     we hit a bracket, but just look up this information in the  | 
|         |    118 //     jtable. You can use the jumpLeft and jumpRight functions | 
|         |    119 //     from Part 1 for calculating the jtable. | 
|         |    120 // | 
|         |    121 //     Then adapt the compute and run functions from Part 1 in order  | 
|         |    122 //     to take advantage of the information stored in the jtable.  | 
|         |    123 //     This means whenever jumpLeft and jumpRight was called previously, | 
|         |    124 //     you should look up the jump address in the jtable. | 
|         |    125   | 
|         |    126  | 
|         |    127 def jtable(pg: String) : Map[Int, Int] =  | 
|         |    128     (0 until pg.length).collect { pc => pg(pc) match { | 
|         |    129       case '[' => (pc -> jumpRight(pg, pc + 1, 0)) | 
|         |    130       case ']' => (pc -> jumpLeft(pg, pc - 1, 0)) | 
|         |    131     }}.toMap | 
|         |    132  | 
|         |    133  | 
|         |    134 // testcase | 
|         |    135 // jtable("""+++++[->++++++++++<]>--<+++[->>++++++++++<<]>>++<<----------[+>.>.<+<]""") | 
|         |    136 // =>  Map(69 -> 61, 5 -> 20, 60 -> 70, 27 -> 44, 43 -> 28, 19 -> 6) | 
|         |    137  | 
|         |    138  | 
|         |    139 def compute2(pg: String, tb: Map[Int, Int], pc: Int, mp: Int, mem: Mem) : Mem = { | 
|         |    140   if (0 <= pc && pc < pg.length) {  | 
|         |    141     val (new_pc, new_mp, new_mem) = pg(pc) match { | 
|         |    142       case '>' => (pc + 1, mp + 1, mem) | 
|         |    143       case '<' => (pc + 1, mp - 1, mem) | 
|         |    144       case '+' => (pc + 1, mp, write(mem, mp, sread(mem, mp) + 1)) | 
|         |    145       case '-' => (pc + 1, mp, write(mem, mp, sread(mem, mp) - 1)) | 
|         |    146       case '.' => { print(sread(mem, mp).toChar); (pc + 1, mp, mem) } | 
|         |    147       case ',' => (pc + 1, mp, write(mem, mp, Console.in.read().toByte)) | 
|         |    148       case '['  =>  | 
|         |    149 	if (sread(mem, mp) == 0) (tb(pc), mp, mem) else (pc + 1, mp, mem)  | 
|         |    150       case ']'  =>  | 
|         |    151 	if (sread(mem, mp) != 0) (tb(pc), mp, mem) else (pc + 1, mp, mem)  | 
|         |    152       case _ => (pc + 1, mp, mem) | 
|         |    153     }		      | 
|         |    154     compute2(pg, tb, new_pc, new_mp, new_mem)	 | 
|         |    155   } | 
|         |    156   else mem | 
|         |    157 } | 
|         |    158  | 
|         |    159  | 
|         |    160 def run2(pg: String, m: Mem = Map()) =  | 
|         |    161   compute2(pg, jtable(pg), 0, 0, m) | 
|         |    162  | 
|         |    163 //time_needed(1, run2(load_bff("benchmark.bf"))) | 
|         |    164  | 
|         |    165  | 
|         |    166  | 
|         |    167 // (6) Write a function optimise which deletes "dead code" (everything | 
|         |    168 // that is not a bf-command) and also replaces substrings of the form | 
|         |    169 // [-] by a new command 0. The idea is that the loop [-] just resets the | 
|         |    170 // memory at the current location to 0. In the compute3 and run3 functions | 
|         |    171 // below you implement this command by writing the number 0 to mem(mp),  | 
|         |    172 // that is write(mem, mp, 0).  | 
|         |    173 // | 
|         |    174 // The easiest way to modify a string in this way is to use the regular | 
|         |    175 // expression """[^<>+-.,\[\]]""", which recognises everything that is  | 
|         |    176 // not a bf-command and replace it by the empty string. Similarly the | 
|         |    177 // regular expression """\[-\]""" finds all occurences of [-] and  | 
|         |    178 // by using the Scala method .replaceAll you can repplace it with the  | 
|         |    179 // string "0" standing for the new bf-command. | 
|         |    180  | 
|         |    181 def optimise(s: String) : String =  | 
|         |    182   s.replaceAll("""[^<>+-.,\[\]]""","").replaceAll("""\[-\]""", "0") | 
|         |    183  | 
|         |    184  | 
|         |    185 def compute3(pg: String, tb: Map[Int, Int], pc: Int, mp: Int, mem: Mem) : Mem = { | 
|         |    186   if (0 <= pc && pc < pg.length) {  | 
|         |    187     val (new_pc, new_mp, new_mem) = pg(pc) match { | 
|         |    188       case '0' => (pc + 1, mp, write(mem, mp, 0)) | 
|         |    189       case '>' => (pc + 1, mp + 1, mem) | 
|         |    190       case '<' => (pc + 1, mp - 1, mem) | 
|         |    191       case '+' => (pc + 1, mp, write(mem, mp, sread(mem, mp) + 1)) | 
|         |    192       case '-' => (pc + 1, mp, write(mem, mp, sread(mem, mp) - 1)) | 
|         |    193       case '.' => { print(sread(mem, mp).toChar); (pc + 1, mp, mem) } | 
|         |    194       case ',' => (pc + 1, mp, write(mem, mp, Console.in.read().toByte)) | 
|         |    195       case '['  =>  | 
|         |    196 	if (sread(mem, mp) == 0) (tb(pc), mp, mem) else (pc + 1, mp, mem)  | 
|         |    197       case ']'  =>  | 
|         |    198 	if (sread(mem, mp) != 0) (tb(pc), mp, mem) else (pc + 1, mp, mem)  | 
|         |    199       case _ => (pc + 1, mp, mem) | 
|         |    200     }		      | 
|         |    201     compute3(pg, tb, new_pc, new_mp, new_mem)	 | 
|         |    202   } | 
|         |    203   else mem | 
|         |    204 } | 
|         |    205  | 
|         |    206 def run3(pg: String, m: Mem = Map()) = {  | 
|         |    207   val pg_opt = optimise(pg) | 
|         |    208   compute3(pg_opt, jtable(pg_opt), 0, 0, m) | 
|         |    209 } | 
|         |    210  | 
|         |    211  | 
|         |    212 // testcases | 
|         |    213  | 
|         |    214 //optimise(load_bff("benchmark.bf"))          // should have inserted 0's | 
|         |    215 //optimise(load_bff("benchmark.bf")).length   // => 181   | 
|         |    216 //optimise(load_bff("mandelbrot.bf")).length  // => 11203 | 
|         |    217   | 
|         |    218 //time_needed(1, run3(load_bff("benchmark.bf"))) | 
|         |    219  | 
|         |    220  | 
|         |    221  | 
|         |    222 // (7)  Write a function combine which replaces sequences | 
|         |    223 // of repated increment and decrement commands by appropriate | 
|         |    224 // two-character commands. For example for sequences of + | 
|         |    225 // | 
|         |    226 //              orig bf-cmds  | replacement | 
|         |    227 //            ------------------------------ | 
|         |    228 //              +             | +A  | 
|         |    229 //              ++            | +B | 
|         |    230 //              +++           | +C | 
|         |    231 //                            | | 
|         |    232 //              ...           | | 
|         |    233 //                            |  | 
|         |    234 //              +++....+++    | +Z | 
|         |    235 //                (where length = 26) | 
|         |    236 // | 
|         |    237 //  Similar for the bf-command -, > and <. All other commands should | 
|         |    238 //  be unaffected by this change. | 
|         |    239 // | 
|         |    240 //  Adapt the compute4 and run4 functions such that they can deal | 
|         |    241 //  appropriately with such two-character commands. | 
|         |    242  | 
|         |    243 def splice(cs: List[Char], acc: List[(Char, Int)]) : List[(Char, Int)] = (cs, acc) match { | 
|         |    244   case (Nil, acc) => acc   | 
|         |    245   case ('[' :: cs, acc) => splice(cs, ('[', 1) :: acc) | 
|         |    246   case (']' :: cs, acc) => splice(cs, (']', 1) :: acc) | 
|         |    247   case ('.' :: cs, acc) => splice(cs, ('.', 1) :: acc) | 
|         |    248   case (',' :: cs, acc) => splice(cs, (',', 1) :: acc) | 
|         |    249   case ('0' :: cs, acc) => splice(cs, ('0', 1) :: acc) | 
|         |    250   case (c :: cs, Nil) => splice(cs, List((c, 1))) | 
|         |    251   case (c :: cs, (d, n) :: acc) =>  | 
|         |    252     if (c == d && n < 26) splice(cs, (c, n + 1) :: acc) | 
|         |    253     else splice(cs, (c, 1) :: (d, n) :: acc) | 
|         |    254 } | 
|         |    255  | 
|         |    256 def spl(s: String) = splice(s.toList, Nil).reverse | 
|         |    257  | 
|         |    258 //spl(load_bff("benchmark.bf")) | 
|         |    259  | 
|         |    260 def combine(s: String) : String = { | 
|         |    261   (for ((c, n) <- spl(s)) yield c match { | 
|         |    262     case '>' => List('>', (n + '@').toChar) | 
|         |    263     case '<' => List('<', (n + '@').toChar) | 
|         |    264     case '+' => List('+', (n + '@').toChar) | 
|         |    265     case '-' => List('-', (n + '@').toChar) | 
|         |    266     case _ => List(c) | 
|         |    267   }).flatten.mkString | 
|         |    268 } | 
|         |    269  | 
|         |    270  | 
|         |    271 //combine(load_bff("benchmark.bf")) | 
|         |    272  | 
|         |    273 def compute4(pg: String, tb: Map[Int, Int], pc: Int, mp: Int, mem: Mem) : Mem = { | 
|         |    274   if (0 <= pc && pc < pg.length) {  | 
|         |    275     val (new_pc, new_mp, new_mem) = pg(pc) match { | 
|         |    276       case '0' => (pc + 1, mp, write(mem, mp, 0)) | 
|         |    277       case '>' => (pc + 2, mp + (pg(pc + 1) - '@'), mem) | 
|         |    278       case '<' => (pc + 2, mp - (pg(pc + 1) - '@'), mem) | 
|         |    279       case '+' => (pc + 2, mp, write(mem, mp, sread(mem, mp) + (pg(pc + 1) - '@'))) | 
|         |    280       case '-' => (pc + 2, mp, write(mem, mp, sread(mem, mp) - (pg(pc + 1) - '@'))) | 
|         |    281       case '.' => { print(sread(mem, mp).toChar); (pc + 1, mp, mem) } | 
|         |    282       case ',' => (pc + 1, mp, write(mem, mp, Console.in.read().toByte)) | 
|         |    283       case '['  =>  | 
|         |    284 	if (sread(mem, mp) == 0) (tb(pc), mp, mem) else (pc + 1, mp, mem)  | 
|         |    285       case ']'  =>  | 
|         |    286 	if (sread(mem, mp) != 0) (tb(pc), mp, mem) else (pc + 1, mp, mem)  | 
|         |    287       case _ => (pc + 1, mp, mem) | 
|         |    288     }		      | 
|         |    289     compute4(pg, tb, new_pc, new_mp, new_mem)	 | 
|         |    290   } | 
|         |    291   else mem | 
|         |    292 } | 
|         |    293  | 
|         |    294 def run4(pg: String, m: Mem = Map()) = {  | 
|         |    295   val pg_opt = combine(optimise(pg)) | 
|         |    296   compute4(pg_opt, jtable(pg_opt), 0, 0, m) | 
|         |    297 } | 
|         |    298  | 
|         |    299 // testcases | 
|         |    300 //combine(optimise(load_bff("benchmark.bf"))) // => """>A+B[<A+M>A-A]<A[[.....""" | 
|         |    301 //combine(optimise(load_bff("benchmark.bf"))).length // => 134 | 
|         |    302 //combine(optimise(load_bff("mandelbrot.bf"))).length // => 6509 | 
|         |    303  | 
|         |    304 //time_needed(1, run4(load_bff("benchmark.bf"))) | 
|         |    305  | 
|         |    306 //time_needed(1, run(load_bff("sierpinski.bf")))  | 
|         |    307 //time_needed(1, run4(load_bff("sierpinski.bf")))  | 
|         |    308  | 
|         |    309 //time_needed(1, run4(load_bff("mandelbrot.bf"))) | 
|         |    310  | 
|         |    311  | 
|         |    312 //} |