/*
 * Copyright 2001 Silicon Graphics, Inc. All rights reserved.
 */
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <setjmp.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include "sial.h"

char *sialpp_create_buffer(void *, int);
void sialpp_switch_to_buffer(void *);
void sialpp_delete_buffer(void *);

typedef void fdone(void *data);
extern void* sial_create_buffer(FILE *, int);
typedef struct inbuf_s {
	srcpos_t pos;		/* current filename,line,col */
	int cursor;		/* position of next input() character */
	int len;		/* length of the buffer */
	char *buf;		/* buffer */
	void *data;		/* opaque data for callback */
	void *mac;		/* for nested defines substitutions */
	fdone *donefunc;	/* function to call when done with buffer */
	int space;
	int eofonpop;		/* terminate parsing at end of buffer ? */
#if linux
	void* yybuf;
#endif

} inbuf_t;

void sial_switch_to_buffer(void *);
void sial_delete_buffer(void *);
#define MAXIN 20
static inbuf_t inlist[MAXIN];
static inbuf_t *in=0;
static int nin=0;
static int eol=0, virgin=1;
#if linux
static int inpp=0;
#endif

extern void *sial_getmac(char *, int);

/* this function is called by the macro macro functions to set
   and test the current buffer level in order to take care of 
   imbedded macros w/ the same parameter names.
   see sial_getmac().
*/ 
void *sial_getcurmac(void) 
{ 
	return in ? in->mac : 0 ;
}

static void
sial_pusherror(void)
{
	sial_error("Too many level of input stream");
}

/*
	Push a buffer onto the parser input stream.
*/
void
sial_pushbuf(char *buf, char *fname, void (*vf)(void *), void *d, void *m)
{
fdone *f=(fdone*)vf;

	if(nin==MAXIN) sial_pusherror();

	/* if we are pushing a macro then use upper level coordinates */
	if(fname) {

		inlist[nin].pos.line=1;
		inlist[nin].pos.col=1;
		inlist[nin].pos.file=fname;

	} else sial_setpos(&inlist[nin].pos);

	/* set it */
	if(nin) {

		sial_curpos(&inlist[nin].pos, &inlist[nin-1].pos);

	} else {

		sial_curpos(&inlist[nin].pos, 0);

	}

	inlist[nin].buf=buf;
	inlist[nin].donefunc=f;
	inlist[nin].space=0;
	inlist[nin].data=d;
	inlist[nin].mac=m;
	inlist[nin].cursor=0;
	inlist[nin].len=strlen(buf);
	inlist[nin].eofonpop=0;
#if linux
	if(inpp) {
		inlist[nin].yybuf=sialpp_create_buffer(0, inlist[nin].len);
		sialpp_switch_to_buffer(inlist[nin].yybuf);
	}else{
		inlist[nin].yybuf=sial_create_buffer(0, inlist[nin].len);
		sial_switch_to_buffer(inlist[nin].yybuf);
	}
#endif
	in=&inlist[nin];
	nin++;
}

/* read the rest of the "#include" line from the parser input stream
   open the corresponding file, push it's contain on the parser input
   stream.
*/
int
sial_pushfile(char *name)
{
struct stat s;
char *fname;

	if(nin==MAXIN) sial_pusherror();
	
	fname=sial_fileipath(name);

	if(fname) {

		if(!stat(fname, &s)) {
	
			char *buf=sial_alloc(s.st_size+1);
			int fd;

			if((fd=open(fname, O_RDONLY))==-1) {

					sial_msg("%s: %s", fname, strerror(errno));

			}
			else {

				if(read(fd, buf, s.st_size) != s.st_size) {

					if(errno != EISDIR) 
						sial_msg("%s: read error : %s", fname, strerror(errno));

				}
				else {

					
					buf[s.st_size]='\0';
					sial_pushbuf(buf, fname, sial_free, buf, 0);
					close(fd);
					return 1;

				}
				close(fd);
			}

			sial_free(buf);

		}
		sial_free(fname);
	}
	return 0;

}

/*
	Done with the current buffer.
	Go back to previous on the stack.
*/
static int
sial_popin(void)
{

	if(eol || !nin) {

		if(!nin) in=0;
		return 1;
	
	} else {

		nin--;

		/* call back */
		if(inlist[nin].donefunc) {

			inlist[nin].donefunc(inlist[nin].data);
		}
		if(inlist[nin].eofonpop) {

			eol=1;
#if linux
			inpp=0;
#endif
		}
		if(!nin) in=0;
		else {

			in=&inlist[nin-1];
			if(!eol) {
#if linux
				if(inpp) {
					sialpp_switch_to_buffer(inlist[nin-1].yybuf);
					sialpp_delete_buffer(inlist[nin].yybuf);
				} else {
					sial_switch_to_buffer(inlist[nin-1].yybuf);
					sial_delete_buffer(inlist[nin].yybuf);
				}
#endif
			}
			sial_curpos(&in->pos, 0);
		}
		return 0;
	}
}

/*
  With linux we need to use the wrap function
  so that the flex buffer stuff is keaped in the game.
*/
int
sialwrap(void) 
{ 
	return sial_popin(); 
}

int
sialppwrap(void)
{ 
	if(eol) return 1;
	return sial_popin();
}

void
sial_popallin(void)
{
	while(nin) {
		eol=0;
		sial_popin();
	}
}

#define BLK_IFDEF	1
#define BLK_IFNDEF	2
#define BLK_IF		3
#define BLK_ELIF	4
#define BLK_ELSE	5

typedef struct ifblk {
	int type;	/* type of block */
	int exprpos;    /* curpor to start of corresponding expression */
	int bstart;	/* curpor position at start of block */
	int dirlen;	/* length of the directive name */
	int bend;	/* cursor position at end of block */
	struct ifblk *next;
} ifblk_t;

static int
sial_isif(int pos)
{
	if(
	      (in->len-pos>6 && !strncmp(in->buf+pos, "ifndef", 6))
	   || (in->len-pos>5 && !strncmp(in->buf+pos, "ifdef", 5)) 
	   || (in->len-pos>2 && !strncmp(in->buf+pos, "if", 2)) 

	) return 1;

	return 0;
}

/*
	Get directly to next block, skipping nested blocks.
*/
static int
sial_nxtblk(int pos, int lev)
{
int virgin=0;

	while(1) {

		if(pos==in->len) {

			sial_error("Block without endif");
		}

		if(virgin && in->buf[pos]=='#') {

			pos++;

			/* nexted if ? */
			if(in->buf[pos]=='i' && sial_isif(pos)) {

				while(1) {
					pos=sial_nxtblk(pos, lev+1);
					if(in->len-pos>5 && !strncmp(in->buf+pos, "endif", 5)) break;
				}

			} else if(in->buf[pos]=='e') return pos;

		} else if(in->buf[pos]=='\n') {

			virgin=1;

		} else if(in->buf[pos] != ' ' && in->buf[pos] != '\t') {

			virgin=0;
		}
		pos++;
	}
}

static ifblk_t *
sial_getblklst(void)
{
ifblk_t *lst, *last;
int doneelse=0, pos;

	lst=sial_alloc(sizeof(ifblk_t));

	lst->bstart=in->cursor-1;
	if(!strncmp(in->buf+in->cursor, "ifdef", 5)) {

		lst->type=BLK_IFDEF;
		lst->exprpos=lst->bstart+6;
		lst->dirlen=6;

	} else if(!strncmp(in->buf+in->cursor, "ifndef", 6)){

		lst->type=BLK_IFNDEF;
		lst->exprpos=lst->bstart+7;
		lst->dirlen=7;

	} else {

		lst->type=BLK_IF;
		lst->exprpos=lst->bstart+3;
		lst->dirlen=3;
	}

	last=lst;
	pos=in->cursor;

	while(1) {

		ifblk_t *new=sial_alloc(sizeof(ifblk_t));

		pos=sial_nxtblk(pos, 0);

		last->bend=pos-2;
		new->bstart=pos-1;
		if(!strncmp(in->buf+pos, "elif", 4)) {

			if(doneelse) {

				sial_error("Additional block found after #else directive");
			}
			new->type=BLK_ELIF;
			new->exprpos=new->bstart+5;
			new->dirlen=5;

		} else if(!strncmp(in->buf+pos, "else", 4)) {

			if(doneelse) {

				sial_error("#else already done");
			}
			new->type=BLK_ELSE;
			new->exprpos=new->bstart+5;
			new->dirlen=5;
			doneelse=1;

		} else if(!strncmp(in->buf+pos, "endif", 5)) {

			sial_free(new);
			last->next=0;
			break;
		}
		last->next=new;
		last=new;
	}
	return lst;
}

/*
	Zap a complete block.
	We put spaces everywhere but over the newline.
	Hey, it works. It's good enough for me.
*/
static void
sial_zapblk(ifblk_t *blk)
{
int i;

	for(i=blk->bstart;i<blk->bend;i++) {

		if(in->buf[i]!='\n') in->buf[i]=' ';
	}
}

int sial_eol(char c) { return (!c || c=='\n') ? 1 : 0; }

/*
	This function is called by sial_input() when a #if[def] is found.
	We gather all blocks of the if/then/else into a list.
	Parsing and execution of the expression is done only when needed.
*/
void sial_rsteofoneol(void)
{
	eol=0;
	virgin=1;
#if linux
	inpp=0;
#endif
}

void
sial_zapif(void)
{
ifblk_t *lst=sial_getblklst();
ifblk_t *last=lst;
int b=0;

	/* we scan the entire list untill a condition is true or we
	   reach #else or we reach the end */
	while(lst) {

		switch(lst->type) {

			case BLK_IFDEF:
			case BLK_IFNDEF:
			{
			char mname[MAX_SYMNAMELEN+1], c;
			int i=0, j=lst->bstart+lst->dirlen;
			int v;

				/* get the macro name and see if it exists */
				/* skip all white spaces */
				while((c=in->buf[j]) == ' ' || c == '\t') if(c=='\n' || !c) {

					sial_error("Macro name not found!");

				} else j++;

				/* get the constant or macro name */
				while((c=in->buf[j]) != ' ' && c != '\t' && c != '(') {

					if(c=='\n' || !c) break;

					if(i==MAX_SYMNAMELEN) break;

					mname[i++]=c;
					j++;
				}
				mname[i]='\0';
				lst->dirlen += (j-lst->bstart-lst->dirlen);
				if(sial_getmac(mname,0)) v=1;
				else v=0;
				b=lst->type==BLK_IFDEF?v:!v;

			}
			break;

			case BLK_IF: case BLK_ELIF:
			{
			node_t*n;
			void sialpprestart(int);
			void sialppparse(void);
			char *expr=sial_getline();
			int len=lst->dirlen;

#if linux
				sialpprestart(0);
				inpp=1;
#endif
				lst->dirlen += (in->cursor-lst->exprpos-1);
				sial_pushbuf(expr, 0, sial_free, expr, 0);
				in->eofonpop=1;
				in->cursor += len;
				sialppparse();

				sial_rsteofoneol();
				eol=0;

				/* get the resulting node_t*/
				n=sial_getppnode();

				/* execute it */
				{
					
				int *exval;
				jmp_buf exitjmp;
				void *sa;
				value_t *v;

					sa=sial_setexcept();

					if(!setjmp(exitjmp)) {

						sial_pushjmp(J_EXIT, &exitjmp, &exval);
						v=NODE_EXE(n);
						sial_rmexcept(sa);
						sial_popjmp(J_EXIT);
						b=sial_bool(v);
						sial_freeval(v);

					} else {

						sial_rmexcept(sa);
						sial_parseback();
					}
				}
			}
			break;

			case BLK_ELSE:
			{

				b=1;

			}
			break;
		}

		last=lst;
		if(b) break;

		/* count new lines */
		{
			while(in->cursor < lst->bend+1) {

				if(sial_eol(in->buf[in->cursor]))
					sial_line(1);
				in->cursor++;
			}
	
		}
		lst=lst->next;
	}

	if(lst) {

		/* remove the # directive itself */
		memset(in->buf+lst->bstart, ' ', lst->dirlen);

		/* zap all remaining blocks */
		while((lst=lst->next)) { sial_zapblk(lst); last=lst; }
	}

	/* most remove the #endif */
	memset(in->buf+last->bend+1, ' ', 6);
}

static int rawinput=0;
void sial_rawinput(int on) { rawinput=on; }

/*
	Get the next character from the input stream tack.
*/
int
sial_input(void) 
{ 
register char c;

redo:

	if(!in || eol) {

		return 0;
	}

	if(in->cursor==in->len) {

#if linux
		return (-1);
#else
		sial_popin();
		goto redo;
#endif
	}

	c=in->buf[in->cursor++];
	if(!rawinput) {
		if(c=='\\') {

			if(in->cursor==in->len) return c;
			else if(in->buf[in->cursor]=='\n') {

				sial_line(1);
				in->cursor++;
				goto redo;
			}

		} else if(c=='/') {

			if(in->cursor==in->len) return c;
			else if(in->buf[in->cursor]=='/') {

				/* C++ stype comment. Eat it. */
				in->cursor++;
				while(in->cursor<in->len) {

					c=in->buf[in->cursor++];
					if(c=='\n') { 
						/* leave the newline in there */
						in->cursor--;
						break;
					}
				}
				goto redo;

			}else if(in->buf[in->cursor]=='*') {

				/* C style comment, eat it */
				in->cursor++;
				while(in->cursor<in->len) {

					c=in->buf[in->cursor++];
					if(c=='*' && (in->cursor<in->len)) {

						if(in->buf[in->cursor]=='/') {

							in->cursor++;
							break;

						}

					} else if(c=='/' && (in->cursor<in->len)) {

						if(in->buf[in->cursor]=='*') {

							sial_warning("Nested comment");

						}

					}
					if(c=='\n') sial_line(1);
				}
				goto redo;
			}

		}else if(virgin && c=='#') {

			char *p=in->buf+in->cursor;
			char *end=in->buf+in->len;
			int c=0;

			/* skip white spaces '#      define ... ' */
			while(p<(end-4) && (*p==' ' || *p=='\t')) { p++; c++; }

			/* this must be a preprocessor command */
			/* we trigger on the if, ifdef only. #define, #undef, #include are
			   handled by the lexer */

			if(!strncmp(p, "if", 2)) {

				in->cursor += c;
				sial_zapif();
				/* zapif sets the cursor correctly */
				goto redo;
			}
		}
	}

	if(c=='\n') {

		virgin=1;
		sial_line(1);

	}else if(c != ' ' && c != '\t') {

		virgin=0;

	} 
	else if(!rawinput){

		register char c2=c;

		/* return one white space for a group of them */
		while((in->cursor < in->len) 
			&& in->buf[in->cursor]==c2) in->cursor++;

	}

	return c;
}

char *
sial_cursorp()
{
	if(!in) return 0;
	return in->buf+in->cursor;
}

void
sial_unput(char c)
{

	if(!c) return;
	if(!nin) return;
	if(!in->cursor) {

		sial_error("Fatal unput error");

	}
	in->buf[--in->cursor]=c;
	if(c=='\n') {

		sial_line(-1);
	}
}

/*
	Get a single line from the parser stream.
*/
char *
sial_getline()
{
char *buf2=0;

	/* use the current input stream for that */
	if(in) {

		/* save the cursor */
		int n=0, c;
		char *buf=sial_alloc(in->len-in->cursor+1);

		while(!sial_eol(c=sial_input()))
			buf[n++]=c;
		buf[n]='\0';
		buf2=sial_alloc(n+2);
		strcpy(buf2,buf);
		buf2[n]=' ';
		buf2[n+1]='\0';
		sial_free(buf);
		/* leave the newline there */
		sial_unput(c);
	}
	return buf2;
}


/* read a complete line from the input stream */
void
sial_include(void)
{
char name[MAX_SYMNAMELEN+1];
int n=0;
int c;
int found=0;

	while((c=sial_input())) {

		if(c=='"') {

			if(!found) found++;
			else break;
			continue;
		}

		if(c=='<') {

			found++;
			continue;
			
		}
		if(c=='>') break;
		if(sial_eol(c)) {

			sial_error("Unexpected EOL on #include");
		}
		if(found) {

			if(n==MAX_SYMNAMELEN) {

				sial_error("Filename too long");
			}
			name[n++]=c;

		}
	}
	name[n]='\0';

	/* flush the rest of the line */
	while((c=sial_input())) {

		if(sial_eol(c)) break;
	}
	sial_unput(c);
	if(sial_fileipath(name)) {

		sial_pushfile(name);

	} else {

		sial_error("Include file not found: %s", name);
	}
}
