text: #include	"mk.h"
  
  static int initdone = 0;
  
  static int bquote(Biobuf *, Bufblock *);
  static int assquote(Biobuf *, Bufblock *, int);
  static int assdquote(Biobuf *, Bufblock *);
  static char *squote(char*);
  static long nextrune(Biobuf*, int);
  static int expandvar(Biobuf *, Bufblock *);
  static Bufblock *varname(Biobuf*);
  static int varsub(Biobuf *, Bufblock *);
  static int whitespace(Biobuf*);
  
  /*
   *	Assemble a line skipping blank lines, comments, and eliding
   *	escaped newlines
   */
  int
  assline(Biobuf *bp, Bufblock *buf)
  {
  	int c;
  	int lastc;
  
  	initdone = 0;
  	buf->current=buf->start;
  	while ((c = nextrune(bp, 1)) >= 0){
  		switch(c) {
  		case '\n':
  			if (buf->current != buf->start) {
  				insert(buf, 0);
  				return 1;
  			}
  			break;		/* skip empty lines */
  		case '\'':
  			rinsert(buf, c);
  			if (!assquote(bp, buf, 1))
  				Exit();
  			break;
  		case '`':
  			if (!bquote(bp, buf))
  				Exit();
  			break;
  		case '$':
  			if (!varsub(bp, buf))
  				Exit();
  			break;
  		case '#':
  			lastc = '#';
  			while ((c = Bgetc(bp)) != '\n') {
  				if (c < 0)
  					goto eof;
  				lastc = c;
  			}
  			inline++;
  			if (lastc == '\\')
  				break;		/* propagate escaped newlines??*/
  			if (buf->current != buf->start) {
  				insert(buf, 0);
  				return 1;
  			}
  			break;
  		default:
  			rinsert(buf, c);
  			break;
  		}
  	}
  eof:
  	insert(buf, 0);
  	return *buf->start != 0;
  }
  /*
   *	Assemble a token surrounded by single quotes
   */
  static int
  assquote(Biobuf *bp, Bufblock *buf, int preserve)
  {
  	int c, line;
  
  	line = inline;
  	while ((c = nextrune(bp, 0)) >= 0) {
  		if (c == '\'') {
  			if (preserve)
  				rinsert(buf, c);
  			c = Bgetrune(bp);
  			if (c < 0)
  				break;
  			if (c != '\'') {
  				Bungetrune(bp);
  				return 1;
  			}
  		}
  		rinsert(buf, c);
  	}
  	SYNERR(line); fprint(2, "missing closing '\n");
  	return 0;
  }
  /*
   *	assemble a back-quoted shell command
   */
  static int
  bquote(Biobuf *bp, Bufblock *buf)
  {
  	int c, line;
  	int start;
  	char *end;
  
  	line = inline;
  	c = whitespace(bp);
  	if(c != '{') {
  		SYNERR(line);
  		fprint(2, "missing opening { after `\n");
  		return 0;
  	}
  	start = buf->current-buf->start;
  	while ((c = nextrune(bp, 0)) > 0) {
  		if (c == '\n')
  			break;
  		if (c == '}') {
  			c = Bgetrune(bp);
  			if (c != '`')	/* for backward compatibility */
  				Bungetrune(bp);
  			insert(buf, '\n');
  			insert(buf,0);
  			end = buf->current-1;
  			buf->current = buf->start+start;
  			if(initdone == 0){
  				execinit();
  				initdone = 1;
  			}
  			rcexec(buf->current, end, buf);
  			return 1;
  		} else if (c == '\'') {
  			insert(buf, c);
  			if (!assquote(bp, buf, 1))
  				return 0;
  			continue;
  		}
  		rinsert(buf, c);
  	}
  	SYNERR(line);
  	fprint(2, "missing closing } after `{\n");
  	return 0;
  }
  
  static int
  varsub(Biobuf *bp, Bufblock *buf)
  {
  	int c;
  	Bufblock *buf2;
  
  	c = Bgetrune(bp);
  	if(c == '{')		/* either ${name} or ${name: A%B==C%D}*/
  		return expandvar(bp, buf);
  	Bungetrune(bp);
  	buf2 = varname(bp);
  	if (!buf2)
  		return 0;
  	varmatch(buf2->start, buf);
  	freebuf(buf2);
  	return 1;
  }
  
  static int
  expandvar(Biobuf *bp, Bufblock *buf)
  {
  	Bufblock *buf2;
  	int c;
  	int start;
  	Symtab *sym;
  
  	buf2 = varname(bp);
  	if (!buf2)
  		return 0;
  	c = Bgetrune(bp);
  	if (c == '}') {				/* ${name} variant*/
  		varmatch(buf2->start, buf);
  		freebuf(buf2);
  		return 1;
  	}
  	if (c != ':') {
  		SYNERR(-1);
  		fprint(2, "bad variable name <%s>\n", buf2->start);
  		freebuf(buf2);
  		return 0;
  	}
  	start = buf2->current-buf2->start;
  	while ((c = Bgetrune(bp)) != '}') {	/* ${name:A%B=C%D} variant */
  		switch(c)
  		{
  		case 0:
  		case '\n':
  			SYNERR(-1);
  			fprint(2, "missing '}'\n");
  			freebuf(buf2);
  			return 0;
  		case '\'':
  			if (!assquote(bp, buf2, 0)) {
  				freebuf(buf2);
  				return 0;
  			}
  			break;
  		case '`':
  			if (!bquote(bp, buf2)) {
  				freebuf(buf2);
  				return 0;
  			}
  			break;
  		case '$':
  			if (!varsub(bp, buf2)) {
  				freebuf(buf2);
  				return 0;
  			}
  			break;
  		case ' ':
  		case '\t':
  			break;
  		default:
  			rinsert(buf2, c);
  			break;
  		}
  	}
  	insert(buf2, 0);
  	sym = symlook(buf2->start, S_VAR, 0);
  	if (!sym || !sym->value)
  		bufcpy(buf, buf2->start, start-2);
  	else subsub((Word *) sym->value, buf2->start+start, buf);
  	freebuf(buf2);
  	return 1;
  }
  /*
   *	extract a variable name
   */
  static Bufblock *
  varname(Biobuf *bp)
  {
  	Bufblock *buf;
  	int c;
  
  	buf = newbuf();
  	c = Bgetrune(bp);
  	while (WORDCHR(c)) {
  		rinsert(buf, c);
  		c = Bgetrune(bp);
  	}
  	if (c < 0 || buf->current == buf->start) {
  		SYNERR(-1);
  		fprint(2, "bad variable name <%s>\n", buf->start);
  		freebuf(buf);
  		return 0;
  	}
  	Bungetrune(bp);
  	insert(buf, 0);
  	return buf;
  }
  /*
   *	search a string for unescaped characters in a pattern set
   */
  char *
  charin(char *cp, char *pat)
  {
  	Rune r;
  	int n;
  
  	while (*cp) {
  		n = chartorune(&r, cp);
  		if (r == '\'') {	/* skip quoted string */
  			cp = squote(cp);	/* n must = 1 */
  			if (!cp)
  				return 0;
  		} else
  			if (utfrune(pat, r))
  				return cp;
  		cp += n;
  	}
  	return 0;
  }
  /*
   *	skip a token in single quotes.
   */
  static char *
  squote(char *cp)
  {
  	Rune r;
  	int n;
  
  	cp++;			/* skip opening quote */
  	while (*cp) {
  		n = chartorune(&r, cp);
  		if (r == '\'') {
  			n += chartorune(&r, cp+n);
  			if (r != '\'')
  				return(cp);
  		}
  		cp += n;
  	}
  	SYNERR(-1);		/* should never occur */
  	fprint(2, "missing closing '\n");
  	return 0;
  }
  /*
   *	get next character stripping escaped newlines
   *	the flag specifies whether escaped newlines are to be elided or
   *	replaced with a blank.
   */
  static long
  nextrune(Biobuf *bp, int elide)
  {
  	int c;
  
  	for (;;) {
  		c = Bgetrune(bp);
  		if (c == '\\') {
  			if (Bgetrune(bp) == '\n') {
  				inline++;
  				if (elide)
  					continue;
  				return ' ';
  			}
  			Bungetrune(bp);
  		}
  		if (c == '\n')
  			inline++;
  		return c;
  	}
  	return 0;
  }
  
  static int
  whitespace(Biobuf *bp)
  {
  	int c;
  
  	while ((c = Bgetrune(bp)) == ' ' || c == '\t')
  			;
  	return c;
  }
  