/* mybf - My Brainfuck Interpreter

   By James Stanley

   No license - Use as you wish */

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

unsigned char memory[65536];
unsigned char used[8192];
//Globals are guaranteed to be intialised to 0 ;)

int used_mem;
unsigned char bit[8] = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };

void set_used(int pointer) {
  unsigned char byte;
  
  byte = used[pointer/8];
  
  if(!(byte & bit[pointer%8])) used_mem++;
  
  byte |= bit[pointer%8];
  
  used[pointer/8] = byte;
}

int end_of_bracket(char *program, unsigned long int inst_ptr) {
  int brackets = 0;
  
  do {
    if(program[inst_ptr] == '[') brackets++;
    if(program[inst_ptr] == ']') brackets--;
    inst_ptr++;
  } while(brackets > 0);
  
  return inst_ptr;
}

int start_of_bracket(char *program, unsigned long int inst_ptr) {
  int brackets = 0;
  
  do {
    if(program[inst_ptr] == ']') brackets++;
    if(program[inst_ptr] == '[') brackets--;
    inst_ptr--;
  } while(brackets > 0);
  
  return inst_ptr;
}

int main(int argc, char **argv) {
  char *program;
  unsigned short mem_ptr = 0;
  unsigned long int inst_ptr = 0;
  unsigned long int end = 0;
  int n;
  int cycles = 0;
  FILE *prog;
  struct stat prog_stat;
  
  if(argc != 2) {
    fprintf(stderr, "USAGE: %s program\n", argv[0]);
    return 1;
  }
  
  n = stat(argv[1], &prog_stat);
  if(n) {
    fprintf(stderr, "Unable to stat %s.\n", argv[1]);
    return 1;
  }
  
  program = malloc(prog_stat.st_size);//Not all of this is needed, usually
  
  prog = fopen(argv[1], "r");
  if(!prog) {
    fprintf(stderr, "Unable to open %s for reading.\n", argv[1]);
    return 1;
  }
  
  while(!feof(prog)) {
    n = fgetc(prog);
    if(n == '>' || n == '<' || n == '+' || n == '-' || n == '.' || n == ',' || n == '[' || n == ']' || n == '#') {
      program[inst_ptr++] = n;
    }
  }
  
  fclose(prog);
  
  end = inst_ptr;
  inst_ptr = 0;
  
  while(inst_ptr < end) {
    cycles++;
    switch(program[inst_ptr]) {
      case '>':
        mem_ptr++;
        break;
      case '<':
        mem_ptr--;
        break;
      case '+':
        memory[mem_ptr]++;
        set_used(mem_ptr);
        break;
      case '-':
        memory[mem_ptr]--;
        set_used(mem_ptr);
        break;
      case '.':
        fputc(memory[mem_ptr], stdout);
        set_used(mem_ptr);
        break;
      case ',':
        memory[mem_ptr] = fgetc(stdin);
        set_used(mem_ptr);
        break;
      case '[':
        set_used(mem_ptr);
        if(!memory[mem_ptr]) {
          inst_ptr = end_of_bracket(program, inst_ptr) - 1;
        }
        break;
      case ']':
        inst_ptr = start_of_bracket(program, inst_ptr);
        break;
      case '#':
        fprintf(stderr, "mem_ptr = %d\n", mem_ptr);
        for(n = 0; n < 65535; n++) {
          if(memory[n]) {
            fprintf(stderr, "memory[%d] = %d", n, memory[n]);
            if(isprint(memory[n])) fprintf(stderr, " (%c)", memory[n]);
            fputc('\n', stderr);
          }
        }
        break;
    }
    inst_ptr++;
  }
  
  fputc('\n', stdout);
  
  fprintf(stderr, "\nProgram used %d byte(s) of memory.\n", used_mem);
  fprintf(stderr, "Program file was %d byte(s) long.\n", prog_stat.st_size);
  fprintf(stderr, "Program code was %d byte(s) long.\n", end);
  fprintf(stderr, "Program took %d cycle(s) to execute.\n", cycles);
  
  return 0;
}

