| 
     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 }  |