/*
 * Copyright (C) 2005 Michael Velten <michael@michnet.de>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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.
 */

/*
 * An interpreter for Brainfuck written in C. (2005-03-09)
 */

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

#define BF_ARRAY_SIZE 30000

typedef struct {
	char *ptr;
	unsigned int len;
} file_t;

typedef struct {
	char token;
	int n;
} chunk_t;

static void map_file(const char *, file_t *);
static unsigned int parse(file_t *, chunk_t *);
static void run(chunk_t *, unsigned int);
	
/*
 * We just use mmap for the sake of simplicity.
 */
static void
map_file(const char *path, file_t *file)
{
	struct stat statbuf;
	char *fp;
	int fd;

	if (stat(path, &statbuf) < 0) {
		perror("can't stat given file");
		exit(1);
	}
	if ((fd = open(path, O_RDONLY)) < 0) {
		perror("can't open given file for reading");
		exit(1);
	}
	if ((fp = mmap(0, statbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0)) == MAP_FAILED) {
		perror("can't mmap given file for reading");
		exit(1);
	}
	if (close(fd) < 0) {
		perror("can't close given file");
		exit(1);
	}
	file->ptr = fp;
	file->len = statbuf.st_size;
}

/*
 * Parse BF code, fill "chunk" with optimized code, and return length of "chunk".
 */
static unsigned int
parse(file_t *file, chunk_t *chunk)
{
	register unsigned int i, j = 0;
	register unsigned char prev_token = '\0', curr_token;
	unsigned int *stack, k = 0;
	
	if (!file->len)
		return 0;

	if ((stack = malloc(file->len * sizeof(unsigned int))) == NULL) {
		perror("can't malloc memory for stack");
		exit(1);
	}

	for (i = 0; i < file->len; i++) {
		curr_token = file->ptr[i];
		switch (curr_token) {
			case '>':
			case '+':
				if (curr_token == prev_token) {
					chunk[j-1].n++;
					continue;
				}
				chunk[j].n = 1;
				break;

			case '<':
			case '-':
				if (curr_token == prev_token) {
					chunk[j-1].n--;
					continue;
				}
				chunk[j].n = -1;
				break;

			case '.':
			case ',':
				break;

			case '[':
				stack[k++] = j;
				break;

			case ']':
				if (!k) {
					fprintf(stderr, "syntax error at pos %d: unmatched bracket: ]\n", i);
					exit(1);
				}
				chunk[j].n = stack[--k];
				chunk[chunk[j].n].n = j;
				break;

			default:
				continue;
		}
		if (curr_token == '<')
			chunk[j++].token = '>';
		else if (curr_token == '-')
			chunk[j++].token = '+';
		else
			chunk[j++].token = curr_token;
		prev_token = curr_token;
	}
	if (k) {
		fprintf(stderr, "syntax error: unmatched bracket: [\n");
		exit(1);
	}

	return j;
}

/*
 * Interpret optimized code.
 */
static void
run(chunk_t *chunk, unsigned int len)
{
	register unsigned int i;
	unsigned char t[BF_ARRAY_SIZE];
	register unsigned char *tp = t;

	for (i = 0; i < len; i++) {
		switch (chunk[i].token) {
			case '>':
				tp += chunk[i].n;
				break;

			case '+':
				if (tp < t || tp >= t + BF_ARRAY_SIZE) {
					fprintf(stderr, "runtime error: dereferencing invalid pointer value\n");
					exit(1);
				}
				else
					*tp += chunk[i].n;
				break;

			case '.':
				putchar(*tp);
				fflush(stdout);
				break;

			case ',':
				*tp = getchar();
				break;

			case '[':
				if (!*tp)
					i = chunk[i].n;
				break;

			case ']':
				if (*tp)
					i = chunk[i].n;
				break;
		}
	}
}

int
main(int argc, char **argv)
{
	file_t file;
	chunk_t *chunk;
	unsigned int len;
	
	if (argc != 2) {
		printf("usage: %s FILE\n", argv[0]);
		exit(1);
	}
	map_file(argv[1], &file);
	if ((chunk = malloc(file.len * sizeof(chunk_t))) == NULL) {
		perror("can't malloc memory for chunk array");
		exit(1);
	}
	len = parse(&file, chunk);
	run(chunk, len);

	exit(0);
}
