// A Transpiler for the Brainf*** language
//=========================================

import io.Source
import scala.util._


// loding a bf-file 
def load_bff(name: String) : String = 
  Try(Source.fromFile(name)("ISO-8859-1").mkString).getOrElse("")

// "splicing" a BF program counting occurrences
def splice(cs: List[Char], acc: List[(Char, Int)]) : List[(Char, Int)] = (cs, acc) match {
  case (Nil, acc) => acc
  case (c :: cs, Nil) => splice(cs, List((c, 1)))
  case (c :: cs, (d, n) :: acc) => 
    if (c == d) splice(cs, (c, n + 1) :: acc)
    else splice(cs, (c, 1) :: (d, n) :: acc)
}

def spl(s: String) = splice(s.toList, Nil).reverse

// generating "compound" c-instructions 
def instr2(c: Char, n: Int) : String = c match {
  case '>' => "ptr += " + n.toString + ";"
  case '<' => "ptr -= " + n.toString + ";"
  case '+' => "(*ptr) += " + n.toString + ";"
  case '-' => "(*ptr) -= " + n.toString + ";"
  case '.' => "putchar(*ptr);" * n
  case ',' => "*ptr = getchar();\n" * n
  case '['  => "while(*ptr){" * n
  case ']'  => "}" * n
  case _ => ""
}


def instrs2(prog: String) : String =
  spl(prog).map{ case (c, n) => instr2(c, n) }.mkString


def compile_str(prog: String) : String = {
  "#include <string.h>\n" ++
  "#include <stdio.h>\n" ++
  "char field[30000];\n" ++
  "char *ptr = &field[15000];" ++
  "int main()\n{\n" ++
  "memset(field, '\\0', 30000);\n" ++
  instrs2(prog) ++
  "\n return 0;\n}"
}

def compile(name: String, prog: String) = {
  val fw = new java.io.FileWriter(name + ".c") 
  val is = compile_str(prog)
  //println(is)
  fw.write(is) 
  fw.close()
}

import sys.process._

def compile_run(prog: String) = {
  compile("tmp", prog)
  "gcc -O0 -o tmp tmp.c".!
  "./tmp".!
  ()
}

def time_needed[T](n: Int, code: => T) = {
  val start = System.nanoTime()
  for (i <- 0 until n) code
  val end = System.nanoTime()
  (end - start) / (n * 1.0e9)
}

// the mandelbrot program
val b0 = load_bff("mandelbrot.bf")

println(s"${time_needed(1, compile_run(b0))} secs")



// a benchmark program (counts down from 'Z' to 'A')
val b1 = """>++[<+++++++++++++>-]<[[>+>+<<-]>[<+>-]++++++++
            [>++++++++<-]>.[-]<<>++++++++++[>++++++++++[>++
            ++++++++[>++++++++++[>++++++++++[>++++++++++[>+
            +++++++++[-]<-]<-]<-]<-]<-]<-]<-]++++++++++."""

println(s"${time_needed(1, compile_run(b1))} secs")


