| #include "sam.h" | 
 | #include "parse.h" | 
 |  | 
 | static char	linex[]="\n"; | 
 | static char	wordx[]=" \t\n"; | 
 | struct cmdtab cmdtab[]={ | 
 | /*	cmdc	text	regexp	addr	defcmd	defaddr	count	token	 fn	*/ | 
 | 	'\n',	0,	0,	0,	0,	aDot,	0,	0,	nl_cmd, | 
 | 	'a',	1,	0,	0,	0,	aDot,	0,	0,	a_cmd, | 
 | 	'b',	0,	0,	0,	0,	aNo,	0,	linex,	b_cmd, | 
 | 	'B',	0,	0,	0,	0,	aNo,	0,	linex,	b_cmd, | 
 | 	'c',	1,	0,	0,	0,	aDot,	0,	0,	c_cmd, | 
 | 	'd',	0,	0,	0,	0,	aDot,	0,	0,	d_cmd, | 
 | 	'D',	0,	0,	0,	0,	aNo,	0,	linex,	D_cmd, | 
 | 	'e',	0,	0,	0,	0,	aNo,	0,	wordx,	e_cmd, | 
 | 	'f',	0,	0,	0,	0,	aNo,	0,	wordx,	f_cmd, | 
 | 	'g',	0,	1,	0,	'p',	aDot,	0,	0,	g_cmd, | 
 | 	'i',	1,	0,	0,	0,	aDot,	0,	0,	i_cmd, | 
 | 	'k',	0,	0,	0,	0,	aDot,	0,	0,	k_cmd, | 
 | 	'm',	0,	0,	1,	0,	aDot,	0,	0,	m_cmd, | 
 | 	'n',	0,	0,	0,	0,	aNo,	0,	0,	n_cmd, | 
 | 	'p',	0,	0,	0,	0,	aDot,	0,	0,	p_cmd, | 
 | 	'q',	0,	0,	0,	0,	aNo,	0,	0,	q_cmd, | 
 | 	'r',	0,	0,	0,	0,	aDot,	0,	wordx,	e_cmd, | 
 | 	's',	0,	1,	0,	0,	aDot,	1,	0,	s_cmd, | 
 | 	't',	0,	0,	1,	0,	aDot,	0,	0,	m_cmd, | 
 | 	'u',	0,	0,	0,	0,	aNo,	2,	0,	u_cmd, | 
 | 	'v',	0,	1,	0,	'p',	aDot,	0,	0,	g_cmd, | 
 | 	'w',	0,	0,	0,	0,	aAll,	0,	wordx,	w_cmd, | 
 | 	'x',	0,	1,	0,	'p',	aDot,	0,	0,	x_cmd, | 
 | 	'y',	0,	1,	0,	'p',	aDot,	0,	0,	x_cmd, | 
 | 	'X',	0,	1,	0,	'f',	aNo,	0,	0,	X_cmd, | 
 | 	'Y',	0,	1,	0,	'f',	aNo,	0,	0,	X_cmd, | 
 | 	'!',	0,	0,	0,	0,	aNo,	0,	linex,	plan9_cmd, | 
 | 	'>',	0,	0,	0,	0,	aDot,	0,	linex,	plan9_cmd, | 
 | 	'<',	0,	0,	0,	0,	aDot,	0,	linex,	plan9_cmd, | 
 | 	'|',	0,	0,	0,	0,	aDot,	0,	linex,	plan9_cmd, | 
 | 	'=',	0,	0,	0,	0,	aDot,	0,	linex,	eq_cmd, | 
 | 	'c'|0x100,0,	0,	0,	0,	aNo,	0,	wordx,	cd_cmd, | 
 | 	0,	0,	0,	0,	0,	0,	0,	0 | 
 | }; | 
 | Cmd	*parsecmd(int); | 
 | Addr	*compoundaddr(void); | 
 | Addr	*simpleaddr(void); | 
 | void	freecmd(void); | 
 | void	okdelim(int); | 
 |  | 
 | Rune	line[BLOCKSIZE]; | 
 | Rune	termline[BLOCKSIZE]; | 
 | Rune	*linep = line; | 
 | Rune	*terminp = termline; | 
 | Rune	*termoutp = termline; | 
 |  | 
 | List	cmdlist = { 'p' }; | 
 | List	addrlist = { 'p' }; | 
 | List	relist = { 'p' }; | 
 | List	stringlist = { 'p' }; | 
 |  | 
 | int	eof; | 
 |  | 
 | void | 
 | resetcmd(void) | 
 | { | 
 | 	linep = line; | 
 | 	*linep = 0; | 
 | 	terminp = termoutp = termline; | 
 | 	freecmd(); | 
 | } | 
 |  | 
 | int | 
 | inputc(void) | 
 | { | 
 | 	int n, nbuf; | 
 | 	char buf[UTFmax]; | 
 | 	Rune r; | 
 |  | 
 |     Again: | 
 | 	nbuf = 0; | 
 | 	if(downloaded){ | 
 | 		while(termoutp == terminp){ | 
 | 			cmdupdate(); | 
 | 			if(patset) | 
 | 				tellpat(); | 
 | 			while(termlocked > 0){ | 
 | 				outT0(Hunlock); | 
 | 				termlocked--; | 
 | 			} | 
 | 			if(rcv() == 0) | 
 | 				return -1; | 
 | 		} | 
 | 		r = *termoutp++; | 
 | 		if(termoutp == terminp) | 
 | 			terminp = termoutp = termline; | 
 | 	}else{ | 
 |    		do{ | 
 | 			n = read(0, buf+nbuf, 1); | 
 | 			if(n <= 0) | 
 | 				return -1; | 
 | 			nbuf += n; | 
 | 		}while(!fullrune(buf, nbuf)); | 
 | 		chartorune(&r, buf); | 
 | 	} | 
 | 	if(r == 0){ | 
 | 		warn(Wnulls); | 
 | 		goto Again; | 
 | 	} | 
 | 	return r; | 
 | } | 
 |  | 
 | int | 
 | inputline(void) | 
 | { | 
 | 	int i, c, start; | 
 |  | 
 | 	/* | 
 | 	 * Could set linep = line and i = 0 here and just | 
 | 	 * error(Etoolong) below, but this way we keep | 
 | 	 * old input buffer history around for a while. | 
 | 	 * This is useful only for debugging. | 
 | 	 */ | 
 | 	i = linep - line; | 
 | 	do{ | 
 | 		if((c = inputc())<=0) | 
 | 			return -1; | 
 | 		if(i == nelem(line)-1){ | 
 | 			if(linep == line) | 
 | 				error(Etoolong); | 
 | 			start = linep - line; | 
 | 			runemove(line, linep, i-start); | 
 | 			i -= start; | 
 | 			linep = line; | 
 | 		} | 
 | 	}while((line[i++]=c) != '\n'); | 
 | 	line[i] = 0; | 
 | 	return 1; | 
 | } | 
 |  | 
 | int | 
 | getch(void) | 
 | { | 
 | 	if(eof) | 
 | 		return -1; | 
 | 	if(*linep==0 && inputline()<0){ | 
 | 		eof = TRUE; | 
 | 		return -1; | 
 | 	} | 
 | 	return *linep++; | 
 | } | 
 |  | 
 | int | 
 | nextc(void) | 
 | { | 
 | 	if(*linep == 0) | 
 | 		return -1; | 
 | 	return *linep; | 
 | } | 
 |  | 
 | void | 
 | ungetch(void) | 
 | { | 
 | 	if(--linep < line) | 
 | 		panic("ungetch"); | 
 | } | 
 |  | 
 | Posn | 
 | getnum(int signok) | 
 | { | 
 | 	Posn n=0; | 
 | 	int c, sign; | 
 |  | 
 | 	sign = 1; | 
 | 	if(signok>1 && nextc()=='-'){ | 
 | 		sign = -1; | 
 | 		getch(); | 
 | 	} | 
 | 	if((c=nextc())<'0' || '9'<c)	/* no number defaults to 1 */ | 
 | 		return sign; | 
 | 	while('0'<=(c=getch()) && c<='9') | 
 | 		n = n*10 + (c-'0'); | 
 | 	ungetch(); | 
 | 	return sign*n; | 
 | } | 
 |  | 
 | int | 
 | skipbl(void) | 
 | { | 
 | 	int c; | 
 | 	do | 
 | 		c = getch(); | 
 | 	while(c==' ' || c=='\t'); | 
 | 	if(c >= 0) | 
 | 		ungetch(); | 
 | 	return c; | 
 | } | 
 |  | 
 | void | 
 | termcommand(void) | 
 | { | 
 | 	Posn p; | 
 |  | 
 | 	for(p=cmdpt; p<cmd->b.nc; p++){ | 
 | 		if(terminp >= &termline[BLOCKSIZE]){ | 
 | 			cmdpt = cmd->b.nc; | 
 | 			error(Etoolong); | 
 | 		} | 
 | 		*terminp++ = filereadc(cmd, p); | 
 | 	} | 
 | 	cmdpt = cmd->b.nc; | 
 | } | 
 |  | 
 | void | 
 | cmdloop(void) | 
 | { | 
 | 	Cmd *cmdp; | 
 | 	File *ocurfile; | 
 | 	int loaded; | 
 |  | 
 | 	for(;;){ | 
 | 		if(!downloaded && curfile && curfile->unread) | 
 | 			load(curfile); | 
 | 		if((cmdp = parsecmd(0))==0){ | 
 | 			if(downloaded){ | 
 | 				rescue(); | 
 | 				exits("eof"); | 
 | 			} | 
 | 			break; | 
 | 		} | 
 | 		ocurfile = curfile; | 
 | 		loaded = curfile && !curfile->unread; | 
 | 		if(cmdexec(curfile, cmdp) == 0) | 
 | 			break; | 
 | 		freecmd(); | 
 | 		cmdupdate(); | 
 | 		update(); | 
 | 		if(downloaded && curfile && | 
 | 		    (ocurfile!=curfile || (!loaded && !curfile->unread))) | 
 | 			outTs(Hcurrent, curfile->tag); | 
 | 			/* don't allow type ahead on files that aren't bound */ | 
 | 		if(downloaded && curfile && curfile->rasp == 0) | 
 | 			terminp = termoutp; | 
 | 	} | 
 | } | 
 |  | 
 | Cmd * | 
 | newcmd(void){ | 
 | 	Cmd *p; | 
 |  | 
 | 	p = emalloc(sizeof(Cmd)); | 
 | 	inslist(&cmdlist, cmdlist.nused, (long)p); | 
 | 	return p; | 
 | } | 
 |  | 
 | Addr* | 
 | newaddr(void) | 
 | { | 
 | 	Addr *p; | 
 |  | 
 | 	p = emalloc(sizeof(Addr)); | 
 | 	inslist(&addrlist, addrlist.nused, (long)p); | 
 | 	return p; | 
 | } | 
 |  | 
 | String* | 
 | newre(void) | 
 | { | 
 | 	String *p; | 
 |  | 
 | 	p = emalloc(sizeof(String)); | 
 | 	inslist(&relist, relist.nused, (long)p); | 
 | 	Strinit(p); | 
 | 	return p; | 
 | } | 
 |  | 
 | String* | 
 | newstring(void) | 
 | { | 
 | 	String *p; | 
 |  | 
 | 	p = emalloc(sizeof(String)); | 
 | 	inslist(&stringlist, stringlist.nused, (long)p); | 
 | 	Strinit(p); | 
 | 	return p; | 
 | } | 
 |  | 
 | void | 
 | freecmd(void) | 
 | { | 
 | 	int i; | 
 |  | 
 | 	while(cmdlist.nused > 0) | 
 | 		free(cmdlist.voidpptr[--cmdlist.nused]); | 
 | 	while(addrlist.nused > 0) | 
 | 		free(addrlist.voidpptr[--addrlist.nused]); | 
 | 	while(relist.nused > 0){ | 
 | 		i = --relist.nused; | 
 | 		Strclose(relist.stringpptr[i]); | 
 | 		free(relist.stringpptr[i]); | 
 | 	} | 
 | 	while(stringlist.nused>0){ | 
 | 		i = --stringlist.nused; | 
 | 		Strclose(stringlist.stringpptr[i]); | 
 | 		free(stringlist.stringpptr[i]); | 
 | 	} | 
 | } | 
 |  | 
 | int | 
 | lookup(int c) | 
 | { | 
 | 	int i; | 
 |  | 
 | 	for(i=0; cmdtab[i].cmdc; i++) | 
 | 		if(cmdtab[i].cmdc == c) | 
 | 			return i; | 
 | 	return -1; | 
 | } | 
 |  | 
 | void | 
 | okdelim(int c) | 
 | { | 
 | 	if(c=='\\' || ('a'<=c && c<='z') | 
 | 	|| ('A'<=c && c<='Z') || ('0'<=c && c<='9')) | 
 | 		error_c(Edelim, c); | 
 | } | 
 |  | 
 | void | 
 | atnl(void) | 
 | { | 
 | 	skipbl(); | 
 | 	if(getch() != '\n') | 
 | 		error(Enewline); | 
 | } | 
 |  | 
 | void | 
 | getrhs(String *s, int delim, int cmd) | 
 | { | 
 | 	int c; | 
 |  | 
 | 	while((c = getch())>0 && c!=delim && c!='\n'){ | 
 | 		if(c == '\\'){ | 
 | 			if((c=getch()) <= 0) | 
 | 				error(Ebadrhs); | 
 | 			if(c == '\n'){ | 
 | 				ungetch(); | 
 | 				c='\\'; | 
 | 			}else if(c == 'n') | 
 | 				c='\n'; | 
 | 			else if(c!=delim && (cmd=='s' || c!='\\'))	/* s does its own */ | 
 | 				Straddc(s, '\\'); | 
 | 		} | 
 | 		Straddc(s, c); | 
 | 	} | 
 | 	ungetch();	/* let client read whether delimeter, '\n' or whatever */ | 
 | } | 
 |  | 
 | String * | 
 | collecttoken(char *end) | 
 | { | 
 | 	String *s = newstring(); | 
 | 	int c; | 
 |  | 
 | 	while((c=nextc())==' ' || c=='\t') | 
 | 		Straddc(s, getch()); /* blanks significant for getname() */ | 
 | 	while((c=getch())>0 && utfrune(end, c)==0) | 
 | 		Straddc(s, c); | 
 | 	Straddc(s, 0); | 
 | 	if(c != '\n') | 
 | 		atnl(); | 
 | 	return s; | 
 | } | 
 |  | 
 | String * | 
 | collecttext(void) | 
 | { | 
 | 	String *s = newstring(); | 
 | 	int begline, i, c, delim; | 
 |  | 
 | 	if(skipbl()=='\n'){ | 
 | 		getch(); | 
 | 		i = 0; | 
 | 		do{ | 
 | 			begline = i; | 
 | 			while((c = getch())>0 && c!='\n') | 
 | 				i++, Straddc(s, c); | 
 | 			i++, Straddc(s, '\n'); | 
 | 			if(c < 0) | 
 | 				goto Return; | 
 | 		}while(s->s[begline]!='.' || s->s[begline+1]!='\n'); | 
 | 		Strdelete(s, s->n-2, s->n); | 
 | 	}else{ | 
 | 		okdelim(delim = getch()); | 
 | 		getrhs(s, delim, 'a'); | 
 | 		if(nextc()==delim) | 
 | 			getch(); | 
 | 		atnl(); | 
 | 	} | 
 |     Return: | 
 | 	Straddc(s, 0);		/* JUST FOR CMDPRINT() */ | 
 | 	return s; | 
 | } | 
 |  | 
 | Cmd * | 
 | parsecmd(int nest) | 
 | { | 
 | 	int i, c; | 
 | 	struct cmdtab *ct; | 
 | 	Cmd *cp, *ncp; | 
 | 	Cmd cmd; | 
 |  | 
 | 	cmd.next = cmd.ccmd = 0; | 
 | 	cmd.re = 0; | 
 | 	cmd.flag = cmd.num = 0; | 
 | 	cmd.addr = compoundaddr(); | 
 | 	if(skipbl() == -1) | 
 | 		return 0; | 
 | 	if((c=getch())==-1) | 
 | 		return 0; | 
 | 	cmd.cmdc = c; | 
 | 	if(cmd.cmdc=='c' && nextc()=='d'){	/* sleazy two-character case */ | 
 | 		getch();		/* the 'd' */ | 
 | 		cmd.cmdc='c'|0x100; | 
 | 	} | 
 | 	i = lookup(cmd.cmdc); | 
 | 	if(i >= 0){ | 
 | 		if(cmd.cmdc == '\n') | 
 | 			goto Return;	/* let nl_cmd work it all out */ | 
 | 		ct = &cmdtab[i]; | 
 | 		if(ct->defaddr==aNo && cmd.addr) | 
 | 			error(Enoaddr); | 
 | 		if(ct->count) | 
 | 			cmd.num = getnum(ct->count); | 
 | 		if(ct->regexp){ | 
 | 			/* x without pattern -> .*\n, indicated by cmd.re==0 */ | 
 | 			/* X without pattern is all files */ | 
 | 			if((ct->cmdc!='x' && ct->cmdc!='X') || | 
 | 			   ((c = nextc())!=' ' && c!='\t' && c!='\n')){ | 
 | 				skipbl(); | 
 | 				if((c = getch())=='\n' || c<0) | 
 | 					error(Enopattern); | 
 | 				okdelim(c); | 
 | 				cmd.re = getregexp(c); | 
 | 				if(ct->cmdc == 's'){ | 
 | 					cmd.ctext = newstring(); | 
 | 					getrhs(cmd.ctext, c, 's'); | 
 | 					if(nextc() == c){ | 
 | 						getch(); | 
 | 						if(nextc() == 'g') | 
 | 							cmd.flag = getch(); | 
 | 					} | 
 | 			 | 
 | 				} | 
 | 			} | 
 | 		} | 
 | 		if(ct->addr && (cmd.caddr=simpleaddr())==0) | 
 | 			error(Eaddress); | 
 | 		if(ct->defcmd){ | 
 | 			if(skipbl() == '\n'){ | 
 | 				getch(); | 
 | 				cmd.ccmd = newcmd(); | 
 | 				cmd.ccmd->cmdc = ct->defcmd; | 
 | 			}else if((cmd.ccmd = parsecmd(nest))==0) | 
 | 				panic("defcmd"); | 
 | 		}else if(ct->text) | 
 | 			cmd.ctext = collecttext(); | 
 | 		else if(ct->token) | 
 | 			cmd.ctext = collecttoken(ct->token); | 
 | 		else | 
 | 			atnl(); | 
 | 	}else | 
 | 		switch(cmd.cmdc){ | 
 | 		case '{': | 
 | 			cp = 0; | 
 | 			do{ | 
 | 				if(skipbl()=='\n') | 
 | 					getch(); | 
 | 				ncp = parsecmd(nest+1); | 
 | 				if(cp) | 
 | 					cp->next = ncp; | 
 | 				else | 
 | 					cmd.ccmd = ncp; | 
 | 			}while(cp = ncp); | 
 | 			break; | 
 | 		case '}': | 
 | 			atnl(); | 
 | 			if(nest==0) | 
 | 				error(Enolbrace); | 
 | 			return 0; | 
 | 		default: | 
 | 			error_c(Eunk, cmd.cmdc); | 
 | 		} | 
 |     Return: | 
 | 	cp = newcmd(); | 
 | 	*cp = cmd; | 
 | 	return cp; | 
 | } | 
 |  | 
 | String*				/* BUGGERED */ | 
 | getregexp(int delim) | 
 | { | 
 | 	String *r = newre(); | 
 | 	int c; | 
 |  | 
 | 	for(Strzero(&genstr); ; Straddc(&genstr, c)) | 
 | 		if((c = getch())=='\\'){ | 
 | 			if(nextc()==delim) | 
 | 				c = getch(); | 
 | 			else if(nextc()=='\\'){ | 
 | 				Straddc(&genstr, c); | 
 | 				c = getch(); | 
 | 			} | 
 | 		}else if(c==delim || c=='\n') | 
 | 			break; | 
 | 	if(c!=delim && c) | 
 | 		ungetch(); | 
 | 	if(genstr.n > 0){ | 
 | 		patset = TRUE; | 
 | 		Strduplstr(&lastpat, &genstr); | 
 | 		Straddc(&lastpat, '\0'); | 
 | 	} | 
 | 	if(lastpat.n <= 1) | 
 | 		error(Epattern); | 
 | 	Strduplstr(r, &lastpat); | 
 | 	return r; | 
 | } | 
 |  | 
 | Addr * | 
 | simpleaddr(void) | 
 | { | 
 | 	Addr addr; | 
 | 	Addr *ap, *nap; | 
 |  | 
 | 	addr.next = 0; | 
 | 	addr.left = 0; | 
 | 	addr.num = 0; | 
 | 	switch(skipbl()){ | 
 | 	case '#': | 
 | 		addr.type = getch(); | 
 | 		addr.num = getnum(1); | 
 | 		break; | 
 | 	case '0': case '1': case '2': case '3': case '4': | 
 | 	case '5': case '6': case '7': case '8': case '9':  | 
 | 		addr.num = getnum(1); | 
 | 		addr.type='l'; | 
 | 		break; | 
 | 	case '/': case '?': case '"': | 
 | 		addr.are = getregexp(addr.type = getch()); | 
 | 		break; | 
 | 	case '.': | 
 | 	case '$': | 
 | 	case '+': | 
 | 	case '-': | 
 | 	case '\'': | 
 | 		addr.type = getch(); | 
 | 		break; | 
 | 	default: | 
 | 		return 0; | 
 | 	} | 
 | 	if(addr.next = simpleaddr()) | 
 | 		switch(addr.next->type){ | 
 | 		case '.': | 
 | 		case '$': | 
 | 		case '\'': | 
 | 			if(addr.type!='"') | 
 | 		case '"': | 
 | 				error(Eaddress); | 
 | 			break; | 
 | 		case 'l': | 
 | 		case '#': | 
 | 			if(addr.type=='"') | 
 | 				break; | 
 | 			/* fall through */ | 
 | 		case '/': | 
 | 		case '?': | 
 | 			if(addr.type!='+' && addr.type!='-'){ | 
 | 				/* insert the missing '+' */ | 
 | 				nap = newaddr(); | 
 | 				nap->type='+'; | 
 | 				nap->next = addr.next; | 
 | 				addr.next = nap; | 
 | 			} | 
 | 			break; | 
 | 		case '+': | 
 | 		case '-': | 
 | 			break; | 
 | 		default: | 
 | 			panic("simpleaddr"); | 
 | 		} | 
 | 	ap = newaddr(); | 
 | 	*ap = addr; | 
 | 	return ap; | 
 | } | 
 |  | 
 | Addr * | 
 | compoundaddr(void) | 
 | { | 
 | 	Addr addr; | 
 | 	Addr *ap, *next; | 
 |  | 
 | 	addr.left = simpleaddr(); | 
 | 	if((addr.type = skipbl())!=',' && addr.type!=';') | 
 | 		return addr.left; | 
 | 	getch(); | 
 | 	next = addr.next = compoundaddr(); | 
 | 	if(next && (next->type==',' || next->type==';') && next->left==0) | 
 | 		error(Eaddress); | 
 | 	ap = newaddr(); | 
 | 	*ap = addr; | 
 | 	return ap; | 
 | } |