main_testing5/bfc.scala
changeset 463 0315d9983cd0
parent 404 bf20a9fa5c29
child 475 59e005dcf163
equal deleted inserted replaced
462:34feeb53c0ba 463:0315d9983cd0
     1 // Part 2 about a "Compiler" for the Brainf*** language
     1 // Main Part 5 about a "Compiler" for the Brainf*** language
     2 //======================================================
     2 //============================================================
       
     3 
     3 
     4 
     4 object M5b {
     5 object M5b {
     5 
     6 
     6 // !!! Copy any function you need from file bf.scala !!!
     7 // !!! Copy any function you need from file bf.scala !!!
     7 //
     8 //
     8 // If you need any auxiliary function, feel free to 
     9 // If you need any auxiliary function, feel free to 
     9 // implement it, but do not make any changes to the
    10 // implement it, but do not make any changes to the
    10 // templates below.
    11 // templates below.
    11 
    12 
    12 
    13 
       
    14 // DEBUGGING INFORMATION FOR COMPILERS!!!
       
    15 //
       
    16 // Compiler, even real ones, are fiendishly difficult to get
       
    17 // to produce correct code. One way to debug them is to run
       
    18 // example programs ``unoptimised''; and then optimised. Does
       
    19 // the optimised version still produce the same result?
       
    20 
       
    21 
       
    22 // for timing purposes
    13 def time_needed[T](n: Int, code: => T) = {
    23 def time_needed[T](n: Int, code: => T) = {
    14   val start = System.nanoTime()
    24   val start = System.nanoTime()
    15   for (i <- 0 until n) code
    25   for (i <- 0 until n) code
    16   val end = System.nanoTime()
    26   val end = System.nanoTime()
    17   (end - start)/(n * 1.0e9)
    27   (end - start)/(n * 1.0e9)
    18 }
    28 }
    19 
    29 
       
    30 
    20 type Mem = Map[Int, Int]
    31 type Mem = Map[Int, Int]
    21 
       
    22 
    32 
    23 import io.Source
    33 import io.Source
    24 import scala.util._
    34 import scala.util._
    25 
    35 
    26 def load_bff(name: String) : String = 
    36 // ADD YOUR CODE BELOW
    27   Try(Source.fromFile(name)("ISO-8859-1").mkString).getOrElse("")
    37 //======================
    28 
    38 
    29 def sread(mem: Mem, mp: Int) : Int = 
    39 // (6)
    30   mem.getOrElse(mp, 0)
    40 def jumpRight(prog: String, pc: Int, level: Int) : Int = {
       
    41     if (pc >= prog.length) prog.length
       
    42     else if (prog(pc) == '[') jumpRight(prog, pc + 1, level + 1)
       
    43     else if (prog(pc) == ']' && level == 0) pc + 1
       
    44     else if (prog(pc) == ']') jumpRight(prog, pc + 1, level - 1)
       
    45     else jumpRight(prog, pc + 1, level)
       
    46 }
    31 
    47 
    32 def write(mem: Mem, mp: Int, v: Int) : Mem =
    48 def jtable(pg: String) : Map[Int, Int] = {
    33   mem.updated(mp, v)
    49     val pairs = for {
       
    50         i <- 0 until pg.length
       
    51         if pg.charAt(i) == '['
       
    52         j = jumpRight(pg, i+1, 0)
       
    53     } yield (i, j)
       
    54     pairs.flatMap { case (i, j) => 
       
    55         List((i, j), (j-1, i+1))
       
    56     }.toMap
       
    57 }
    34 
    58 
    35 def jumpRight(prog: String, pc: Int, level: Int) : Int = {
    59 def write(mem: Mem, mp: Int, v: Int) : Mem = mem + (mp -> v)
    36   if (prog.length <= pc) pc 
    60 
    37   else (prog(pc), level) match {
    61 // testcase
    38     case (']', 0) => pc + 1
    62 //
    39     case (']', l) => jumpRight(prog, pc + 1, l - 1)
    63 // jtable("""+++++[->++++++++++<]>--<+++[->>++++++++++<<]>>++<<----------[+>.>.<+<]""") 
    40     case ('[', l) => jumpRight(prog, pc + 1, l + 1)
    64 // =>  Map(69 -> 61, 5 -> 20, 60 -> 70, 27 -> 44, 43 -> 28, 19 -> 6)
    41     case (_, l) => jumpRight(prog, pc + 1, l)
    65 
       
    66 def compute2(pg: String, tb: Map[Int, Int], pc: Int, mp: Int, mem: Mem) : Mem = {
       
    67   if (pc >= pg.length) mem
       
    68   else {
       
    69       val (npc, nmp, nmem) = pg(pc) match {
       
    70           case '>' => (pc + 1, mp + 1, mem)
       
    71           case '<' => (pc + 1, mp - 1, mem)
       
    72           case '+' => (pc + 1, mp, mem + (mp -> (mem.getOrElse(mp, 0) + 1)))
       
    73           case '-' => (pc + 1, mp, mem + (mp -> (mem.getOrElse(mp, 0) - 1)))
       
    74           case '.' => {print(mem.getOrElse(mp,0).toChar);(pc + 1, mp, mem)}
       
    75           case '[' => if (mem.getOrElse(mp, 0) == 0) (tb(pc), mp, mem) else (pc + 1, mp, mem)
       
    76           case ']' => if (mem.getOrElse(mp, 0) != 0) (tb(pc), mp, mem) else (pc + 1, mp, mem)
       
    77           case _ => (pc + 1, mp, mem)
       
    78       }
       
    79       compute2(pg, tb, npc, nmp, nmem)
    42   }
    80   }
    43 }
    81 }
    44 
    82 
    45 def jumpLeft(prog: String, pc: Int, level: Int) : Int = {
    83 def run2(pg: String, m: Mem = Map()) = 
    46   if (pc < 0) pc 
    84   compute2(pg, jtable(pg), 0, 0, m)
    47   else (prog(pc), level) match {
    85   
    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 
    86 
    55 def compute(prog: String, pc: Int, mp: Int, mem: Mem) : Mem = {
    87 // testcases
    56   if (0 <= pc && pc < prog.length) { 
    88 // time_needed(1, run2(load_bff("benchmark.bf")))
    57     val (new_pc, new_mp, new_mem) = prog(pc) match {
    89 // time_needed(1, run2(load_bff("sierpinski.bf")))
    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 
       
    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
       
    80 //
       
    81 //time_needed(1, run(load_bff("benchmark.bf")))
       
    82 
    90 
    83 
    91 
    84 
    92 
    85 // DEBUGGING INFORMATION!!!
    93 // (7) 
    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 
       
    95 def optimise(s: String) : String =
       
    96   s.replaceAll("""[^<>+-.,\[\]]""","").replaceAll("""\[-\]""","0")
    94 
    97 
    95 
    98 def compute3(pg: String, tb: Map[Int, Int], pc: Int, mp: Int, mem: Mem) : Mem = {
    96 // (5) Write a function jtable that precomputes the "jump
    99     if (pc >= pg.length) mem
    97 //     table" for a bf-program. This function takes a bf-program 
   100     else {
    98 //     as an argument and Returns a Map[Int, Int]. The 
   101       val (npc, nmp, nmem) = pg(pc) match {
    99 //     purpose of this map is to record the information
   102           case '>' => (pc + 1, mp + 1, mem)
   100 //     that given on the position pc is a '[' or a ']',
   103           case '<' => (pc + 1, mp - 1, mem)
   101 //     then to which pc-position do we need to jump next?
   104           case '+' => (pc + 1, mp, mem + (mp -> (mem.getOrElse(mp, 0) + 1)))
   102 // 
   105           case '-' => (pc + 1, mp, mem + (mp -> (mem.getOrElse(mp, 0) - 1)))
   103 //     For example for the program
   106           case '.' => {print(mem.getOrElse(mp,0).toChar);(pc + 1, mp, mem)}
   104 //    
   107           case '[' => if (mem.getOrElse(mp, 0) == 0) (tb(pc), mp, mem) else (pc + 1, mp, mem)
   105 //       "+++++[->++++++++++<]>--<+++[->>++++++++++<<]>>++<<----------[+>.>.<+<]"
   108           case ']' => if (mem.getOrElse(mp, 0) != 0) (tb(pc), mp, mem) else (pc + 1, mp, mem)
   106 //
   109           case _ => (pc + 1, mp, mem)
   107 //     we obtain the map
   110       }
   108 //
   111       compute3(pg, tb, npc, nmp, nmem)
   109 //       Map(69 -> 61, 5 -> 20, 60 -> 70, 27 -> 44, 43 -> 28, 19 -> 6)
   112     }
   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
       
   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.
       
   119 //
       
   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.
       
   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) }
       
   146        case '['  => 
       
   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 }
   113 }
   156 
   114 
   157 
   115 def run3(pg: String, m: Mem = Map()) = {
   158 def run2(pg: String, m: Mem = Map()) = 
   116   val opt_pg = optimise(pg)
   159   compute2(pg, jtable(pg), 0, 0, m)
   117   val jt = jtable(opt_pg)
   160 
   118   compute3(opt_pg, jt, 0, 0, m)
   161 //time_needed(1, run2(load_bff("benchmark.bf")))
       
   162 
       
   163 
       
   164 
       
   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
       
   167 // [-] by a new command 0. The idea is that the loop [-] just resets the
       
   168 // memory at the current location to 0. In the compute3 and run3 functions
       
   169 // below you implement this command by writing the number 0 to mem(mp), 
       
   170 // that is write(mem, mp, 0). 
       
   171 //
       
   172 // The easiest way to modify a string in this way is to use the regular
       
   173 // expression """[^<>+-.\[\]""", which recognises everything that is 
       
   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 
       
   177 // string "0" standing for the new bf-command.
       
   178 
       
   179 def optimise(s: String) : String = {
       
   180   s.replaceAll("""[^<>+-.\[\]]""","")
       
   181    .replaceAll("""\[-\]""", "0")
       
   182 }
       
   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 '['  => 
       
   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 }
   119 }
   209 
   120 
   210 
   121 
   211 // testcases
   122 // testcases
   212 
   123 //
   213 //println(optimise(load_bff("collatz.bf")))
   124 // optimise(load_bff("benchmark.bf"))          // should have inserted 0's
   214 //optimise(load_bff("benchmark.bf"))          // should have inserted 0's
   125 // optimise(load_bff("mandelbrot.bf")).length  // => 11203
   215 //optimise(load_bff("mandelbrot.bf")).length  // => 11203
   126 // optimise(load_bff("benchmark.bf")).length
   216  
   127 // time_needed(1, run3(load_bff("benchmark.bf")))
   217 //time_needed(1, run3(load_bff("benchmark.bf")))
       
   218 
   128 
   219 
   129 
       
   130 // (8)  
       
   131 def combine(s: String): String = ???
   220 
   132 
   221 // (7)  Write a function combine which replaces sequences
   133 // testcase
   222 // of repated increment and decrement commands by appropriate
   134 // combine(load_bff("benchmark.bf"))
   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.
       
   241 
   135 
   242 def splice(cs: List[Char], acc: List[(Char, Int)]) : List[(Char, Int)] = (cs, acc) match {
   136 def compute4(pg: String, tb: Map[Int, Int], pc: Int, mp: Int, mem: Mem) : Mem = {
   243   case (Nil, acc) => acc  
   137     if (pc >= pg.length) mem
   244   case ('[' :: cs, acc) => splice(cs, ('[', 1) :: acc)
   138     else {
   245   case (']' :: cs, acc) => splice(cs, (']', 1) :: acc)
   139       val (npc, nmp, nmem) = pg(pc) match {
   246   case ('.' :: cs, acc) => splice(cs, ('.', 1) :: acc)
   140           case '>' => (pc + 1, mp + 1, mem)
   247   case ('0' :: cs, acc) => splice(cs, ('0', 1) :: acc)
   141           case '<' => (pc + 1, mp - 1, mem)
   248   case (c :: cs, Nil) => splice(cs, List((c, 1)))
   142           case '+' => (pc + 1, mp, mem + (mp -> (mem.getOrElse(mp, 0) + 1)))
   249   case (c :: cs, (d, n) :: acc) => 
   143           case '-' => (pc + 1, mp, mem + (mp -> (mem.getOrElse(mp, 0) - 1)))
   250     if (c == d && n < 26) splice(cs, (c, n + 1) :: acc)
   144           case '.' => {print(mem.getOrElse(mp,0).toChar);(pc + 1, mp, mem)}
   251     else splice(cs, (c, 1) :: (d, n) :: acc)
   145           case '[' => if (mem.getOrElse(mp, 0) == 0) (tb(pc), mp, mem) else (pc + 1, mp, mem)
       
   146           case ']' => if (mem.getOrElse(mp, 0) != 0) (tb(pc), mp, mem) else (pc + 1, mp, mem)
       
   147           case _ => (pc + 1, mp, mem)
       
   148       }
       
   149       compute3(pg, tb, npc, nmp, nmem)
       
   150     }
   252 }
   151 }
   253 
   152 
   254 def spl(s: String) = splice(s.toList, Nil).reverse
   153 // should call first optimise and then combine on the input string
   255 
   154 //
   256 //spl(load_bff("benchmark.bf"))
   155 def run4(pg: String, m: Mem = Map()) = {
   257 
   156   val co_opt_pg = combine(optimise(pg))
   258 def combine(s: String) : String = {
   157   val jt = jtable(co_opt_pg)
   259   (for ((c, n) <- spl(s)) yield c match {
   158   compute3(co_opt_pg, jt, 0, 0, m)
   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 
       
   269 //combine(load_bff("benchmark.bf"))
       
   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) }
       
   280        case '['  => 
       
   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()) = { 
       
   292   val pg_opt = combine(optimise(pg))
       
   293   compute4(pg_opt, jtable(pg_opt), 0, 0, m)
       
   294 }
   159 }
   295 
   160 
   296 // testcases
   161 // testcases
   297 //println(combine(optimise(load_bff("mandelbrot.bf").drop(123))))
   162 // combine(optimise(load_bff("benchmark.bf"))) // => """>A+B[<A+M>A-A]<A[[....."""
   298 
   163 
   299 //combine(optimise(load_bff("benchmark.bf"))) // => """>A+B[<A+M>A-A]<A[[....."""
   164 // testcases (they should now run much faster)
       
   165 // time_needed(1, run4(load_bff("benchmark.bf")))
       
   166 // time_needed(1, run4(load_bff("sierpinski.bf"))) 
       
   167 // time_needed(1, run4(load_bff("mandelbrot.bf")))
   300 
   168 
   301 //time_needed(1, run4(load_bff("benchmark.bf")))
       
   302 
   169 
   303 //time_needed(1, run(load_bff("sierpinski.bf"))) 
   170 }
   304 //time_needed(1, run4(load_bff("sierpinski.bf"))) 
       
   305 
       
   306 //println(time_needed(1, run4(load_bff("mandelbrot.bf"))))
       
   307 
   171 
   308 
   172 
   309 
   173 
   310 
   174 
   311 
   175 
   312 }
   176 // This template code is subject to copyright 
   313 
   177 // by King's College London, 2022. Do not 
   314 /*
   178 // make the template code public in any shape 
   315 import CW10b._
   179 // or form, and do not exchange it with other 
   316 println(time_needed(1, run(load_bff("collatz.bf"))))
   180 // students under any circumstance.
   317 println(time_needed(1, run2(load_bff("collatz.bf"))))
       
   318 println(time_needed(1, run3(load_bff("collatz.bf"))))
       
   319 println(time_needed(1, run4(load_bff("collatz.bf"))))
       
   320 */