// Arena Brainf*k threaded optimizing interpreter - Copyright 2006 J.L. Bezemer
// You can redistribute this file and/or modify it under
// the terms of the GNU General Public License

// Values are pushed on the stack by push() and popped
// from the stack by pop(). The stackpointer is stored in sp.

stack = mkarray ();                    // this is the stack
sp = 0;                                // this is the stack pointer

int pop () {
  if (sp > 0) {                        // if values on the stack
     n = (stack [--sp]);               // return a value
     global ("stack", "sp");
     return (n);
  } else {                             // else throw an exception
    throw ("Unmatched [");
    return (0);
  }
}

void push (int n) {
  stack [sp++] = n;                    // push a value on the stack
  global ("stack", "sp");
}

// This is a brainf*k compatible input buffer. The interpreter reads one
// character at the time, although the user can fill the entire buffer at
// one go. If the buffer is empty, the user is prompted again.

tib = "";

int key () {                           // if buffer empty prompt user
  if (strlen (tib) == 0) tib = fgets (stdin);
  c = left (tib, 1); tib = right (tib, strlen (tib) - 1); global ("tib");
  return (ord (c) == () ? 0 : ord (c)); // return a character value
}

// These are the code-, parameter-, data- and symbolsegment 
// of the brackf*k interpreter. 'mp' is a pointer to the currently
// accessed memory location

cs = ps = ds = ss = mkarray (0);
mp = 0;

// These are all the brainf*k instructions. The last one was added in order
// to provide a return instruction to the calling program

int m_move (int ip) { mp += ps [ip]; global ("mp"); return (++ip); }
int m_add  (int ip) { ds [mp] += ps [ip]; global ("ds"); return (++ip);}
int m_get  (int ip) { ds [mp] = key (); global ("ds"); return (++ip); }
int m_put  (int ip) { printf ("%s", chr (ds [mp])); return (++ip); }
int p_do   (int ip) { return ((ds [mp] & 255) == 0 ? ps [ip] : ++ip); }
int p_loop (int ip) { return (ps [ip]); }
int p_exit (int ip) { exit (0); return (ip); }

// This function compiles an instruction, no questions asked. All
// segments (code, parameter and symbol) are updated

int compile (fn a, string b, int c, int d) {
   cs [d] = a;
   ss [d] = b;
   ps [d] = c;
   global ("cs", "ps", "ss");
   return (++d);
}

// This function tries to figure out whether the instruction to be
// compiled is actually of the same family as the previous instruction
// that has been compiled. If it is, it just changes the parameter segment
// accordingly, but does not compile a new instruction

int optimize (fn a, string b, int c, int d) {
  if ((d) && (ss [d - 1] == b)) ps [d - 1] += c;
  else d = compile (a, b, c, d);
  global ("ps");
  return (d);
}

// These are the instructions of how to compile the brainf*k operators.
// They contain the brainf*k instruction, the symbolic name, the change
// in the parameter segment and the current program counter. Note not
// all brainf*k instructions can be optimized

int do_next (int pc) { return (optimize (m_move, "move",  1, pc)); }
int do_prev (int pc) { return (optimize (m_move, "move", -1, pc)); }
int do_inc  (int pc) { return (optimize (m_add, "add",  1, pc)); }
int do_dec  (int pc) { return (optimize (m_add, "add", -1, pc)); }
int do_put  (int pc) { return (compile (m_put, "put", 0, pc)); }
int do_get  (int pc) { return (compile (m_get, "get", 0, pc)); }
int do_do   (int pc) { push (pc); return (compile (p_do, "jump0", 0, pc)); }

int do_loop (int pc) {
  ps [(addr = pop ())] = pc + 1;       // patch backlink address
  global ("ps");
  return (compile (p_loop, "jump", addr, pc));
}

// This table matches the brainf*k operators to compilation instructions

symbols = mkstruct (
  ">", do_next, 
  "<", do_prev, 
  "+", do_inc, 
  "-", do_dec, 
  ".", do_put,
  ",", do_get,
  "[", do_do, 
  "]", do_loop 
);

// This one actually compiles a brainf*k program to a serie of Arena calls.

void compile_bf (f) {
  pc = 0;
  while ((char = fgetc (f)) != ()) {   // get a brainf*k keyword
    assemble = struct_get (symbols, char);
    if (assemble != ()) pc = assemble (pc);
  }                                    // compile as long as there is keyword
  compile (p_exit, "exit", 0, pc);     // compile the exit instruction
  if (sp != 0) throw ("Unmatched ]");  // check the stack (all addr compiled)
}

// This function allows you to decompile a compiled brainf*k program

void decompile_bf () {
  for (x = 0; ss [x] != "exit"; x++)
      printf ("[%4d] %-5s (%d)\n", x, ss [x], ps [x]);
}

// Execute the compiled program

void execute_bf () {
  ip = 0;                              // reset instruction pointer
  for (;;) {                           // do this forever
    operator = cs [ip];                // get the next operator
    ip = operator (ip);                // and execute it
  }
}

// This is the main function. It opens the file, compiles the program
// and runs the command

void run (string name, fn do_bf) {
  if ((f = fopen (name, "r")) != ()) {
     try {
        compile_bf (f);                // compile the program
        fclose (f);                    // close the file
        do_bf ();                      // then execute it
     } catch (e) printf ("%s ", e);    // catch any exceptions
  }
}

switch (argc) {
  case (2): run (argv [1], execute_bf); break;
  case (3): run (argv [2], decompile_bf); break;
  default : print ("Usage: bf [d] filename.b\n"); break;
}
