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