/*
 * Copyright 2001 Silicon Graphics, Inc. All rights reserved.
 */
#include "sial.h"
#include <string.h>
#include <unistd.h>
#include <curses.h>
#include <term.h>
#include <termio.h>
#include <ctype.h>
#include <stdarg.h>
#include <malloc.h>
#include <limits.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <stdlib.h>
#include <regex.h>
#include <ctype.h>

static FILE *ofile=0;
static int cols=25;
static char *bold_on, *bold_off;


static void
sial_getwinsize(void)
{
struct winsize w;

	if (ioctl (fileno(ofile), TIOCGWINSZ, &w) == 0)
	{
		cols=w.ws_col;
	}
	else /* use ENV */
	{
	char *ewidth;
		if ((ewidth = getenv ("COLUMNS")))
		cols = atoi (ewidth);
		/* use what's in terminfo */
		if (cols <= 0)
		cols = tigetnum ("co");
	}
	if(cols <= 10) cols=10;
	if(cols > 80) cols=80;
}

void
sial_setofile(void * f)
{
int out;
int ret;
char *term;

	ofile=(FILE *)f;

	bold_on="";
	bold_off="";
	cols=80;

	out=fileno(ofile);
        if(isatty(out))
        {

        	if(!(term = getenv ("TERM"))) term="dumb";
        	if(setupterm(term, out, &ret)!=ERR)
        	{
                	bold_on=tigetstr("bold");
			if(!bold_on) bold_on="";
                	bold_off=tigetstr("sgr0");
			if(!bold_off) bold_off="";
        	}
		sial_getwinsize();
        }
}

void *
sial_getofile(void)
{
	return ofile;
}

/*
	Output a line of text to the screen with line wrap
	and escape sequence.
*/
#define ESC '<'
#define ESC2 '>'

static int
sial_tabs(int tabs, char *t, int lf)
{
int i;

	if(lf) fprintf(ofile, "\n");
	for(i=0;i <tabs; i++) fprintf(ofile, t);
	return tabs*4;
}

void
sial_format(int tabs, char *str)
{
char *t="    ";
char *p;
int n;
int mode=0;

	n=sial_tabs(tabs, t, 0);
	sial_getwinsize();
		
	for(p=str; *p; p++) {


		/* check for escape */
		if(!mode && *p == ESC && *(p+1) && *(p+1) == ESC) {

			fprintf(ofile, "%s", bold_on);
			p++;
			mode=1;

		} else if(mode && *p == ESC2 && *(p+1) && *(p+1) == ESC2) {

			fprintf(ofile, "%s", bold_off);
			p++;
			mode=0;

		} else if(*p==' ' || *p=='\t' ) {

			char *p2;
			int wl;

			for(p2=p+1; *p2 && *p2 != ' ' && *p2 != '\t'; p2++);

			wl=p2-p-1;

			if(wl > cols) {

				char *p3=p+(cols-n-1);

				char c=*p3;
				char c2=*(p3+1);

				*p3='-';
				*(p3+1)='\0';

				fprintf(ofile, "%s", p);
				*p3=c;
				*(p3+1)=c2;
				n=sial_tabs(tabs, t, 0);

			} else if(n + (p2-p) >= cols) {

				n=sial_tabs(tabs, t, 1);

			} else {

				fprintf(ofile, " ");
				n++;
			}

		} else if(*p=='\n') {

			n=sial_tabs(tabs, t, 1);
			
		} else {

			fprintf(ofile, "%c", *p);
			n++;
		}
	}

}

void
sial_msg(char *fmt, ...)
{
va_list ap;
	va_start(ap, fmt);
	vfprintf(ofile, fmt, ap);
	va_end(ap);
}

void
sial_freenode(node_t *n)
{
	n->free(n->data);
	sial_free(n);
}

int lineno=1, lastline=1;
int col=1;
static char *filename=0;
static char *lastfile=0;

void
sial_setlastfile(char *fname, int line)
{
	if(!fname) return;
	if(lastfile) sial_free(lastfile);
	lastfile=sial_strdup(fname);
	lastline=line;
}

void
sial_rstpos(void)
{
	lineno=1;
	col=1;
	/* do not free filename */
	filename=0;
}

void
sial_setpos(srcpos_t *p)
{
	p->line=lineno;
	p->col=col;
	p->file=filename;
}

/* set the current position */
void
sial_curpos(srcpos_t *p, srcpos_t *s)
{
	if(s) {
		s->line=lineno;
		s->col=col;
		s->file=filename;
	}
	lineno=p->line;
	col=p->col;
	filename=p->file;
}

int
sial_line(int inc){ return lineno+=inc; }

int
sial_col(int inc) { return col+=inc; }

char *
sial_filename(void) { return filename; }

/*
	This function scans a printf() fmt string and transaletes the %p
	to %08x or %016x depending on the pointer size of the object image.
	We also substiture %> for 8 spaces if the pointer size is 4 bytes, this
	permits easy allignment of output on either 32 or 64 bit images.

	ex:

	Proc	%> pid ppid
	%p    %3d %3d

	In this case the %> alligns the pid with it's corresponding value_t
	in the next line of output.

	We also process the '?' format which will be set to match the 
	corresponding value_t type.

	Also, format versus argument type validation is performed.

*/

/*
	Printf formats have the form : 
	%3$-*3$.*4$lld
	%20x
	%08x
	%-08.8f
*/
/* these are the buildin blocks for a regex matching formats */
#define F_POSP	"([0-9]+\\$)*"
#define F_FLGS	"([-'+ #0]*)"
#define F_WARG	"(\\*([0-9]+\\$)*){0,1}"
#define F_WIDTH	"([0-9]*)"
#define F_PREC	"((\\.(\\*([0-9]+\\$)*)*([0-9]*))*)"
#define F_SIZE	"([hlL]*)"
#define F_FMT	"([diouxXfeEgGcCsSpn?>]{1})"
#define FMTREG F_POSP""F_FLGS""F_WARG""F_WIDTH""F_PREC""F_SIZE""F_FMT
#define M_POSP          1
#define M_FLAGS         2
#define M_WIDTHARG      3
#define M_WIDTDIGITS    4
#define M_WIDTH         5
#define M_PRECARG       8
#define M_PRECDIGITS    9
#define M_PREC          10
#define M_SIZE          11
#define M_FMT           12
#define NMATCH          16
static int addit[]={M_FLAGS,M_WIDTHARG,M_WIDTH,M_PRECARG,M_PREC,M_SIZE};

#define ptrto(idx) (matches[idx].rm_so==matches[idx].rm_eo?0:(pi+matches[idx].rm_so))
#define matchlen(idx) (matches[(idx)].rm_eo-matches[(idx)].rm_so)

void sial_error(char *fmt, ...);

static int
chkforint(char *p, value_t **vals, int *curarg)
{
int pos=-1;

	if(!p) return -1;

	/* a single star ? */
	if(isdigit(p[1])) {

		if(sscanf(p+1, "%d", &pos)!=1) {

			return pos;
		}
		pos--;

	} else {

		pos=*curarg;
		*curarg=(*curarg)+1;

	}

	if(pos < BT_MAXARGS && vals[pos] && vals[pos]->type.type == V_BASE) return pos;
	sial_error("Expected 'integer' type for arg%d", pos+1);
	return -1;
}

#define pushval(val, s, sig) 	(										\
					sig ?									\
					(									\
						(s==8) ? 							\
							(val)->v.sll 						\
						: (								\
							(s==4)  ?						\
								(val)->v.sl					\
							: (							\
								(s==2) ?					\
									(val)->v.ss				\
								:(						\
									(s==1) ?				\
										(val)->v.sc			\
									:( 					\
										sial_error("Oops pushval"),1	\
									)					\
								)						\
							)							\
						)								\
					) : (									\
						(s==8) ? 							\
							(val)->v.ull 						\
						: (								\
							(s==4)  ?						\
								(val)->v.ul					\
							: (							\
								(s==2) ?					\
									(val)->v.us				\
								:(						\
									(s==1) ?				\
										(val)->v.uc			\
									:( 					\
										sial_error("Oops pushval"),1	\
									)					\
								)						\
							)							\
						)								\
					)									\
			)
						

static char *
add_fmt(int len, char *s, char *onefmt, int ppos, int wpos, int posarg, value_t **vals)
{
int size=(vals[posarg]->type.type == V_REF ? sial_defbsize(): vals[posarg]->type.size);
int sign=(vals[posarg]->type.type == V_REF ? 0 : sial_issigned(vals[posarg]->type.typattr));

	if(vals[posarg]->type.type == V_STRING) {

		if(wpos>=0 && ppos<0) 
			s+=snprintf(s, len, onefmt
				, (int)sial_getval(vals[wpos])
				, vals[posarg]->v.data);
		else if(wpos<0 && ppos>=0) 
			s+=snprintf(s, len, onefmt
				, (int)sial_getval(vals[ppos])
				, vals[posarg]->v.data);
		else if(wpos>=0 && ppos>=0) 
			s+=snprintf(s, len, onefmt
				, (int)sial_getval(vals[wpos])
				, (int)sial_getval(vals[ppos])
				, vals[posarg]->v.data);
		else s+=snprintf(s, len, onefmt
				, vals[posarg]->v.data);

	} else {

		if(wpos>=0 && ppos<0) 
			s+=snprintf(s, len, onefmt
				, (int)sial_getval(vals[wpos])
				, pushval(vals[posarg], size, sign));
		else if(wpos<0 && ppos>=0) 
			s+=snprintf(s, len, onefmt
				, (int)sial_getval(vals[ppos])
				, pushval(vals[posarg], size, sign));
		else if(wpos>=0 && ppos>=0) 
			s+=snprintf(s, len, onefmt
				, (int)sial_getval(vals[wpos])
				, (int)sial_getval(vals[ppos])
				, pushval(vals[posarg], size, sign));
		else s+=snprintf(s, len, onefmt
				, pushval(vals[posarg], size, sign));

	}
	return s;
}

static char *
sial_ptr(char *fmt, value_t **vals)
{
int len=strlen(fmt)+100;
char *nfmt=sial_alloc(len),*ni=nfmt;
char *onefmt=sial_alloc(100), *onei=onefmt;
char *p=fmt;
char last=' ';
int curarg=0;
#define NBYTES (len-(nfmt-ni))

	while(*p) {

		if(*p=='%') {

			static regex_t preg;
			static int done=0;
			regmatch_t matches[NMATCH];

			if(!done) {

				regcomp(&preg, FMTREG, REG_EXTENDED);
				done=1;
			}

			/* build a new format translation */
			onefmt=onei;
			*onefmt++=*p++;

			/* if the returned pointer is (char*)-1 or NULL then something is wrong */
			if(!regexec(&preg, p, NMATCH, matches, 0)) {

				int i, n=matches[0].rm_eo-1;
				int posarg, wpos, ppos;
				char *pi=p; /* save p for ptrto() macro */

				/* check that the width and precision field args point
				   to a int value_t. If they were used */
				wpos=chkforint(ptrto(M_WIDTHARG), vals, &curarg);
				ppos=chkforint(ptrto(M_PRECARG), vals, &curarg);

				/* argument position was specfified ? */
				if(ptrto(M_POSP)) {

					/* we work from 0-n, printf works from 1-n */
					if(sscanf(ptrto(M_POSP), "%d", &posarg)==1) posarg--;

					if(posarg >= BT_MAXARGS || !vals[posarg]) {
						sial_error("Invalid arg position specified [%d]", posarg+1);
					}

				} else posarg=curarg++;

				/* jump over the format spec in the original */
				p+=n;
#if 0
for(i=0;i<NMATCH;i++) {
	char buf[40];

	if(ptrto(i)) {
		int n=matchlen(i);
		strncpy(buf, pi+matches[i].rm_so, n);
		buf[n]='\0';
		printf("match[%d]=[%s]\n", i, buf);
	}
}
#endif

				/* copy all format specs to destination except fmt */
				for(i=0;i<sizeof(addit)/sizeof(addit[0]);i++) {

					switch(addit[i]) {

						case M_WIDTHARG:

							if(wpos >=0 ){

								*onefmt++='*';

							} else goto def;

						break;
						case M_PRECARG:

							if(ppos >=0 ){

								*onefmt++='.';
								*onefmt++='*';

							} else goto def;

						break;
						case M_PREC:
							if(ptrto(addit[i])) *onefmt++='.';
							goto def;
						default:
def:
						if(ptrto(addit[i])) {
							strcpy(onefmt, ptrto(addit[i]));
							onefmt+=matchlen(addit[i]);
						}
					}
				}

				if(*p=='p') {

ref:
					/* if user overrides anything don't do nothing */
					if(ptrto(M_FLAGS)||ptrto(M_WIDTH)||ptrto(M_WIDTHARG)||ptrto(M_PREC)||ptrto(M_PRECARG)||ptrto(M_SIZE)) {
						*onefmt++='p';

					} else {
						if(sial_defbsize()==8) {

							strcpy(onefmt, "016llx");
							onefmt+=6;

						} else {

							strcpy(onefmt, "08x");
							onefmt+=3;
						}
					}
					*onefmt='\0';
					p++;
					nfmt=add_fmt(NBYTES, nfmt, onei, ppos, wpos, posarg, vals);

				} else if(*p=='>') { 

					nfmt--;
					if(sial_defbsize()==8) {

						int i;

						for(i=0;i<8;i++) *nfmt++=last;
					}
					p++;

				} else if(*p=='?') {

					/* put the proper format for the user */
					if(!vals[posarg]) {

						sial_error("Expected additional argument %d\n", posarg+1);

					} else switch(vals[posarg]->type.type) {

						case V_BASE: case V_ENUM:
						{
							if(!ptrto(M_SIZE)) {

								if(vals[posarg]->type.size==8) {

									*onefmt++='l';
									*onefmt++='l';
								}
							}
							if(sial_issigned(vals[posarg]->type.typattr)) {

								*onefmt++='d';

							}else{

								*onefmt++='u';
							}
						}
						break;
						case V_REF:
						{
							*p='p';
							goto ref;
						}
						case V_STRING:
						{
							*onefmt++='s';
						}
						break;
					}
					p++;
					*onefmt='\0';
					nfmt=add_fmt(NBYTES, nfmt, onei, ppos, wpos, posarg, vals);

				} else {

					/* check that format and value_t agree */
					/* can't do a lot more then check for strings vs anything_else */

					if(!vals[posarg]) {

						sial_error("Expected additional argument %d\n", posarg+1);


					} else if(*p=='s') {

						if(vals[posarg]->type.type != V_STRING) {

							sial_error("Expected type 'string' as arg%d", posarg+1);
						}

					} else if(vals[posarg]->type.type == V_STRING) {

						sial_error("Incompatible type 'string' in arg%d", posarg+1);

					}
					*onefmt++=*p++;
					*onefmt='\0';
					nfmt=add_fmt(NBYTES, nfmt, onei, ppos, wpos, posarg, vals);
				}

			} else {

				sial_warning("Malformed format specifier!");

			}

		} else {
	
			last=*p;
			if(nfmt-ni > len) sial_error("format tranlation overflow!");
			*nfmt++=*p++;

		}
	}
	sial_free(onei);
	*nfmt='\0';
	return ni;
}

value_t* sial_printf(value_t *vfmt, ...)
{
char *fmt = sial_getptr(vfmt, char);
va_list ap;
value_t *vals[BT_MAXARGS];
int i;
	
	va_start(ap, vfmt);
	for(i=0;i<BT_MAXARGS-2;i++){	
		vals[i]=va_arg(ap,value_t*);
	}								
	va_end(ap);
	fmt=sial_ptr(fmt, vals);
	fprintf(ofile, "%s", fmt);
	sial_free(fmt);
	return sial_makebtype(1);
}

#define MAX_SPRINTF 2000
value_t* sial_sprintf(value_t *vfmt, ...)
{
char *fmt=sial_getptr(vfmt, char);
int i;
va_list ap;
value_t *vals[BT_MAXARGS];
value_t *v;

	va_start(ap, vfmt);
	for(i=0;i<BT_MAXARGS-1;i++){
		vals[i]=va_arg(ap,value_t*);
	}
	va_end(ap);
	fmt=sial_ptr(fmt, vals);
	v=sial_setstrval(sial_newval(), fmt);
	sial_free(fmt);
	return v;
}


/*
	When there is a parse error in a file.
*/
void
sial_error(char *fmt, ...)
{
va_list ap;

	sial_setlastfile(filename, sial_line(0));
	va_start(ap, fmt);
	fprintf(ofile, "File %s, line %d, Error: ", filename, sial_line(0));
	vfprintf(ofile, fmt, ap);
	fprintf(ofile, "\n");
	va_end(ap);
	sial_exit(1);
}

void
sial_rerror(srcpos_t *p, char *fmt, ...)
{
va_list ap;

	sial_setlastfile(p->file, p->line);
	va_start(ap, fmt);
	fprintf(ofile, "%s : line %d : Error: ", p->file, p->line);
	vfprintf(ofile, fmt, ap);
	fprintf(ofile, "\n");
	va_end(ap);
	sial_exit(1);
}

void
sial_warning(char *fmt, ...)
{
va_list ap;

	sial_setlastfile(filename, sial_line(0));
	va_start(ap, fmt);
	fprintf(ofile, "%s : line %d : Warning: ", filename, lineno);
	vfprintf(ofile, fmt, ap);
	fprintf(ofile, "\n");
	va_end(ap);
}

void
sial_rwarning(srcpos_t *p, char *fmt, ...)
{
va_list ap;

	sial_setlastfile(p->file, p->line);
	va_start(ap, fmt);
	fprintf(ofile, "%s : line %d : Warning: ", p->file, p->line);
	vfprintf(ofile, fmt, ap);
	fprintf(ofile, "\n");
	va_end(ap);
}

void
sial_vilast()
{
	if(lastfile) {

		sial_exevi(lastfile, lastline);

	} else {

		sial_msg("No last error record available");
	}
}

void
sial_getcomment(void)
{
	while(1) {
	
	unsigned char c;

		while((c=sial_input())!='*' && c!=255) 

		if(c==255) goto bad;

		if((c=sial_input())=='/') return;
		else if(c==255) {
bad:
			sial_error("Unterminated comment!");
		}
	}
}

/* on assignment this function is called to set the new value */
void
sial_setfct(value_t *v1, value_t *v2)
{
	/* duplicate type and data, safeguarding array info */
	sial_dupval(v1, v2);

	/* value_t v1 is still setable */
	v1->set=1;
	v1->setval=v1;
}

node_t *
sial_sibling(node_t *n, node_t *m)
{
node_t *p;

	if(m) {

		for(p=n;p->next;p=p->next);
		p->next=m;
		m->next=0;
	}
	return n;
}

