| #include <u.h> | 
 | #include <libc.h> | 
 | #include <draw.h> | 
 | #include <thread.h> | 
 | #include <cursor.h> | 
 | #include <mouse.h> | 
 | #include <keyboard.h> | 
 | #include <frame.h> | 
 | #include <fcall.h> | 
 | #include <plumb.h> | 
 | #include "dat.h" | 
 | #include "edit.h" | 
 | #include "fns.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, | 
 | 	'c',	1,	0,	0,	0,	aDot,	0,	0,	c_cmd, | 
 | 	'd',	0,	0,	0,	0,	aDot,	0,	0,	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, | 
 | 	'm',	0,	0,	1,	0,	aDot,	0,	0,	m_cmd, | 
 | 	'p',	0,	0,	0,	0,	aDot,	0,	0,	p_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, | 
 | 	'=',	0,	0,	0,	0,	aDot,	0,	linex,	eq_cmd, | 
 | 	'B',	0,	0,	0,	0,	aNo,	0,	linex,	B_cmd, | 
 | 	'D',	0,	0,	0,	0,	aNo,	0,	linex,	D_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,	aDot,	0,	linex,	pipe_cmd, | 
 | 	'|',	0,	0,	0,	0,	aDot,	0,	linex,	pipe_cmd, | 
 | 	'>',	0,	0,	0,	0,	aDot,	0,	linex,	pipe_cmd, | 
 | /* deliberately unimplemented: | 
 | 	'k',	0,	0,	0,	0,	aDot,	0,	0,	k_cmd, | 
 | 	'n',	0,	0,	0,	0,	aNo,	0,	0,	n_cmd, | 
 | 	'q',	0,	0,	0,	0,	aNo,	0,	0,	q_cmd, | 
 | 	'!',	0,	0,	0,	0,	aNo,	0,	linex,	plan9_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	*cmdstartp; | 
 | Rune *cmdendp; | 
 | Rune	*cmdp; | 
 | Channel	*editerrc; | 
 |  | 
 | String	*lastpat; | 
 | int	patset; | 
 |  | 
 | List	cmdlist; | 
 | List	addrlist; | 
 | List	stringlist; | 
 | Text	*curtext; | 
 | int	editing = Inactive; | 
 |  | 
 | String*	newstring(int); | 
 |  | 
 | void | 
 | editthread(void *v) | 
 | { | 
 | 	Cmd *cmdp; | 
 |  | 
 | 	USED(v); | 
 | 	threadsetname("editthread"); | 
 | 	while((cmdp=parsecmd(0)) != 0){ | 
 | 		if(cmdexec(curtext, cmdp) == 0) | 
 | 			break; | 
 | 		freecmd(); | 
 | 	} | 
 | 	sendp(editerrc, nil); | 
 | } | 
 |  | 
 | void | 
 | allelogterm(Window *w, void *x) | 
 | { | 
 | 	USED(x); | 
 | 	elogterm(w->body.file); | 
 | } | 
 |  | 
 | void | 
 | alleditinit(Window *w, void *x) | 
 | { | 
 | 	USED(x); | 
 | 	textcommit(&w->tag, TRUE); | 
 | 	textcommit(&w->body, TRUE); | 
 | 	w->body.file->editclean = FALSE; | 
 | } | 
 |  | 
 | void | 
 | allupdate(Window *w, void *x) | 
 | { | 
 | 	Text *t; | 
 | 	int i; | 
 | 	File *f; | 
 |  | 
 | 	USED(x); | 
 | 	t = &w->body; | 
 | 	f = t->file; | 
 | 	if(f->curtext != t)	/* do curtext only */ | 
 | 		return; | 
 | 	if(f->elog.type == Null) | 
 | 		elogterm(f); | 
 | 	else if(f->elog.type != Empty){ | 
 | 		elogapply(f); | 
 | 		if(f->editclean){ | 
 | 			f->mod = FALSE; | 
 | 			for(i=0; i<f->ntext; i++) | 
 | 				f->text[i]->w->dirty = FALSE; | 
 | 		} | 
 | 	} | 
 | 	textsetselect(t, t->q0, t->q1); | 
 | 	textscrdraw(t); | 
 | 	winsettag(w); | 
 | } | 
 |  | 
 | void | 
 | editerror(char *fmt, ...) | 
 | { | 
 | 	va_list arg; | 
 | 	char *s; | 
 |  | 
 | 	va_start(arg, fmt); | 
 | 	s = vsmprint(fmt, arg); | 
 | 	va_end(arg); | 
 | 	freecmd(); | 
 | 	allwindows(allelogterm, nil);	/* truncate the edit logs */ | 
 | 	sendp(editerrc, s); | 
 | 	threadexits(nil); | 
 | } | 
 |  | 
 | void | 
 | editcmd(Text *ct, Rune *r, uint n) | 
 | { | 
 | 	char *err; | 
 |  | 
 | 	if(n == 0) | 
 | 		return; | 
 | 	if(2*n > RBUFSIZE){ | 
 | 		warning(nil, "string too long\n"); | 
 | 		return; | 
 | 	} | 
 |  | 
 | 	allwindows(alleditinit, nil); | 
 | 	if(cmdstartp) | 
 | 		free(cmdstartp); | 
 | 	cmdstartp = runemalloc(n+2); | 
 | 	runemove(cmdstartp, r, n); | 
 | 	if(r[n-1] != '\n') | 
 | 		cmdstartp[n++] = '\n'; | 
 | 	cmdstartp[n] = '\0'; | 
 | 	cmdendp = cmdstartp+n; | 
 | 	cmdp = cmdstartp; | 
 | 	if(ct->w == nil) | 
 | 		curtext = nil; | 
 | 	else | 
 | 		curtext = &ct->w->body; | 
 | 	resetxec(); | 
 | 	if(editerrc == nil){ | 
 | 		editerrc = chancreate(sizeof(char*), 0); | 
 | 		chansetname(editerrc, "editerrc"); | 
 | 		lastpat = allocstring(0); | 
 | 	} | 
 | 	threadcreate(editthread, nil, STACK); | 
 | 	err = recvp(editerrc); | 
 | 	editing = Inactive; | 
 | 	if(err != nil){ | 
 | 		if(err[0] != '\0') | 
 | 			warning(nil, "Edit: %s\n", err); | 
 | 		free(err); | 
 | 	} | 
 |  | 
 | 	/* update everyone whose edit log has data */ | 
 | 	allwindows(allupdate, nil); | 
 | } | 
 |  | 
 | int | 
 | getch(void) | 
 | { | 
 | 	if(cmdp == cmdendp) | 
 | 		return -1; | 
 | 	return *cmdp++; | 
 | } | 
 |  | 
 | int | 
 | nextc(void) | 
 | { | 
 | 	if(cmdp == cmdendp) | 
 | 		return -1; | 
 | 	return *cmdp; | 
 | } | 
 |  | 
 | void | 
 | ungetch(void) | 
 | { | 
 | 	if(--cmdp < cmdstartp) | 
 | 		error("ungetch"); | 
 | } | 
 |  | 
 | long | 
 | getnum(int signok) | 
 | { | 
 | 	long n; | 
 | 	int c, sign; | 
 |  | 
 | 	n = 0; | 
 | 	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 | 
 | cmdskipbl(void) | 
 | { | 
 | 	int c; | 
 | 	do | 
 | 		c = getch(); | 
 | 	while(c==' ' || c=='\t'); | 
 | 	if(c >= 0) | 
 | 		ungetch(); | 
 | 	return c; | 
 | } | 
 |  | 
 | /* | 
 |  * Check that list has room for one more element. | 
 |  */ | 
 | void | 
 | growlist(List *l) | 
 | { | 
 | 	if(l->u.listptr==0 || l->nalloc==0){ | 
 | 		l->nalloc = INCR; | 
 | 		l->u.listptr = emalloc(INCR*sizeof(void*)); | 
 | 		l->nused = 0; | 
 | 	}else if(l->nused == l->nalloc){ | 
 | 		l->u.listptr = erealloc(l->u.listptr, (l->nalloc+INCR)*sizeof(void*)); | 
 | 		memset(l->u.ptr+l->nalloc, 0, INCR*sizeof(void*)); | 
 | 		l->nalloc += INCR; | 
 | 	} | 
 | } | 
 |  | 
 | /* | 
 |  * Remove the ith element from the list | 
 |  */ | 
 | void | 
 | dellist(List *l, int i) | 
 | { | 
 | 	memmove(&l->u.ptr[i], &l->u.ptr[i+1], (l->nused-(i+1))*sizeof(void*)); | 
 | 	l->nused--; | 
 | } | 
 |  | 
 | /* | 
 |  * Add a new element, whose position is i, to the list | 
 |  */ | 
 | void | 
 | inslist(List *l, int i, void *v) | 
 | { | 
 | 	growlist(l); | 
 | 	memmove(&l->u.ptr[i+1], &l->u.ptr[i], (l->nused-i)*sizeof(void*)); | 
 | 	l->u.ptr[i] = v; | 
 | 	l->nused++; | 
 | } | 
 |  | 
 | void | 
 | listfree(List *l) | 
 | { | 
 | 	free(l->u.listptr); | 
 | 	free(l); | 
 | } | 
 |  | 
 | String* | 
 | allocstring(int n) | 
 | { | 
 | 	String *s; | 
 |  | 
 | 	s = emalloc(sizeof(String)); | 
 | 	s->n = n; | 
 | 	s->nalloc = n+10; | 
 | 	s->r = emalloc(s->nalloc*sizeof(Rune)); | 
 | 	s->r[n] = '\0'; | 
 | 	return s; | 
 | } | 
 |  | 
 | void | 
 | freestring(String *s) | 
 | { | 
 | 	free(s->r); | 
 | 	free(s); | 
 | } | 
 |  | 
 | Cmd* | 
 | newcmd(void){ | 
 | 	Cmd *p; | 
 |  | 
 | 	p = emalloc(sizeof(Cmd)); | 
 | 	inslist(&cmdlist, cmdlist.nused, p); | 
 | 	return p; | 
 | } | 
 |  | 
 | String* | 
 | newstring(int n) | 
 | { | 
 | 	String *p; | 
 |  | 
 | 	p = allocstring(n); | 
 | 	inslist(&stringlist, stringlist.nused, p); | 
 | 	return p; | 
 | } | 
 |  | 
 | Addr* | 
 | newaddr(void) | 
 | { | 
 | 	Addr *p; | 
 |  | 
 | 	p = emalloc(sizeof(Addr)); | 
 | 	inslist(&addrlist, addrlist.nused, p); | 
 | 	return p; | 
 | } | 
 |  | 
 | void | 
 | freecmd(void) | 
 | { | 
 | 	int i; | 
 |  | 
 | 	while(cmdlist.nused > 0) | 
 | 		free(cmdlist.u.ucharptr[--cmdlist.nused]); | 
 | 	while(addrlist.nused > 0) | 
 | 		free(addrlist.u.ucharptr[--addrlist.nused]); | 
 | 	while(stringlist.nused>0){ | 
 | 		i = --stringlist.nused; | 
 | 		freestring(stringlist.u.stringptr[i]); | 
 | 	} | 
 | } | 
 |  | 
 | void | 
 | okdelim(int c) | 
 | { | 
 | 	if(c=='\\' || ('a'<=c && c<='z') | 
 | 	|| ('A'<=c && c<='Z') || ('0'<=c && c<='9')) | 
 | 		editerror("bad delimiter %c\n", c); | 
 | } | 
 |  | 
 | void | 
 | atnl(void) | 
 | { | 
 | 	int c; | 
 |  | 
 | 	cmdskipbl(); | 
 | 	c = getch(); | 
 | 	if(c != '\n') | 
 | 		editerror("newline expected (saw %C)", c); | 
 | } | 
 |  | 
 | void | 
 | Straddc(String *s, int c) | 
 | { | 
 | 	if(s->n+1 >= s->nalloc){ | 
 | 		s->nalloc += 10; | 
 | 		s->r = erealloc(s->r, s->nalloc*sizeof(Rune)); | 
 | 	} | 
 | 	s->r[s->n++] = c; | 
 | 	s->r[s->n] = '\0'; | 
 | } | 
 |  | 
 | 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("bad right hand side"); | 
 | 			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 delimiter, '\n' or whatever */ | 
 | } | 
 |  | 
 | String * | 
 | collecttoken(char *end) | 
 | { | 
 | 	String *s = newstring(0); | 
 | 	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); | 
 | 	if(c != '\n') | 
 | 		atnl(); | 
 | 	return s; | 
 | } | 
 |  | 
 | String * | 
 | collecttext(void) | 
 | { | 
 | 	String *s; | 
 | 	int begline, i, c, delim; | 
 |  | 
 | 	s = newstring(0); | 
 | 	if(cmdskipbl()=='\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->r[begline]!='.' || s->r[begline+1]!='\n'); | 
 | 		s->r[s->n-2] = '\0'; | 
 | 		s->n -= 2; | 
 | 	}else{ | 
 | 		okdelim(delim = getch()); | 
 | 		getrhs(s, delim, 'a'); | 
 | 		if(nextc()==delim) | 
 | 			getch(); | 
 | 		atnl(); | 
 | 	} | 
 |     Return: | 
 | 	return s; | 
 | } | 
 |  | 
 | int | 
 | cmdlookup(int c) | 
 | { | 
 | 	int i; | 
 |  | 
 | 	for(i=0; cmdtab[i].cmdc; i++) | 
 | 		if(cmdtab[i].cmdc == c) | 
 | 			return i; | 
 | 	return -1; | 
 | } | 
 |  | 
 | Cmd* | 
 | parsecmd(int nest) | 
 | { | 
 | 	int i, c; | 
 | 	struct cmdtab *ct; | 
 | 	Cmd *cp, *ncp; | 
 | 	Cmd cmd; | 
 |  | 
 | 	cmd.next = cmd.u.cmd = 0; | 
 | 	cmd.re = 0; | 
 | 	cmd.flag = cmd.num = 0; | 
 | 	cmd.addr = compoundaddr(); | 
 | 	if(cmdskipbl() == -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 = cmdlookup(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) | 
 | 			editerror("command takes no address"); | 
 | 		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')){ | 
 | 				cmdskipbl(); | 
 | 				if((c = getch())=='\n' || c<0) | 
 | 					editerror("no address"); | 
 | 				okdelim(c); | 
 | 				cmd.re = getregexp(c); | 
 | 				if(ct->cmdc == 's'){ | 
 | 					cmd.u.text = newstring(0); | 
 | 					getrhs(cmd.u.text, c, 's'); | 
 | 					if(nextc() == c){ | 
 | 						getch(); | 
 | 						if(nextc() == 'g') | 
 | 							cmd.flag = getch(); | 
 | 					} | 
 | 			 | 
 | 				} | 
 | 			} | 
 | 		} | 
 | 		if(ct->addr && (cmd.u.mtaddr=simpleaddr())==0) | 
 | 			editerror("bad address"); | 
 | 		if(ct->defcmd){ | 
 | 			if(cmdskipbl() == '\n'){ | 
 | 				getch(); | 
 | 				cmd.u.cmd = newcmd(); | 
 | 				cmd.u.cmd->cmdc = ct->defcmd; | 
 | 			}else if((cmd.u.cmd = parsecmd(nest))==0) | 
 | 				error("defcmd"); | 
 | 		}else if(ct->text) | 
 | 			cmd.u.text = collecttext(); | 
 | 		else if(ct->token) | 
 | 			cmd.u.text = collecttoken(ct->token); | 
 | 		else | 
 | 			atnl(); | 
 | 	}else | 
 | 		switch(cmd.cmdc){ | 
 | 		case '{': | 
 | 			cp = 0; | 
 | 			do{ | 
 | 				if(cmdskipbl()=='\n') | 
 | 					getch(); | 
 | 				ncp = parsecmd(nest+1); | 
 | 				if(cp) | 
 | 					cp->next = ncp; | 
 | 				else | 
 | 					cmd.u.cmd = ncp; | 
 | 			}while(cp = ncp); | 
 | 			break; | 
 | 		case '}': | 
 | 			atnl(); | 
 | 			if(nest==0) | 
 | 				editerror("right brace with no left brace"); | 
 | 			return 0; | 
 | 		default: | 
 | 			editerror("unknown command %c", cmd.cmdc); | 
 | 		} | 
 |     Return: | 
 | 	cp = newcmd(); | 
 | 	*cp = cmd; | 
 | 	return cp; | 
 | } | 
 |  | 
 | String* | 
 | getregexp(int delim) | 
 | { | 
 | 	String *buf, *r; | 
 | 	int i, c; | 
 |  | 
 | 	buf = allocstring(0); | 
 | 	for(i=0; ; i++){ | 
 | 		if((c = getch())=='\\'){ | 
 | 			if(nextc()==delim) | 
 | 				c = getch(); | 
 | 			else if(nextc()=='\\'){ | 
 | 				Straddc(buf, c); | 
 | 				c = getch(); | 
 | 			} | 
 | 		}else if(c==delim || c=='\n') | 
 | 			break; | 
 | 		if(i >= RBUFSIZE) | 
 | 			editerror("regular expression too long"); | 
 | 		Straddc(buf, c); | 
 | 	} | 
 | 	if(c!=delim && c) | 
 | 		ungetch(); | 
 | 	if(buf->n > 0){ | 
 | 		patset = TRUE; | 
 | 		freestring(lastpat); | 
 | 		lastpat = buf; | 
 | 	}else | 
 | 		freestring(buf); | 
 | 	if(lastpat->n == 0) | 
 | 		editerror("no regular expression defined"); | 
 | 	r = newstring(lastpat->n); | 
 | 	runemove(r->r, lastpat->r, lastpat->n);	/* newstring put \0 at end */ | 
 | 	return r; | 
 | } | 
 |  | 
 | Addr * | 
 | simpleaddr(void) | 
 | { | 
 | 	Addr addr; | 
 | 	Addr *ap, *nap; | 
 |  | 
 | 	addr.num = 0; | 
 | 	addr.next = 0; | 
 | 	addr.u.left = 0; | 
 | 	switch(cmdskipbl()){ | 
 | 	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.u.re = 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 '"': | 
 | 				editerror("bad address syntax"); | 
 | 			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: | 
 | 			error("simpleaddr"); | 
 | 		} | 
 | 	ap = newaddr(); | 
 | 	*ap = addr; | 
 | 	return ap; | 
 | } | 
 |  | 
 | Addr * | 
 | compoundaddr(void) | 
 | { | 
 | 	Addr addr; | 
 | 	Addr *ap, *next; | 
 |  | 
 | 	addr.u.left = simpleaddr(); | 
 | 	if((addr.type = cmdskipbl())!=',' && addr.type!=';') | 
 | 		return addr.u.left; | 
 | 	getch(); | 
 | 	next = addr.next = compoundaddr(); | 
 | 	if(next && (next->type==',' || next->type==';') && next->u.left==0) | 
 | 		editerror("bad address syntax"); | 
 | 	ap = newaddr(); | 
 | 	*ap = addr; | 
 | 	return ap; | 
 | } |