/* DUP - 2009 Ian Osgood */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>

/* code */
/* TODO: UTF-8 encoding */

const char *code;
size_t code_len;

int *ahead;

size_t ip = 0;

/* stacks */

#define MAX_STACK 256		/* TODO: growable */

int stack[MAX_STACK];
int ret[MAX_STACK];

/* variables and memory */

#define MAX_MEM 16384		/* TODO: growable */

int var[MAX_MEM];
int *mem = var + 26;

void push(int *s, int d)
{
	if (*s == MAX_STACK) {
		puts("Stack overflow");
		exit(3);
	}
	s[*s] = d;
	++(*s);
}
int pop(int *s)
{
	if (*s == 1) {
		puts("Stack underflow");
		exit(3);
	}
	--(*s);
	return s[*s];
}
inline int pick(int *s, int i)
{
	return s[*s - i - 1];
}
/* int *top(int *s) { return s + *s - 1; } */

void eval_op(char c)
{
	int t, s;

	switch(c)
	{
	case '$' : push(stack, pick(stack, 0)); break;
	case '%' : (void)pop(stack); break;
	case '^' : push(stack, pick(stack, 1)); break;
	case '\\': t=pick(stack, 1); stack[*stack - 2] = pick(stack, 0); stack[*stack - 1] = t; break;
	case '@' : t=pick(stack, 2); stack[*stack - 3] = pick(stack, 1); stack[*stack - 2] = pick(stack, 0); stack[*stack - 1] = t; break;
	
	case '(' : push(ret, pop(stack)); break;
	case ')' : push(stack, pop(ret)); break;

	case '+' : stack[*stack - 2] += pop(stack); break;
	case '-' : stack[*stack - 2] -= pop(stack); break;
	case '*' : stack[*stack - 2] *= pop(stack); break;
	case '/' : t=pick(stack, 0); s=pick(stack, 1);
		stack[*stack - 2] = s%t; stack[*stack - 1] = s/t; break;
	case '_' : stack[*stack - 1] = -pick(stack, 0); break;
	
	case '&' : stack[*stack - 2] &= pop(stack); break;
	case '|' : stack[*stack - 2] ^= pop(stack); break;
	case '~' : stack[*stack - 1] = ~pick(stack, 0); break;
	
	case '<' : stack[*stack - 2] = -(pick(stack, 1) <  pop(stack)); break;
	case '=' : stack[*stack - 2] = -(pick(stack, 1) == pop(stack)); break;
	case '>' : stack[*stack - 2] = -(pick(stack, 1) >  pop(stack)); break;
	
	case ':' : mem[pop(stack)] = pop(stack); break;
	case ';' : push(stack, mem[pop(stack)]); break;
	
	case '\'': push(stack, code[++ip]); break;
	case '"' : while (code[++ip] != '"') {
			mem[stack[*stack-1]] = code[ip];
			++stack[*stack-1];
		} break;
	case '{' : ip = ahead[ip]; break;
	
	case '[' : push(stack, ip); ip = ahead[ip]; break;
	case ']' :
		if (*ret > 3 && code[pick(ret, 2)] == '#') {
			if (pop(stack)) {		/* continue (2dup) */
				push(ret, pick(ret, 1));	/* test */
				push(ret, pick(ret, 1));	/* body */
			} else 
				*ret -= 2;		/* break (2drop) */
		}
		ip = pop(ret); break;
	case '!' : push(ret, ip); ip = pop(stack); break;
	case '?' : s=pop(stack); t=pop(stack);			/* t for true */
		push(ret, ip); ip = pop(stack) ? t : s; break;
	case '#' : s=pop(stack); t=pop(stack);			/* t for test */
		push(ret, ip); push(ret, t); push(ret, s); ip = t; break;
	
	case '`' : push(stack, getchar()); break;
	case ',' : putchar(pop(stack)); break;
	case '.' : printf("%d", pop(stack)); break;
	/* TODO: flush */
	}
}

void eval()
{
	char c = code[ip];
	if (isdigit(c)) {
		if (ahead[ip]) {
			push(stack, ahead[ip+1]);	/* cached large number */
			ip = ahead[ip];
		} else
			push(stack, c-'0');		/* single digit */
	} else
	if ('a'<=c && c<='z') {
		push(stack, 'a'-1-c);
	} else
		eval_op(c);
	++ip;
}

int scan_for(int start, char c)
{
	char *found = strchr(code+start+1, c);
	if (!found) {
		printf("Unmatched '%c' at %d\n", code[start], start);
		exit(4);
	}
	return found - code;
}

int scan_ahead(int ip)
{
	int start = ip;

	if (isdigit(code[ip])) {
		int n = 0;
		do {
			n = 10*n + (code[ip]-'0');
		} while (isdigit(code[++ip]));
		--ip;
		if (ip > start) {
			ahead[start] = ip;
			ahead[start+1] = n;	/* cache parsed number */
		}
	}
	else if (code[ip] == '\'')
		++ip;
	else if (code[ip] == '"') {
		ahead[start] = ip = scan_for(ip, '"');
	}
	else if (code[ip] == '{') {
		ahead[start] = ip = scan_for(ip, '}');
	}
	else if (code[ip] == '[') {
		++ip;
		while (1) {
			if (ip >= code_len) {
				printf("Unmatched '[' at %d\n", start);
				exit(4);
			}
			if (code[ip] == ']')
				break;
			ip = scan_ahead(ip);
		}
		ahead[start] = ip;
	}
	return ip+1;
}

void init()
{
	stack[0] = 1;
	ret[0] = 1;

	ahead = (int *)calloc(code_len, sizeof(int));

	ip = 0;
	while (ip < code_len)
		ip = scan_ahead(ip);
	ip = 0;
}

void load(char *file)
{
	FILE *f = fopen(file, "r");
	if (f==NULL) {
		puts("Source file not found");
		exit(2);
	}
	/* source length */
	fseek(f, 0L, SEEK_END);
	code_len = ftell(f);
	rewind(f);
	
	code = (char *)malloc(code_len);
	fread((void *)code, code_len, 1, f);    /* not sure why the cast is necessary */
	fclose(f);
}

void run()
{
	init();
	while (ip < code_len)
		eval();
}

/* test infrastructure
     test("code", stack after);
   
   order of implementation:
   1. numbers whitespace
   2. $ % \ ^ @ pick
   3. + - * / _ & | ~
   4. < = >
   5. variables memory : ;
   6. 'c "strings" {comments}
   7. ( )
   8. [ ] ! ?
   9. #
   10. ` , . flush
*/

int test(const char *snippet, int ss, int *s)
{
	int i, ret=0;
	code = snippet;
	code_len = strlen(code);
	run();
	if (ss != *stack - 1)
		printf("stack size %d expected %d for %s\n", *stack-1, ss, code);
	for (i=0; i< ss; ++i)
		if (stack[i+1] != s[i])
			printf("stack[%d] %d expected %d for %s\n", i, stack[i+1], s[i], code);
	free(ahead);
	return ret;
}

struct test_s {
	const char *snippet;
	int stack_size;
	int stack[10];
} t[] = {
	{ "9", 1, { 9 } },
	{ "1234", 1, { 1234 } },
	{ "12 34", 2, { 12, 34 } },

	{ "2$", 2, { 2, 2 } },
	{ "1%", 0, {} },
	{ "1 2^", 3, { 1, 2, 1 } },
	{ "1 2\\", 2, { 2, 1 } },
	{ "1 2 3@", 3, { 2, 3, 1 } },
	/* TODO: pick ¿ */

	{ "5 3+", 1, { 8 } },
	{ "5 3-", 1, { 2 } },
	{ "5 3*", 1, { 15 } },
	{ "5 3/", 2, { 2, 1 } },
	{ "5_", 1, { -5 } },

	{ "5 3&", 1, { 1 } },
	{ "5 3|", 1, { 6 } },  /* XOR */
	{ "0~", 1, { -1 } },

	{ "5 3<", 1, { 0 } },
	{ "5 3>", 1, { -1 } },
	{ "5 3= 5$=", 2, { 0, -1 } },

	{ "3a:2z: 6a;z;", 3, { 6, 3, 2 } },
	{ "3 70: 1 0: z; 0; 70;", 3, { 2, 1, 3 } },

	{ "'0", 1, { '0' } },
	{ "0$\"str\"^$;\\1+$;\\1+;", 5, { 0, 3, 's', 't', 'r' } },
	{ "{123$}", 0, {} },
	
	{ "1 1(1+)", 2, { 2, 1 } },

	{ "[]" , 1, { 0 } },
	{ "1[2*]!", 1, { 2 } },
	{ "0['t]['f]?", 1, { 'f' } },
	{ "0~['t]['f]?", 1, { 't' } },
	{ "[$1>[$1-f;!*][%1]?]f: 6f;!", 1, { 720 } },
	
	{ "3[$][$1-]#", 4, { 3, 2, 1, 0 } },
	
	/* how to test i/o? */

	{ NULL, 0, {} }
};

int tests()
{
	int ret = 0;
	struct test_s *p;
	for (p = t; p->snippet != NULL; ++p)
		ret |= test(p->snippet, p->stack_size, p->stack);
	return ret;
}

void usage()
{
	puts("Usage:");
	puts("  dup file");
	exit(1);
}

int main(int argc, char *argv[])
{
	//if (argc < 2) usage();
	if (argc < 2) exit(tests());
	load(argv[argc-1]);
	run();
	return 0;
}

