/* osdclang.c */
/* OSDClang interpreter */

/*
  Copyright (c) 2007, Stuart Coyle

  All rights reserved.

  Redistribution and use in source and binary forms, with or without modification,
  are permitted provided that the following conditions are met:

  * Redistributions of source code must retain the above copyright notice, 
  this list of conditions and the following disclaimer.
  * Redistributions in binary form must reproduce the above copyright notice,
  this list of conditions and the following disclaimer in the documentation
  and/or other materials provided with the distribution.
  * Neither the name of Stuart Coyle nor the names of its contributors
  may be used to endorse or promote products derived from this software
  without specific prior written permission.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

*/

#include <stdio.h>
#include <stdlib.h>

/* If this is undefined we will just treat invalid command codes as nop */
#define ERR_ON_INVALID_COMMAND 1

#define DUMP_TEXT 1
#define DUMP_DATA 1
#define LINE_LENGTH 20
/*#define DEBUG*/ 

/* Define your memory sizes here */
#define MAX_DATA_SIZE 4096
#define MAX_TEXT_SIZE 4096

/* The highly advanced memory model */
char *text; 
char *data;
int textsize;

/* data and program pointers */
char* pdata;
char* ptext;

/* Do a big dump. */
void dump(){
	unsigned long int c;
	fprintf(stderr,"Text ptr:%.8lx,\tData ptr:%.8lx\n",
		(unsigned long)(ptext-text),(unsigned long)(pdata-data));
	if(ptext && pdata){
		fprintf(stderr,"Current command: %.2x,\tCurrent data: %.2x\n",*ptext,*pdata);
	}
#ifdef DUMP_TEXT
	fprintf(stderr,"TEXT SEGMENT:");
	for(c = 0; c< textsize;c++){
		if((c % LINE_LENGTH)==0){
			fprintf(stderr,"\n%.8lx: ",c);
		}
	  fprintf(stderr,"%.2x ",text[c]);
	}
	fprintf(stderr,"\n");
#endif
#ifdef DUMP_DATA
	fprintf(stderr,"DATA SEGMENT:");
	for(c = 0; c< MAX_DATA_SIZE;c++){
		if((c % LINE_LENGTH)==0){
			fprintf(stderr,"\n%.8lx: ",c);
		}
	  fprintf(stderr,"%.2x ",data[c]);
	}
	fprintf(stderr,"\n");
#endif
}

/* Print an error, dump memory and exit */
void error(){
	dump();
	free(text);
	free(data);
	exit(1);
}

/* Assign a number to a char - . = 0, ? = 1, ! = 2 */
int convert(char c){
	switch(c){
	case '.': return 0x00;
	case '?': return 0x01;
	case '!': return 0x02;
	default: 
		fprintf(stderr,"Syntax error: unidentified character: %c\n",c);
		error();
 	}
	return -1;
}

/* convert a char pair into bytecode */
char bytecode(char a, char b){
	return (convert(a)<<2)+convert(b);
}

/* Get character but ignore spaces, tabs and newlines */
char osdngetc(FILE *f){
	char c;
	do{
	    c=getc(f);
	}while(c == ' ' || c == '\t' || c == '\n');
	return c;
}

/* Slurp the program file into our expansive memory. */
int load(char *filename){

	FILE *f;
	char a,b;
	int p = 0;

	f = fopen(filename,"r");
	if(!f) {
		fprintf(stderr, "Cannot open file %s for reading.\n",filename);
		error();
	}
	while(((a = osdngetc(f))!= EOF)&& 
	      ((b = osdngetc(f))!= EOF)){
		
		text[p]=bytecode(a,b);
#ifdef DEBUG
		fprintf(stderr,"read: a %c b %c\tp= %d\n",a,b,p);
		fprintf(stderr,"code %d\n",text[p]);
		fflush(stderr);
#endif
		p++;
		if(p > MAX_TEXT_SIZE){
			fprintf(stderr,"Program too large.\n");
			error();
		}
	}
	return p;
}

/* Execute a single command. Oh! the POWER!! */
/* It is a reflection of the minimalist beauty of 
   OSDClang that we can do with only 8 commands! */
void docommand(void){
	switch (*ptext){		
	case 0:  *pdata += 1;
		break; 
	case 1:  pdata++;
		break; 
	case 2:  *pdata = getchar();
		break; 
	case 4:  pdata--;
		break; 
	case 6: if(*pdata){
		      while(*(ptext++)!=6);
	        }
	        break; 
	case 8: putchar(*pdata);
		break; 
	case 9: if(!*pdata){
		     while(*(ptext--)!=9);
	        }
		break; 
	case 10: *pdata -= 1;
		break;
#ifdef ERR_ON_INVALID_COMMAND
	default: {
		fprintf(stderr, "Bytecode not recognised: %.2x\n",*ptext);
		error();
	}
#else
	default: fprintf(stderr, "Bytecode not recognised: %.2x, ignoring\n",*ptext);
#endif		
	};
}

/* Like it says, run the program! */
void run(){
#ifdef DEBUG
	printf("Running\n");
#endif
	ptext = text;
	pdata = data;
	while( ptext < text+textsize){
		docommand();
		if(pdata > data+MAX_DATA_SIZE){ 
			fprintf(stderr,"Heap overflow!\n"); error();
		}
		if(pdata < data){ 
			fprintf(stderr,"Heap underflow!\n"); 
			error();
		}
		if(ptext > text+textsize) {
			fprintf(stderr, "Text pointer out of bounds.\n");
			error();
		}
		if(ptext < text) { 
			fprintf(stderr,"Text pointer underflow. \n\
                                        God only knows how this happened...\n");
			error();
		}
		ptext++;
	}
}

/* Tada! The main function. */
int main(int argc, char **argv){
	
	char *filename;
	filename = argv[1];

	text = malloc(MAX_TEXT_SIZE);
	if(!text){
		fprintf(stderr,"Out of memory: cannot allocate text segment.\n");
		error();
	};

	data = malloc(MAX_DATA_SIZE);
	if(!data){
		fprintf(stderr,"Out of memory: cannot allocate data segment.\n");
		error();
	};

      	textsize = load(filename);
#ifdef DEBUG
	printf("Text size %d\n",textsize);
#endif
	if(!textsize){
		fprintf(stderr,"Error reading program file %s\n",filename);
		error();
	}; 
	run();
	free(text);
	free(data);
	exit(0);
}

