/*
 * brainfuck.c
 * Copyright (C) 2006 Erik Scharwaechter <diozaka@gmx.de>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

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

#define BUF_SIZE 32768

char buf[BUF_SIZE];
size_t p;
char *code;


/* read the given file and save its contents to 'code' */
void readfile(char *filename);

/* interpret the given range */
void interpret(size_t start, size_t end);

/* calculate the width of the loop starting at 'start' */
size_t loop_width(size_t start, size_t end);


int main(int argc, char** argv)
{
	if (argc!=2) {
		printf("%s", "Copyright (C) 2006 Erik Scharwaechter\n");
		printf("%s", "This is free software with ABSOLUTELY NO WARRANTY.\n\n");
		printf("%s", "USAGE: brainfuck FILENAME\n");
		return 1;
	}

	readfile(argv[1]);

	memset(buf, 0, sizeof(buf));
	p=0;
	interpret(0, strlen(code)-1);

	free(code);
	return 0;
}


void readfile(char *filename)
{
	struct stat info;
	FILE *f;

	f = fopen(filename, "r");
	if (f==NULL) {
		printf("Couldn't fopen() %s: %s\n", filename, strerror(errno));
		exit(1);
	}

	memset(&info, 0, sizeof(info));
	if (fstat(fileno(f), &info)==-1) {
		printf("Couldn't fstat() %s: %s\n", filename, strerror(errno));
		exit(1);
	}

	code = calloc(info.st_size+1, sizeof(char));
	memset(code, '\0', sizeof(code));
	fread(code, sizeof(char), info.st_size, f);

	fclose(f);
}


void interpret(size_t start, size_t end)
{
	size_t i, w;
	for (i=start; i<=end; i++) {
		switch (code[i]) {
			case '<':
				if (p>0) p--;
				break;
			case '>':
				if (p<BUF_SIZE-1) p++;
				break;
			case '-':
				if (buf[p]>0) buf[p]--;
				break;
			case '+':
				if (buf[p]<CHAR_MAX) buf[p]++;
				break;
			case '.':
				putchar(buf[p]);
				break;
			case '#':
				printf("%d", buf[p]);
				break;
			case ',':
				buf[p] = getchar();
				break;
			case '[':
				w = loop_width(i, end);
				if (buf[p]==0)
					i+=w; /* skip this loop */
				else {
					interpret(i+1, i+w); /* interpret this loop */
					i+=w;
				}
				break;
			case ']':
				if (buf[p]==0)
					return;
				i=start-1;
				break;
		}
	}
}


size_t loop_width(size_t start, size_t end)
{
	int loops=0;
	size_t i;
	for (i=start; i<=end; i++) {
		if (code[i]=='[') {
			loops++;
			continue;
		}
		if (code[i]==']') {
			loops--;
			if (loops==0)
				return i-start;
		}
	}
	return 0;
}

