// A Transpiler for the Brainf*** language to C
//===============================================
//
// Call with
//
//  amm bfc0.sc <<bf_program.bf>>
//
//
// Note: An interesting exercise is to call
// gcc with -O3 instead of -O0 (see invocation
// below).


// simple instructions
def instr(c: Char) : String = c match {
  case '>' => "ptr++;"
  case '<' => "ptr--;"
  case '+' => "(*ptr)++;"
  case '-' => "(*ptr)--;"
  case '.' => "putchar(*ptr);"
  case ',' => "*ptr = getchar();"
  case '['  => "while(*ptr){"
  case ']'  => "}"
  case _ => ""
}

def instrs(prog: String) : String =
  prog.toList.map(instr(_)).mkString

// adding boilerplate
def compile(prog: String) : String = 
  s"""#include <string.h> 
      #include <stdio.h> 
      int field[30000]; 
      int *ptr = &field[15000]; 
      int main() { 
      memset(field, '\\0', 30000); 
      ${instrs(prog)} 
      return 0;}"""


def compile_to_file(name: String, prog: String) = 
  os.write.over(os.pwd / name, compile(prog))


// running the c-compiler over the transpiled
// BF program and running the resulting binary

def compile_and_run(prog: String) = {
  val tn = "tmp"
  compile_to_file(s"${tn}.c", prog)
  os.proc("gcc", "-O0", "-o", tn, s"${tn}.c").call() // call gcc
  os.proc("./tmp").call(stdout = os.Inherit)         // run binary
}

// Running Testcases
//===================

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

//@doc(" the argument should be a BF program ")
@main
def main(fname: String) = {
  val bf_str = os.read(os.pwd / fname)
  println(s"${time_needed(1, compile_and_run(bf_str))} secs")
}  



