// A Transpiler for the Brainf*** language
//=========================================
//
// This version "optimises" the code by replacing
// for example +++ by (*ptr) += 3, instead of three
// separate (*ptr)++, (*ptr)++, (*ptr)++
//
// Call with
//
// amm bfc1.sc <<bf_program.bf>>
//
// generate "compound" c-instructions
def instr2(c: Char, n: Int) : String = c match {
case '>' => s"ptr += $n ;"
case '<' => s"ptr -= $n ;"
case '+' => s"(*ptr) += $n ;"
case '-' => s"(*ptr) -= $n ;"
case '.' => "putchar(*ptr);" * n
case ',' => "*ptr = getchar(); " * n
case '[' => "while(*ptr){" * n
case ']' => "}" * n
case _ => ""
}
// "splicing" a BF program into "spans"
// and counting the number of occurrences in
// each span; then generate the new intruction
// accordingly
def splice(cs: List[Char], acc: List[String]) : List[String] = cs match {
case Nil => acc
case hd :: _ => {
val (hds, rest) = cs.span(_ == hd)
splice(rest, instr2(hd, hds.length) :: acc)
}
}
def instrs2(prog: String) : String =
splice(prog.toList, Nil).reverse.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);
${instrs2(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")
}