#include "rc.h"
#include "exec.h"
#include "io.h"
#include "fns.h"

char flagset[] = "<flag>";	/* anything non-nil will do */
char *flag[NFLAG];

/*
 * Start executing the given code at the given pc with the given redirection
 */
char *argv0;

void
start(code *c, int pc, var *local)
{
	struct thread *p = new(struct thread);

	p->code = codecopy(c);
	p->pc = pc;
	p->argv = 0;
	p->redir = p->startredir = runq?runq->redir:0;
	p->local = local;
	p->cmdfile = 0;
	p->cmdfd = 0;
	p->eof = 0;
	p->iflag = 0;
	p->lineno = 1;
	p->ret = runq;
	runq = p;
}

word*
newword(char *wd, word *next)
{
	word *p = new(word);
	p->word = strdup(wd);
	p->next = next;
	return p;
}

void
pushword(char *wd)
{
	if(runq->argv==0)
		panic("pushword but no argv!", 0);
	runq->argv->words = newword(wd, runq->argv->words);
}

void
popword(void)
{
	word *p;
	if(runq->argv==0)
		panic("popword but no argv!", 0);
	p = runq->argv->words;
	if(p==0)
		panic("popword but no word!", 0);
	runq->argv->words = p->next;
	efree(p->word);
	efree((char *)p);
}

void
freelist(word *w)
{
	word *nw;
	while(w){
		nw = w->next;
		efree(w->word);
		efree((char *)w);
		w = nw;
	}
}

void
pushlist(void)
{
	list *p = new(list);
	p->next = runq->argv;
	p->words = 0;
	runq->argv = p;
}

void
poplist(void)
{
	list *p = runq->argv;
	if(p==0)
		panic("poplist but no argv", 0);
	freelist(p->words);
	runq->argv = p->next;
	efree((char *)p);
}

int
count(word *w)
{
	int n;
	for(n = 0;w;n++) w = w->next;
	return n;
}

void
pushredir(int type, int from, int to)
{
	redir * rp = new(redir);
	rp->type = type;
	rp->from = from;
	rp->to = to;
	rp->next = runq->redir;
	runq->redir = rp;
}

void
shuffleredir(void)
{
	redir **rr, *rp;

	rp = runq->redir;
	if(rp==0)
		return;
	runq->redir = rp->next;
	rp->next = runq->startredir;
	for(rr = &runq->redir; *rr != rp->next; rr = &((*rr)->next))
		;
	*rr = rp;
}

var*
newvar(char *name, var *next)
{
	var *v = new(var);

	assert(name != nil);
	v->name = name;
	v->val = 0;
	v->fn = 0;
	v->changed = 0;
	v->fnchanged = 0;
	v->next = next;
	return v;
}

/* fabricate bootstrap code (*=(argv);. /rc/lib/rcmain $*; exit) */
static void
loadboot(code *base, int nel, char *rcmain)
{
	code *bs;

	bs = base;
	bs++->i = 1;			/* reference count */
	bs++->f = Xmark;		/* "* = $*" */
	bs++->f = Xword;
	bs++->s="*";
	bs++->f = Xassign;
	bs++->f = Xmark;
	bs++->f = Xmark;
	bs++->f = Xword;
	bs++->s="*";
	bs++->f = Xdol;
	bs++->f = Xword;
	bs++->s = rcmain;		/* ". /rc/lib/rcmain $*" */
	bs++->f = Xword;
	bs++->s=".";
	bs++->f = Xsimple;
	bs++->f = Xexit;		/* exit */
	bs++->i = 0;			/* not reached */
	if (bs > base + nel)
		panic("bootstrap array too small", 0);
}

void
usage(void)
{
	pfmt(err, "Usage: rc [-srdiIlxepvV] [-c arg] [-m command] "
		"[file [arg ...]]\n");
	Exit("bad flags");
}

/*
 * get command line flags, initialize keywords & traps.
 * get values from environment.
 * set $pid, $cflag, $*
 * fabricate bootstrap code and start it
 * start interpreting code
 */
void
main(int argc, char *argv[])
{
	code bootstrap[17];
	char num[12];
	int i;

	err = openfd(2);
	ARGBEGIN {
	case 'd': case 'e': case 'i': case 'l':
	case 'p': case 'r': case 's': case 'v':
	case 'x': case 'I': case 'V':
		flag[ARGC()] = flagset;
		break;
	case 'c':
	case 'm':
		if (flag[ARGC()])
			usage();
		flag[ARGC()] = EARGF(usage());
		break;
	default:
		usage();
		break;
	} ARGEND
	if(argc < 0)
		usage();
	if(argv0 == nil)
		argv0 = "rc";
	if(argv0[0]=='-')			/* login shell? */
		flag['l'] = flagset;
	if(flag['I'])
		flag['i'] = 0;
	else if(flag['i']==0 && argc==0 && Isatty(0))
		flag['i'] = flagset;		/* force interactive */

	kinit();
	Trapinit();
	Vinit();
	inttoascii(num, mypid = getpid());
	setvar("pid", newword(num, (word *)0));
	setvar("cflag", flag['c']? newword(flag['c'], (word *)0): (word *)0);
	setvar("rcname", newword(argv0, (word *)0));

	loadboot(bootstrap, nelem(bootstrap), (flag['m']? flag['m']: Rcmain));
	start(bootstrap, 1, (var *)0);
	/* prime bootstrap argv */
	pushlist();
	for(i = argc-1; i >= 0; --i)
		pushword(argv[i]);
	for(;;){
		if(flag['r'])
			pfnc(err, runq);
		runq->pc++;
		(*runq->code[runq->pc-1].f)();
		if(ntrap)
			dotrap();
	}
}

/*
 * Opcode routines
 * Arguments on stack (...)
 * Arguments in line [...]
 * Code in line with jump around {...}
 *
 * Xappend(file)[fd]			open file to append
 * Xassign(name, val)			assign val to name
 * Xasync{... Xexit}			make thread for {}, no wait
 * Xbackq(split){... Xreturn}		make thread for {}, push stdout
 * Xbang				complement condition
 * Xcase(pat, value){...}		exec code on match, leave (value) on
 * 					stack
 * Xclose[i]				close file descriptor
 * Xconc(left, right)			concatenate, push results
 * Xcount(name)				push var count
 * Xdelfn(name)				delete function definition
 * Xdelhere
 * Xdol(name)				get variable value
 * Xdup[i j]				dup file descriptor
 * Xeflag
 * Xerror
 * Xexit				rc exits with status
 * Xfalse{...}				execute {} if false
 * Xfn(name){... Xreturn}			define function
 * Xfor(var, list){... Xreturn}		for loop
 * Xglob
 * Xif
 * Xifnot
 * Xjump[addr]				goto
 * Xlocal(name, val)			create local variable, assign value
 * Xmark				mark stack
 * Xmatch(pat, str)			match pattern, set status
 * Xpipe[i j]{... Xreturn}{... Xreturn}	construct a pipe between 2 new threads,
 * 					wait for both
 * Xpipefd[type]{... Xreturn}		connect {} to pipe (input or output,
 * 					depending on type), push /dev/fd/??
 * Xpipewait
 * Xpopm(value)				pop value from stack
 * Xpopredir
 * Xqdol(name)				concatenate variable components
 * Xrdcmds
 * Xrdfn
 * Xrdwr(file)[fd]			open file for reading and writing
 * Xread(file)[fd]			open file to read
 * Xreturn				kill thread
 * Xsimple(args)			run command and wait
 * Xsettrue
 * Xsub
 * Xsubshell{... Xexit}			execute {} in a subshell and wait
 * Xtrue{...}				execute {} if true
 * Xunlocal				delete local variable
 * Xwastrue
 * Xword[string]			push string
 * Xwrite(file)[fd]			open file to write
 */

void
Xappend(void)
{
	char *file;
	int f;
	switch(count(runq->argv->words)){
	default:
		Xerror1(">> requires singleton");
		return;
	case 0:
		Xerror1(">> requires file");
		return;
	case 1:
		break;
	}
	file = runq->argv->words->word;
	if((f = open(file, 1))<0 && (f = Creat(file))<0){
		pfmt(err, "%s: ", file);
		Xerror("can't open");
		return;
	}
	seek(f, 0, 2);
	pushredir(ROPEN, f, runq->code[runq->pc].i);
	runq->pc++;
	poplist();
}

void
Xsettrue(void)
{
	setstatus("");
}

void
Xbang(void)
{
	setstatus(truestatus()?"false":"");
}

void
Xclose(void)
{
	pushredir(RCLOSE, runq->code[runq->pc].i, 0);
	runq->pc++;
}

void
Xdup(void)
{
	pushredir(RDUP, runq->code[runq->pc].i, runq->code[runq->pc+1].i);
	runq->pc+=2;
}

void
Xeflag(void)
{
	if(eflagok && !truestatus()) Xexit();
}

void
Xexit(void)
{
	struct var *trapreq;
	struct word *starval;
	static int beenhere = 0;
	if(getpid()==mypid && !beenhere){
		trapreq = vlook("sigexit");
		if(trapreq->fn){
			beenhere = 1;
			--runq->pc;
			starval = vlook("*")->val;
			start(trapreq->fn, trapreq->pc, (struct var *)0);
			runq->local = newvar(strdup("*"), runq->local);
			runq->local->val = copywords(starval, (struct word *)0);
			runq->local->changed = 1;
			runq->redir = runq->startredir = 0;
			return;
		}
	}
	Exit(getstatus());
}

void
Xfalse(void)
{
	if(truestatus()) runq->pc = runq->code[runq->pc].i;
	else runq->pc++;
}
int ifnot;		/* dynamic if not flag */

void
Xifnot(void)
{
	if(ifnot)
		runq->pc++;
	else
		runq->pc = runq->code[runq->pc].i;
}

void
Xjump(void)
{
	runq->pc = runq->code[runq->pc].i;
}

void
Xmark(void)
{
	pushlist();
}

void
Xpopm(void)
{
	poplist();
}

void
Xread(void)
{
	char *file;
	int f;
	switch(count(runq->argv->words)){
	default:
		Xerror1("< requires singleton\n");
		return;
	case 0:
		Xerror1("< requires file\n");
		return;
	case 1:
		break;
	}
	file = runq->argv->words->word;
	if((f = open(file, 0))<0){
		pfmt(err, "%s: ", file);
		Xerror("can't open");
		return;
	}
	pushredir(ROPEN, f, runq->code[runq->pc].i);
	runq->pc++;
	poplist();
}

void
Xrdwr(void)
{
	char *file;
	int f;

	switch(count(runq->argv->words)){
	default:
		Xerror1("<> requires singleton\n");
		return;
	case 0:
		Xerror1("<> requires file\n");
		return;
	case 1:
		break;
	}
	file = runq->argv->words->word;
	if((f = open(file, ORDWR))<0){
		pfmt(err, "%s: ", file);
		Xerror("can't open");
		return;
	}
	pushredir(ROPEN, f, runq->code[runq->pc].i);
	runq->pc++;
	poplist();
}

void
turfredir(void)
{
	while(runq->redir!=runq->startredir)
		Xpopredir();
}

void
Xpopredir(void)
{
	struct redir *rp = runq->redir;
	if(rp==0)
		panic("turfredir null!", 0);
	runq->redir = rp->next;
	if(rp->type==ROPEN)
		close(rp->from);
	efree((char *)rp);
}

void
Xreturn(void)
{
	struct thread *p = runq;
	turfredir();
	while(p->argv) poplist();
	codefree(p->code);
	runq = p->ret;
	efree((char *)p);
	if(runq==0)
		Exit(getstatus());
}

void
Xtrue(void)
{
	if(truestatus()) runq->pc++;
	else runq->pc = runq->code[runq->pc].i;
}

void
Xif(void)
{
	ifnot = 1;
	if(truestatus()) runq->pc++;
	else runq->pc = runq->code[runq->pc].i;
}

void
Xwastrue(void)
{
	ifnot = 0;
}

void
Xword(void)
{
	pushword(runq->code[runq->pc++].s);
}

void
Xwrite(void)
{
	char *file;
	int f;
	switch(count(runq->argv->words)){
	default:
		Xerror1("> requires singleton\n");
		return;
	case 0:
		Xerror1("> requires file\n");
		return;
	case 1:
		break;
	}
	file = runq->argv->words->word;
	if((f = Creat(file))<0){
		pfmt(err, "%s: ", file);
		Xerror("can't open");
		return;
	}
	pushredir(ROPEN, f, runq->code[runq->pc].i);
	runq->pc++;
	poplist();
}

char*
list2str(word *words)
{
	char *value, *s, *t;
	int len = 0;
	word *ap;
	for(ap = words;ap;ap = ap->next)
		len+=1+strlen(ap->word);
	value = emalloc(len+1);
	s = value;
	for(ap = words;ap;ap = ap->next){
		for(t = ap->word;*t;) *s++=*t++;
		*s++=' ';
	}
	if(s==value)
		*s='\0';
	else s[-1]='\0';
	return value;
}

void
Xmatch(void)
{
	word *p;
	char *subject;
	subject = list2str(runq->argv->words);
	setstatus("no match");
	for(p = runq->argv->next->words;p;p = p->next)
		if(match(subject, p->word, '\0')){
			setstatus("");
			break;
		}
	efree(subject);
	poplist();
	poplist();
}

void
Xcase(void)
{
	word *p;
	char *s;
	int ok = 0;
	s = list2str(runq->argv->next->words);
	for(p = runq->argv->words;p;p = p->next){
		if(match(s, p->word, '\0')){
			ok = 1;
			break;
		}
	}
	efree(s);
	if(ok)
		runq->pc++;
	else
		runq->pc = runq->code[runq->pc].i;
	poplist();
}

word*
conclist(word *lp, word *rp, word *tail)
{
	char *buf;
	word *v;
	if(lp->next || rp->next)
		tail = conclist(lp->next==0? lp: lp->next,
			rp->next==0? rp: rp->next, tail);
	buf = emalloc(strlen(lp->word)+strlen((char *)rp->word)+1);
	strcpy(buf, lp->word);
	strcat(buf, rp->word);
	v = newword(buf, tail);
	efree(buf);
	return v;
}

void
Xconc(void)
{
	word *lp = runq->argv->words;
	word *rp = runq->argv->next->words;
	word *vp = runq->argv->next->next->words;
	int lc = count(lp), rc = count(rp);
	if(lc!=0 || rc!=0){
		if(lc==0 || rc==0){
			Xerror1("null list in concatenation");
			return;
		}
		if(lc!=1 && rc!=1 && lc!=rc){
			Xerror1("mismatched list lengths in concatenation");
			return;
		}
		vp = conclist(lp, rp, vp);
	}
	poplist();
	poplist();
	runq->argv->words = vp;
}

void
Xassign(void)
{
	var *v;
	if(count(runq->argv->words)!=1){
		Xerror1("variable name not singleton!");
		return;
	}
	deglob(runq->argv->words->word);
	v = vlook(runq->argv->words->word);
	poplist();
	globlist();
	freewords(v->val);
	v->val = runq->argv->words;
	v->changed = 1;
	runq->argv->words = 0;
	poplist();
}
/*
 * copy arglist a, adding the copy to the front of tail
 */

word*
copywords(word *a, word *tail)
{
	word *v = 0, **end;
	for(end=&v;a;a = a->next,end=&(*end)->next)
		*end = newword(a->word, 0);
	*end = tail;
	return v;
}

void
Xdol(void)
{
	word *a, *star;
	char *s, *t;
	int n;
	if(count(runq->argv->words)!=1){
		Xerror1("variable name not singleton!");
		return;
	}
	s = runq->argv->words->word;
	deglob(s);
	n = 0;
	for(t = s;'0'<=*t && *t<='9';t++) n = n*10+*t-'0';
	a = runq->argv->next->words;
	if(n==0 || *t)
		a = copywords(vlook(s)->val, a);
	else{
		star = vlook("*")->val;
		if(star && 1<=n && n<=count(star)){
			while(--n) star = star->next;
			a = newword(star->word, a);
		}
	}
	poplist();
	runq->argv->words = a;
}

void
Xqdol(void)
{
	word *a, *p;
	char *s;
	int n;
	if(count(runq->argv->words)!=1){
		Xerror1("variable name not singleton!");
		return;
	}
	s = runq->argv->words->word;
	deglob(s);
	a = vlook(s)->val;
	poplist();
	n = count(a);
	if(n==0){
		pushword("");
		return;
	}
	for(p = a;p;p = p->next) n+=strlen(p->word);
	s = emalloc(n);
	if(a){
		strcpy(s, a->word);
		for(p = a->next;p;p = p->next){
			strcat(s, " ");
			strcat(s, p->word);
		}
	}
	else
		s[0]='\0';
	pushword(s);
	efree(s);
}

word*
copynwords(word *a, word *tail, int n)
{
	word *v, **end;

	v = 0;
	end = &v;
	while(n-- > 0){
		*end = newword(a->word, 0);
		end = &(*end)->next;
		a = a->next;
	}
	*end = tail;
	return v;
}

word*
subwords(word *val, int len, word *sub, word *a)
{
	int n, m;
	char *s;
	if(!sub)
		return a;
	a = subwords(val, len, sub->next, a);
	s = sub->word;
	deglob(s);
	m = 0;
	n = 0;
	while('0'<=*s && *s<='9')
		n = n*10+ *s++ -'0';
	if(*s == '-'){
		if(*++s == 0)
			m = len - n;
		else{
			while('0'<=*s && *s<='9')
				m = m*10+ *s++ -'0';
			m -= n;
		}
	}
	if(n<1 || n>len || m<0)
		return a;
	if(n+m>len)
		m = len-n;
	while(--n > 0)
		val = val->next;
	return copynwords(val, a, m+1);
}

void
Xsub(void)
{
	word *a, *v;
	char *s;
	if(count(runq->argv->next->words)!=1){
		Xerror1("variable name not singleton!");
		return;
	}
	s = runq->argv->next->words->word;
	deglob(s);
	a = runq->argv->next->next->words;
	v = vlook(s)->val;
	a = subwords(v, count(v), runq->argv->words, a);
	poplist();
	poplist();
	runq->argv->words = a;
}

void
Xcount(void)
{
	word *a;
	char *s, *t;
	int n;
	char num[12];
	if(count(runq->argv->words)!=1){
		Xerror1("variable name not singleton!");
		return;
	}
	s = runq->argv->words->word;
	deglob(s);
	n = 0;
	for(t = s;'0'<=*t && *t<='9';t++) n = n*10+*t-'0';
	if(n==0 || *t){
		a = vlook(s)->val;
		inttoascii(num, count(a));
	}
	else{
		a = vlook("*")->val;
		inttoascii(num, a && 1<=n && n<=count(a)?1:0);
	}
	poplist();
	pushword(num);
}

void
Xlocal(void)
{
	if(count(runq->argv->words)!=1){
		Xerror1("variable name must be singleton\n");
		return;
	}
	deglob(runq->argv->words->word);
	runq->local = newvar(strdup(runq->argv->words->word), runq->local);
	poplist();
	globlist();
	runq->local->val = runq->argv->words;
	runq->local->changed = 1;
	runq->argv->words = 0;
	poplist();
}

void
Xunlocal(void)
{
	var *v = runq->local, *hid;
	if(v==0)
		panic("Xunlocal: no locals!", 0);
	runq->local = v->next;
	hid = vlook(v->name);
	hid->changed = 1;
	efree(v->name);
	freewords(v->val);
	efree((char *)v);
}

void
freewords(word *w)
{
	word *nw;
	while(w){
		efree(w->word);
		nw = w->next;
		efree((char *)w);
		w = nw;
	}
}

void
Xfn(void)
{
	var *v;
	word *a;
	int end;
	end = runq->code[runq->pc].i;
	globlist();
	for(a = runq->argv->words;a;a = a->next){
		v = gvlook(a->word);
		if(v->fn)
			codefree(v->fn);
		v->fn = codecopy(runq->code);
		v->pc = runq->pc+2;
		v->fnchanged = 1;
	}
	runq->pc = end;
	poplist();
}

void
Xdelfn(void)
{
	var *v;
	word *a;
	for(a = runq->argv->words;a;a = a->next){
		v = gvlook(a->word);
		if(v->fn)
			codefree(v->fn);
		v->fn = 0;
		v->fnchanged = 1;
	}
	poplist();
}

char*
concstatus(char *s, char *t)
{
	static char v[NSTATUS+1];
	int n = strlen(s);
	strncpy(v, s, NSTATUS);
	if(n<NSTATUS){
		v[n]='|';
		strncpy(v+n+1, t, NSTATUS-n-1);
	}
	v[NSTATUS]='\0';
	return v;
}

void
Xpipewait(void)
{
	char status[NSTATUS+1];
	if(runq->pid==-1)
		setstatus(concstatus(runq->status, getstatus()));
	else{
		strncpy(status, getstatus(), NSTATUS);
		status[NSTATUS]='\0';
		Waitfor(runq->pid, 1);
		runq->pid=-1;
		setstatus(concstatus(getstatus(), status));
	}
}

void
Xrdcmds(void)
{
	struct thread *p = runq;
	word *prompt;
	flush(err);
	nerror = 0;
	if(flag['s'] && !truestatus())
		pfmt(err, "status=%v\n", vlook("status")->val);
	if(runq->iflag){
		prompt = vlook("prompt")->val;
		if(prompt)
			promptstr = prompt->word;
		else
			promptstr="% ";
	}
	Noerror();
	if(yyparse()){
		if(!p->iflag || p->eof && !Eintr()){
			if(p->cmdfile)
				efree(p->cmdfile);
			closeio(p->cmdfd);
			Xreturn();	/* should this be omitted? */
		}
		else{
			if(Eintr()){
				pchr(err, '\n');
				p->eof = 0;
			}
			--p->pc;	/* go back for next command */
		}
	}
	else{
		ntrap = 0;	/* avoid double-interrupts during blocked writes */
		--p->pc;	/* re-execute Xrdcmds after codebuf runs */
		start(codebuf, 1, runq->local);
	}
	freenodes();
}

void
pargv0(io *f)
{
	if(strcmp(argv0, "rc")==0 || strcmp(argv0, "/bin/rc")==0)
		pfmt(f, "rc: ");
	else
		pfmt(f, "rc (%s): ", argv0);
}

void
hisfault(io *f)
{
	thread *t;

	for(t = runq; !t->cmdfile && t->ret != 0; t = t->ret)
		;
	if(t->cmdfile && !t->iflag)
		pfmt(f, "%s:%d ", t->cmdfile, t->lineno);
}

void
Xerror(char *s)
{
	io *msg = openstr();

	pargv0(msg);
	hisfault(msg);		/* capture errstr before another sys call */
	pfmt(err, "%s%s: %r\n", (char *)msg->strp, s);
	closeio(msg);
	flush(err);
	setstatus("error");
	while(!runq->iflag) Xreturn();
}

void
Xerror1(char *s)			/* omit errstr */
{
	pargv0(err);
	hisfault(err);
	pfmt(err, "%s\n", s);
	flush(err);
	setstatus("error");
	while(!runq->iflag) Xreturn();
}

void
setstatus(char *s)
{
	setvar("status", newword(s, (word *)0));
}

char*
getstatus(void)
{
	var *status = vlook("status");
	return status->val?status->val->word:"";
}

int
truestatus(void)
{
	char *s;
	for(s = getstatus();*s;s++)
		if(*s!='|' && *s!='0')
			return 0;
	return 1;
}

void
Xdelhere(void)
{
	Unlink(runq->code[runq->pc++].s);
}

void
Xfor(void)
{
	if(runq->argv->words==0){
		poplist();
		runq->pc = runq->code[runq->pc].i;
	}
	else{
		freelist(runq->local->val);
		runq->local->val = runq->argv->words;
		runq->local->changed = 1;
		runq->argv->words = runq->argv->words->next;
		runq->local->val->next = 0;
		runq->pc++;
	}
}

void
Xglob(void)
{
	globlist();
}
