xref: /netbsd-src/usr.bin/sed/compile.c (revision cda4f8f6ee55684e8d311b86c99ea59191e6b74f)
1 /*-
2  * Copyright (c) 1992 Diomidis Spinellis.
3  * Copyright (c) 1992 The Regents of the University of California.
4  * All rights reserved.
5  *
6  * This code is derived from software contributed to Berkeley by
7  * Diomidis Spinellis of Imperial College, University of London.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  * 3. All advertising materials mentioning features or use of this software
18  *    must display the following acknowledgement:
19  *	This product includes software developed by the University of
20  *	California, Berkeley and its contributors.
21  * 4. Neither the name of the University nor the names of its contributors
22  *    may be used to endorse or promote products derived from this software
23  *    without specific prior written permission.
24  *
25  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
26  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
29  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
30  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
31  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
34  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35  * SUCH DAMAGE.
36  */
37 
38 #ifndef lint
39 static char sccsid[] = "@(#)compile.c	5.6 (Berkeley) 11/2/92";
40 #endif /* not lint */
41 
42 #include <sys/types.h>
43 #include <sys/stat.h>
44 
45 #include <ctype.h>
46 #include <errno.h>
47 #include <fcntl.h>
48 #include <limits.h>
49 #include <regex.h>
50 #include <stdio.h>
51 #include <stdlib.h>
52 #include <string.h>
53 
54 #include "defs.h"
55 #include "extern.h"
56 
57 static char	 *compile_addr __P((char *, struct s_addr *));
58 static char	 *compile_ccl __P((char **, char *));
59 static char	 *compile_delimited __P((char *, char *));
60 static char	 *compile_flags __P((char *, struct s_subst *));
61 static char	 *compile_re __P((char *, regex_t **));
62 static char	 *compile_subst __P((char *, struct s_subst *));
63 static char	 *compile_text __P((void));
64 static char	 *compile_tr __P((char *, char **));
65 static struct s_command
66 		**compile_stream __P((char *, struct s_command **, char *));
67 static char	 *duptoeol __P((char *));
68 static struct s_command
69 		 *findlabel __P((struct s_command *, struct s_command *));
70 static void	  fixuplabel __P((struct s_command *, struct s_command *,
71 		  	struct s_command *));
72 
73 /*
74  * Command specification.  This is used to drive the command parser.
75  */
76 struct s_format {
77 	char code;				/* Command code */
78 	int naddr;				/* Number of address args */
79 	enum e_args args;			/* Argument type */
80 };
81 
82 static struct s_format cmd_fmts[] = {
83 	{'{', 2, GROUP},
84 	{'a', 1, TEXT},
85 	{'b', 2, BRANCH},
86 	{'c', 2, TEXT},
87 	{'d', 2, EMPTY},
88 	{'D', 2, EMPTY},
89 	{'g', 2, EMPTY},
90 	{'G', 2, EMPTY},
91 	{'h', 2, EMPTY},
92 	{'H', 2, EMPTY},
93 	{'i', 1, TEXT},
94 	{'l', 2, EMPTY},
95 	{'n', 2, EMPTY},
96 	{'N', 2, EMPTY},
97 	{'p', 2, EMPTY},
98 	{'P', 2, EMPTY},
99 	{'q', 1, EMPTY},
100 	{'r', 1, RFILE},
101 	{'s', 2, SUBST},
102 	{'t', 2, BRANCH},
103 	{'w', 2, WFILE},
104 	{'x', 2, EMPTY},
105 	{'y', 2, TR},
106 	{'!', 2, NONSEL},
107 	{':', 0, LABEL},
108 	{'#', 0, COMMENT},
109 	{'=', 1, EMPTY},
110 	{'\0', 0, COMMENT},
111 };
112 
113 /* The compiled program. */
114 struct s_command *prog;
115 
116 /*
117  * Compile the program into prog.
118  * Initialise appends.
119  */
120 void
121 compile()
122 {
123 	*compile_stream(NULL, &prog, NULL) = NULL;
124 	fixuplabel(prog, prog, NULL);
125 	appends = xmalloc(sizeof(struct s_appends) * appendnum);
126 	match = xmalloc((maxnsub + 1) * sizeof(regmatch_t));
127 }
128 
129 #define EATSPACE() do {							\
130 	if (p)								\
131 		while (*p && isascii(*p) && isspace(*p))		\
132 			p++;						\
133 	} while (0)
134 
135 static struct s_command **
136 compile_stream(terminator, link, p)
137 	char *terminator;
138 	struct s_command **link;
139 	register char *p;
140 {
141 	static char lbuf[_POSIX2_LINE_MAX + 1];	/* To save stack */
142 	struct s_command *cmd, *cmd2;
143 	struct s_format *fp;
144 	int naddr;				/* Number of addresses */
145 
146 	if (p != NULL)
147 		goto semicolon;
148 	for (;;) {
149 		if ((p = cu_fgets(lbuf, sizeof(lbuf))) == NULL) {
150 			if (terminator != NULL)
151 				err(COMPILE, "unexpected EOF (pending }'s)");
152 			return (link);
153 		}
154 
155 semicolon:	EATSPACE();
156 		if (p && (*p == '#' || *p == '\0'))
157 			continue;
158 		if (*p == '}') {
159 			if (terminator == NULL)
160 				err(COMPILE, "unexpected }");
161 			return (link);
162 		}
163 		*link = cmd = xmalloc(sizeof(struct s_command));
164 		link = &cmd->next;
165 		cmd->nonsel = cmd->inrange = 0;
166 		/* First parse the addresses */
167 		naddr = 0;
168 		cmd->a1 = cmd->a2 = NULL;
169 
170 /* Valid characters to start an address */
171 #define	addrchar(c)	(strchr("0123456789/\\$", (c)))
172 		if (addrchar(*p)) {
173 			naddr++;
174 			cmd->a1 = xmalloc(sizeof(struct s_addr));
175 			p = compile_addr(p, cmd->a1);
176 			EATSPACE();				/* EXTENSION */
177 			if (*p == ',') {
178 				naddr++;
179 				p++;
180 				EATSPACE();			/* EXTENSION */
181 				cmd->a2 = xmalloc(sizeof(struct s_addr));
182 				p = compile_addr(p, cmd->a2);
183 			}
184 		}
185 
186 nonsel:		/* Now parse the command */
187 		EATSPACE();
188 		if (!*p)
189 			err(COMPILE, "command expected");
190 		cmd->code = *p;
191 		for (fp = cmd_fmts; fp->code; fp++)
192 			if (fp->code == *p)
193 				break;
194 		if (!fp->code)
195 			err(COMPILE, "invalid command code %c", *p);
196 		if (naddr > fp->naddr)
197 			err(COMPILE,
198 "command %c expects up to %d address(es), found %d", *p, fp->naddr, naddr);
199 		switch (fp->args) {
200 		case NONSEL:			/* ! */
201 			cmd->nonsel = ! cmd->nonsel;
202 			p++;
203 			goto nonsel;
204 		case GROUP:			/* { */
205 			p++;
206 			EATSPACE();
207 			if (!*p)
208 				p = NULL;
209 			cmd2 = xmalloc(sizeof(struct s_command));
210 			cmd2->code = '}';
211 			*compile_stream("}", &cmd->u.c, p) = cmd2;
212 			cmd->next = cmd2;
213 			link = &cmd2->next;
214 			break;
215 		case EMPTY:		/* d D g G h H l n N p P q x = \0 */
216 			p++;
217 			EATSPACE();
218 			if (*p == ';') {
219 				p++;
220 				link = &cmd->next;
221 				goto semicolon;
222 			}
223 			if (*p)
224 				err(COMPILE,
225 "extra characters at the end of %c command", cmd->code);
226 			break;
227 		case TEXT:			/* a c i */
228 			p++;
229 			EATSPACE();
230 			if (*p != '\\')
231 				err(COMPILE,
232 "command %c expects \\ followed by text", cmd->code);
233 			p++;
234 			EATSPACE();
235 			if (*p)
236 				err(COMPILE,
237 "extra characters after \\ at the end of %c command", cmd->code);
238 			cmd->t = compile_text();
239 			break;
240 		case COMMENT:			/* \0 # */
241 			break;
242 		case WFILE:			/* w */
243 			p++;
244 			EATSPACE();
245 			if (*p == '\0')
246 				err(COMPILE, "filename expected");
247 			cmd->t = duptoeol(p);
248 			if (aflag)
249 				cmd->u.fd = -1;
250 			else if ((cmd->u.fd = open(p,
251 			    O_WRONLY|O_APPEND|O_CREAT|O_TRUNC,
252 			    DEFFILEMODE)) == -1)
253 				err(FATAL, "%s: %s\n", p, strerror(errno));
254 			break;
255 		case RFILE:			/* r */
256 			p++;
257 			EATSPACE();
258 			if (*p == '\0')
259 				err(COMPILE, "filename expected");
260 			else
261 				cmd->t = duptoeol(p);
262 			break;
263 		case BRANCH:			/* b t */
264 			p++;
265 			EATSPACE();
266 			if (*p == '\0')
267 				cmd->t = NULL;
268 			else
269 				cmd->t = duptoeol(p);
270 			break;
271 		case LABEL:			/* : */
272 			p++;
273 			EATSPACE();
274 			cmd->t = duptoeol(p);
275 			if (strlen(p) == 0)
276 				err(COMPILE, "empty label");
277 			break;
278 		case SUBST:			/* s */
279 			p++;
280 			if (*p == '\0' || *p == '\\')
281 				err(COMPILE,
282 "substitute pattern can not be delimited by newline or backslash");
283 			cmd->u.s = xmalloc(sizeof(struct s_subst));
284 			p = compile_re(p, &cmd->u.s->re);
285 			if (p == NULL)
286 				err(COMPILE, "unterminated substitute pattern");
287 			--p;
288 			p = compile_subst(p, cmd->u.s);
289 			p = compile_flags(p, cmd->u.s);
290 			EATSPACE();
291 			if (*p == ';') {
292 				p++;
293 				link = &cmd->next;
294 				goto semicolon;
295 			}
296 			break;
297 		case TR:			/* y */
298 			p++;
299 			p = compile_tr(p, (char **)&cmd->u.y);
300 			EATSPACE();
301 			if (*p == ';') {
302 				p++;
303 				link = &cmd->next;
304 				goto semicolon;
305 			}
306 			if (*p)
307 				err(COMPILE,
308 "extra text at the end of a transform command");
309 			break;
310 		}
311 	}
312 }
313 
314 /*
315  * Get a delimited string.  P points to the delimeter of the string; d points
316  * to a buffer area.  Newline and delimiter escapes are processed; other
317  * escapes are ignored.
318  *
319  * Returns a pointer to the first character after the final delimiter or NULL
320  * in the case of a non-terminated string.  The character array d is filled
321  * with the processed string.
322  */
323 static char *
324 compile_delimited(p, d)
325 	char *p, *d;
326 {
327 	char c;
328 
329 	c = *p++;
330 	if (c == '\0')
331 		return (NULL);
332 	else if (c == '\\')
333 		err(COMPILE, "\\ can not be used as a string delimiter");
334 	else if (c == '\n')
335 		err(COMPILE, "newline can not be used as a string delimiter");
336 	while (*p) {
337 		if (*p == '[') {
338 			if ((d = compile_ccl(&p, d)) == NULL)
339 				err(COMPILE, "unbalanced brackets ([])");
340 			continue;
341 		} else if (*p == '\\' && p[1] == c)
342 			p++;
343 		else if (*p == '\\' && p[1] == 'n') {
344 			*d++ = '\n';
345 			p += 2;
346 			continue;
347 		} else if (*p == '\\' && p[1] == '\\')
348 			*d++ = *p++;
349 		else if (*p == c) {
350 			*d = '\0';
351 			return (p + 1);
352 		}
353 		*d++ = *p++;
354 	}
355 	return (NULL);
356 }
357 
358 
359 /* compile_ccl: expand a POSIX character class */
360 static char *
361 compile_ccl(sp, t)
362 	char **sp;
363 	char *t;
364 {
365 	int c, d;
366 	char *s = *sp;
367 
368 	*t++ = *s++;
369 	if (*s == '^')
370 		*t++ = *s++;
371 	if (*s == ']')
372 		*t++ = *s++;
373 	for (; *s && (*t = *s) != ']'; s++, t++)
374 		if (*s == '[' && ((d = *(s+1)) == '.' || d == ':' || d == '=')) {
375 			*++t = *++s, t++, s++;
376 			for (c = *s; (*t = *s) != ']' || c != d; s++, t++)
377 				if ((c = *s) == '\0')
378 					return NULL;
379 		} else if (*s == '\\' && s[1] == 'n')
380 			*t = '\n', s++;
381 	return (*s == ']') ? *sp = ++s, ++t : NULL;
382 }
383 
384 /*
385  * Get a regular expression.  P points to the delimiter of the regular
386  * expression; repp points to the address of a regexp pointer.  Newline
387  * and delimiter escapes are processed; other escapes are ignored.
388  * Returns a pointer to the first character after the final delimiter
389  * or NULL in the case of a non terminated regular expression.  The regexp
390  * pointer is set to the compiled regular expression.
391  * Cflags are passed to regcomp.
392  */
393 static char *
394 compile_re(p, repp)
395 	char *p;
396 	regex_t **repp;
397 {
398 	int eval;
399 	char re[_POSIX2_LINE_MAX + 1];
400 
401 	p = compile_delimited(p, re);
402 	if (p && strlen(re) == 0) {
403 		*repp = NULL;
404 		return (p);
405 	}
406 	*repp = xmalloc(sizeof(regex_t));
407 #ifdef GNU_REGEX
408 	/* initialize pattern buffer */
409 	(*repp)->buffer = NULL;
410 	(*repp)->allocated = 0L;
411 	(*repp)->fastmap = 0;		/* fastmap not used by regex > 0.12 */
412 	(*repp)->translate = 0;
413 #endif
414 	if (p && (eval = regcomp(*repp, re, 0)) != 0)
415 		err(COMPILE, "RE error: %s", strregerror(eval, *repp));
416 	if (maxnsub < (*repp)->re_nsub)
417 		maxnsub = (*repp)->re_nsub;
418 	return (p);
419 }
420 
421 /*
422  * Compile the substitution string of a regular expression and set res to
423  * point to a saved copy of it.  Nsub is the number of parenthesized regular
424  * expressions.
425  */
426 static char *
427 compile_subst(p, s)
428 	char *p;
429 	struct s_subst *s;
430 {
431 	static char lbuf[_POSIX2_LINE_MAX + 1];
432 	int asize, ref, size;
433 	char c, *text, *op, *sp;
434 
435 	c = *p++;			/* Terminator character */
436 	if (c == '\0')
437 		return (NULL);
438 
439 	s->maxbref = 0;
440 	s->linenum = linenum;
441 	asize = 2 * _POSIX2_LINE_MAX + 1;
442 	text = xmalloc(asize);
443 	size = 0;
444 	do {
445 		op = sp = text + size;
446 		for (; *p; p++) {
447 			if (*p == '\\') {
448 				p++;
449 				if (strchr("123456789", *p) != NULL) {
450 					*sp++ = '\\';
451 					ref = *p - '0';
452 					if (s->re != NULL &&
453 					    ref > s->re->re_nsub)
454 						err(COMPILE,
455 "\\%c not defined in the RE", *p);
456 					if (s->maxbref < ref)
457 						s->maxbref = ref;
458 				} else if (*p == '&' || *p == '\\')
459 					*sp++ = '\\';
460 			} else if (*p == c) {
461 				p++;
462 				*sp++ = '\0';
463 				size += sp - op;
464 				s->new = xrealloc(text, size);
465 				return (p);
466 			} else if (*p == '\n') {
467 				err(COMPILE,
468 "unescaped newline inside substitute pattern");
469 				/* NOTREACHED */
470 			}
471 			*sp++ = *p;
472 		}
473 		size += sp - op;
474 		if (asize - size < _POSIX2_LINE_MAX + 1) {
475 			asize *= 2;
476 			text = xmalloc(asize);
477 		}
478 	} while (cu_fgets(p = lbuf, sizeof(lbuf)));
479 	err(COMPILE, "unterminated substitute in regular expression");
480 	/* NOTREACHED */
481 }
482 
483 /*
484  * Compile the flags of the s command
485  */
486 static char *
487 compile_flags(p, s)
488 	char *p;
489 	struct s_subst *s;
490 {
491 	int gn;			/* True if we have seen g or n */
492 	char wfile[_POSIX2_LINE_MAX + 1], *q;
493 
494 	s->n = 1;				/* Default */
495 	s->p = 0;
496 	s->wfile = NULL;
497 	s->wfd = -1;
498 	for (gn = 0;;) {
499 		EATSPACE();			/* EXTENSION */
500 		switch (*p) {
501 		case 'g':
502 			if (gn)
503 				err(COMPILE,
504 "more than one number or 'g' in substitute flags");
505 			gn = 1;
506 			s->n = 0;
507 			break;
508 		case '\0':
509 		case '\n':
510 		case ';':
511 			return (p);
512 		case 'p':
513 			s->p = 1;
514 			break;
515 		case '1': case '2': case '3':
516 		case '4': case '5': case '6':
517 		case '7': case '8': case '9':
518 			if (gn)
519 				err(COMPILE,
520 "more than one number or 'g' in substitute flags");
521 			gn = 1;
522 			/* XXX Check for overflow */
523 			s->n = (int)strtol(p, &p, 10);
524 			break;
525 		case 'w':
526 			p++;
527 #ifdef HISTORIC_PRACTICE
528 			if (*p != ' ') {
529 				err(WARNING, "space missing before w wfile");
530 				return (p);
531 			}
532 #endif
533 			EATSPACE();
534 			q = wfile;
535 			while (*p) {
536 				if (*p == '\n')
537 					break;
538 				*q++ = *p++;
539 			}
540 			*q = '\0';
541 			if (q == wfile)
542 				err(COMPILE, "no wfile specified");
543 			s->wfile = strdup(wfile);
544 			if (!aflag && (s->wfd = open(wfile,
545 			    O_WRONLY|O_APPEND|O_CREAT|O_TRUNC,
546 			    DEFFILEMODE)) == -1)
547 				err(FATAL, "%s: %s\n", wfile, strerror(errno));
548 			return (p);
549 		default:
550 			err(COMPILE,
551 			    "bad flag in substitute command: '%c'", *p);
552 			break;
553 		}
554 		p++;
555 	}
556 }
557 
558 /*
559  * Compile a translation set of strings into a lookup table.
560  */
561 static char *
562 compile_tr(p, transtab)
563 	char *p;
564 	char **transtab;
565 {
566 	int i;
567 	char *lt, *op, *np;
568 	char old[_POSIX2_LINE_MAX + 1];
569 	char new[_POSIX2_LINE_MAX + 1];
570 
571 	if (*p == '\0' || *p == '\\')
572 		err(COMPILE,
573 "transform pattern can not be delimited by newline or backslash");
574 	p = compile_delimited(p, old);
575 	if (p == NULL) {
576 		err(COMPILE, "unterminated transform source string");
577 		return (NULL);
578 	}
579 	p = compile_delimited(--p, new);
580 	if (p == NULL) {
581 		err(COMPILE, "unterminated transform target string");
582 		return (NULL);
583 	}
584 	EATSPACE();
585 	if (strlen(new) != strlen(old)) {
586 		err(COMPILE, "transform strings are not the same length");
587 		return (NULL);
588 	}
589 	/* We assume characters are 8 bits */
590 	lt = xmalloc(UCHAR_MAX);
591 	for (i = 0; i <= UCHAR_MAX; i++)
592 		lt[i] = (char)i;
593 	for (op = old, np = new; *op; op++, np++)
594 		lt[(u_char)*op] = *np;
595 	*transtab = lt;
596 	return (p);
597 }
598 
599 /*
600  * Compile the text following an a or i command.
601  */
602 static char *
603 compile_text()
604 {
605 	int asize, size;
606 	char *text, *p, *op, *s;
607 	char lbuf[_POSIX2_LINE_MAX + 1];
608 
609 	asize = 2 * _POSIX2_LINE_MAX + 1;
610 	text = xmalloc(asize);
611 	size = 0;
612 	while (cu_fgets(lbuf, sizeof(lbuf))) {
613 		op = s = text + size;
614 		p = lbuf;
615 		EATSPACE();
616 		for (; *p; p++) {
617 			if (*p == '\\')
618 				p++;
619 			*s++ = *p;
620 		}
621 		size += s - op;
622 		if (p[-2] != '\\') {
623 			*s = '\0';
624 			break;
625 		}
626 		if (asize - size < _POSIX2_LINE_MAX + 1) {
627 			asize *= 2;
628 			text = xmalloc(asize);
629 		}
630 	}
631 	return (xrealloc(text, size + 1));
632 }
633 
634 /*
635  * Get an address and return a pointer to the first character after
636  * it.  Fill the structure pointed to according to the address.
637  */
638 static char *
639 compile_addr(p, a)
640 	char *p;
641 	struct s_addr *a;
642 {
643 	char *end;
644 
645 	switch (*p) {
646 	case '\\':				/* Context address */
647 		++p;
648 		/* FALLTHROUGH */
649 	case '/':				/* Context address */
650 		p = compile_re(p, &a->u.r);
651 		if (p == NULL)
652 			err(COMPILE, "unterminated regular expression");
653 		a->type = AT_RE;
654 		return (p);
655 
656 	case '$':				/* Last line */
657 		a->type = AT_LAST;
658 		return (p + 1);
659 						/* Line number */
660 	case '0': case '1': case '2': case '3': case '4':
661 	case '5': case '6': case '7': case '8': case '9':
662 		a->type = AT_LINE;
663 		a->u.l = strtol(p, &end, 10);
664 		return (end);
665 	default:
666 		err(COMPILE, "expected context address");
667 		return (NULL);
668 	}
669 }
670 
671 /*
672  * Return a copy of all the characters up to \n or \0
673  */
674 static char *
675 duptoeol(s)
676 	register char *s;
677 {
678 	size_t len;
679 	char *start;
680 
681 	for (start = s; *s != '\0' && *s != '\n'; ++s);
682 	*s = '\0';
683 	len = s - start + 1;
684 	return (memmove(xmalloc(len), start, len));
685 }
686 
687 /*
688  * Find the label contained in the command l in the command linked list cp.
689  * L is excluded from the search.  Return NULL if not found.
690  */
691 static struct s_command *
692 findlabel(l, cp)
693 	struct s_command *l, *cp;
694 {
695 	struct s_command *r;
696 
697 	for (; cp; cp = cp->next)
698 		if (cp->code == ':' && cp != l && strcmp(l->t, cp->t) == 0)
699 			return (cp);
700 		else if (cp->code == '{' && (r = findlabel(l, cp->u.c)))
701 			return (r);
702 	return (NULL);
703 }
704 
705 /*
706  * Convert goto label names to addresses.
707  * Detect duplicate labels.
708  * Set appendnum to the number of a and r commands in the script.
709  * Free the memory used by labels in b and t commands (but not by :)
710  * Root is a pointer to the script linked list; cp points to the
711  * search start.
712  * TODO: Remove } nodes
713  */
714 static void
715 fixuplabel(root, cp, end)
716 	struct s_command *root, *cp, *end;
717 {
718 	struct s_command *cp2;
719 
720 	for (; cp != end; cp = cp->next)
721 		switch (cp->code) {
722 		case ':':
723 			if (findlabel(cp, root))
724 				err(COMPILE2, "duplicate label %s", cp->t);
725 			break;
726 		case 'a':
727 		case 'r':
728 			appendnum++;
729 			break;
730 		case 'b':
731 		case 't':
732 			if (cp->t == NULL) {
733 				cp->u.c = NULL;
734 				break;
735 			}
736 			if ((cp2 = findlabel(cp, root)) == NULL)
737 				err(COMPILE2, "undefined label '%s'", cp->t);
738 			free(cp->t);
739 			cp->u.c = cp2;
740 			break;
741 		case '{':
742 			fixuplabel(root, cp->u.c, cp->next);
743 			break;
744 		}
745 }
746