184441f8Garrett D'Amore/*
284441f8Garrett D'Amore * Copyright 2010 Nexenta Systems, Inc.  All rights reserved.
384441f8Garrett D'Amore * Copyright (c) 1992 Diomidis Spinellis.
484441f8Garrett D'Amore * Copyright (c) 1992, 1993
584441f8Garrett D'Amore *	The Regents of the University of California.  All rights reserved.
684441f8Garrett D'Amore *
784441f8Garrett D'Amore * This code is derived from software contributed to Berkeley by
884441f8Garrett D'Amore * Diomidis Spinellis of Imperial College, University of London.
984441f8Garrett D'Amore *
1084441f8Garrett D'Amore * Redistribution and use in source and binary forms, with or without
1184441f8Garrett D'Amore * modification, are permitted provided that the following conditions
1284441f8Garrett D'Amore * are met:
1384441f8Garrett D'Amore * 1. Redistributions of source code must retain the above copyright
1484441f8Garrett D'Amore *    notice, this list of conditions and the following disclaimer.
1584441f8Garrett D'Amore * 2. Redistributions in binary form must reproduce the above copyright
1684441f8Garrett D'Amore *    notice, this list of conditions and the following disclaimer in the
1784441f8Garrett D'Amore *    documentation and/or other materials provided with the distribution.
1884441f8Garrett D'Amore * 4. Neither the name of the University nor the names of its contributors
1984441f8Garrett D'Amore *    may be used to endorse or promote products derived from this software
2084441f8Garrett D'Amore *    without specific prior written permission.
2184441f8Garrett D'Amore *
2284441f8Garrett D'Amore * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
2384441f8Garrett D'Amore * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2484441f8Garrett D'Amore * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2584441f8Garrett D'Amore * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
2684441f8Garrett D'Amore * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2784441f8Garrett D'Amore * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2884441f8Garrett D'Amore * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2984441f8Garrett D'Amore * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
3084441f8Garrett D'Amore * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
3184441f8Garrett D'Amore * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3284441f8Garrett D'Amore * SUCH DAMAGE.
3384441f8Garrett D'Amore */
3484441f8Garrett D'Amore
3584441f8Garrett D'Amore#include <sys/types.h>
3684441f8Garrett D'Amore#include <sys/stat.h>
3784441f8Garrett D'Amore
3884441f8Garrett D'Amore#include <ctype.h>
3984441f8Garrett D'Amore#include <err.h>
4084441f8Garrett D'Amore#include <errno.h>
4184441f8Garrett D'Amore#include <fcntl.h>
4284441f8Garrett D'Amore#include <limits.h>
4384441f8Garrett D'Amore#include <regex.h>
4484441f8Garrett D'Amore#include <stdio.h>
4584441f8Garrett D'Amore#include <stdlib.h>
4684441f8Garrett D'Amore#include <string.h>
4784441f8Garrett D'Amore#include <wchar.h>
4884441f8Garrett D'Amore#include <libintl.h>
4984441f8Garrett D'Amore#include <note.h>
5084441f8Garrett D'Amore
5184441f8Garrett D'Amore#include "defs.h"
5284441f8Garrett D'Amore#include "extern.h"
5384441f8Garrett D'Amore
5484441f8Garrett D'Amore#define	LHSZ	128
5584441f8Garrett D'Amore#define	LHMASK	(LHSZ - 1)
5684441f8Garrett D'Amorestatic struct labhash {
5784441f8Garrett D'Amore	struct	labhash *lh_next;
5884441f8Garrett D'Amore	uint_t	lh_hash;
5984441f8Garrett D'Amore	struct	s_command *lh_cmd;
6084441f8Garrett D'Amore	int	lh_ref;
6184441f8Garrett D'Amore} *labels[LHSZ];
6284441f8Garrett D'Amore
6384441f8Garrett D'Amorestatic char	 *compile_addr(char *, struct s_addr *);
6484441f8Garrett D'Amorestatic char	 *compile_ccl(char **, char *);
6584441f8Garrett D'Amorestatic char	 *compile_delimited(char *, char *, int);
6684441f8Garrett D'Amorestatic char	 *compile_flags(char *, struct s_subst *);
6784441f8Garrett D'Amorestatic regex_t	 *compile_re(char *, int);
6884441f8Garrett D'Amorestatic char	 *compile_subst(char *, struct s_subst *);
6984441f8Garrett D'Amorestatic char	 *compile_text(void);
7084441f8Garrett D'Amorestatic char	 *compile_tr(char *, struct s_tr **);
7184441f8Garrett D'Amorestatic struct s_command
7284441f8Garrett D'Amore		**compile_stream(struct s_command **);
7384441f8Garrett D'Amorestatic char	 *duptoeol(char *, const char *);
7484441f8Garrett D'Amorestatic void	  enterlabel(struct s_command *);
7584441f8Garrett D'Amorestatic struct s_command
7684441f8Garrett D'Amore		 *findlabel(char *);
7784441f8Garrett D'Amorestatic void	  fixuplabel(struct s_command *, struct s_command *);
7884441f8Garrett D'Amorestatic void	  uselabel(void);
7984441f8Garrett D'Amore
8084441f8Garrett D'Amore/*
8184441f8Garrett D'Amore * Command specification.  This is used to drive the command parser.
8284441f8Garrett D'Amore */
8384441f8Garrett D'Amorestruct s_format {
8484441f8Garrett D'Amore	char code;				/* Command code */
8584441f8Garrett D'Amore	int naddr;				/* Number of address args */
8684441f8Garrett D'Amore	enum e_args args;			/* Argument type */
8784441f8Garrett D'Amore};
8884441f8Garrett D'Amore
8984441f8Garrett D'Amorestatic struct s_format cmd_fmts[] = {
9084441f8Garrett D'Amore	{'{', 2, GROUP},
9184441f8Garrett D'Amore	{'}', 0, ENDGROUP},
9284441f8Garrett D'Amore	{'a', 1, TEXT},
9384441f8Garrett D'Amore	{'b', 2, BRANCH},
9484441f8Garrett D'Amore	{'c', 2, TEXT},
9584441f8Garrett D'Amore	{'d', 2, EMPTY},
9684441f8Garrett D'Amore	{'D', 2, EMPTY},
9784441f8Garrett D'Amore	{'g', 2, EMPTY},
9884441f8Garrett D'Amore	{'G', 2, EMPTY},
9984441f8Garrett D'Amore	{'h', 2, EMPTY},
10084441f8Garrett D'Amore	{'H', 2, EMPTY},
10184441f8Garrett D'Amore	{'i', 1, TEXT},
10284441f8Garrett D'Amore	{'l', 2, EMPTY},
10384441f8Garrett D'Amore	{'n', 2, EMPTY},
10484441f8Garrett D'Amore	{'N', 2, EMPTY},
10584441f8Garrett D'Amore	{'p', 2, EMPTY},
10684441f8Garrett D'Amore	{'P', 2, EMPTY},
10784441f8Garrett D'Amore	{'q', 1, EMPTY},
10884441f8Garrett D'Amore	{'r', 1, RFILE},
10984441f8Garrett D'Amore	{'s', 2, SUBST},
11084441f8Garrett D'Amore	{'t', 2, BRANCH},
11184441f8Garrett D'Amore	{'w', 2, WFILE},
11284441f8Garrett D'Amore	{'x', 2, EMPTY},
11384441f8Garrett D'Amore	{'y', 2, TR},
11484441f8Garrett D'Amore	{'!', 2, NONSEL},
11584441f8Garrett D'Amore	{':', 0, LABEL},
11684441f8Garrett D'Amore	{'#', 0, COMMENT},
11784441f8Garrett D'Amore	{'=', 1, EMPTY},
11884441f8Garrett D'Amore	{'\0', 0, COMMENT},
11984441f8Garrett D'Amore};
12084441f8Garrett D'Amore
12184441f8Garrett D'Amore/* The compiled program. */
12284441f8Garrett D'Amorestruct s_command *prog;
12384441f8Garrett D'Amore
12484441f8Garrett D'Amore/*
12584441f8Garrett D'Amore * Compile the program into prog.
12684441f8Garrett D'Amore * Initialise appends.
12784441f8Garrett D'Amore */
12884441f8Garrett D'Amorevoid
12984441f8Garrett D'Amorecompile(void)
13084441f8Garrett D'Amore{
13184441f8Garrett D'Amore	*compile_stream(&prog) = NULL;
13284441f8Garrett D'Amore	fixuplabel(prog, NULL);
13384441f8Garrett D'Amore	uselabel();
13484441f8Garrett D'Amore	if (appendnum == 0)
13584441f8Garrett D'Amore		appends = NULL;
13684441f8Garrett D'Amore	else if ((appends = malloc(sizeof (struct s_appends) * appendnum)) ==
13784441f8Garrett D'Amore	    NULL)
13884441f8Garrett D'Amore		err(1, "malloc");
13984441f8Garrett D'Amore	if ((match = malloc((maxnsub + 1) * sizeof (regmatch_t))) == NULL)
14084441f8Garrett D'Amore		err(1, "malloc");
14184441f8Garrett D'Amore}
14284441f8Garrett D'Amore
14384441f8Garrett D'Amore#define	EATSPACE() do {							\
14484441f8Garrett D'Amore	if (p)								\
14584441f8Garrett D'Amore		while (*p && isspace((unsigned char)*p))                \
14684441f8Garrett D'Amore			p++;						\
14784441f8Garrett D'Amore	_NOTE(CONSTCOND)						\
14884441f8Garrett D'Amore} while (0)
14984441f8Garrett D'Amore
15084441f8Garrett D'Amorestatic struct s_command **
15184441f8Garrett D'Amorecompile_stream(struct s_command **link)
15284441f8Garrett D'Amore{
15384441f8Garrett D'Amore	char *p;
15484441f8Garrett D'Amore	static char lbuf[_POSIX2_LINE_MAX + 1];	/* To save stack */
15584441f8Garrett D'Amore	struct s_command *cmd, *cmd2, *stack;
15684441f8Garrett D'Amore	struct s_format *fp;
15784441f8Garrett D'Amore	char re[_POSIX2_LINE_MAX + 1];
15884441f8Garrett D'Amore	int naddr;				/* Number of addresses */
15984441f8Garrett D'Amore
16084441f8Garrett D'Amore	stack = 0;
16184441f8Garrett D'Amore	for (;;) {
16284441f8Garrett D'Amore		if ((p = cu_fgets(lbuf, sizeof (lbuf), NULL)) == NULL) {
16384441f8Garrett D'Amore			if (stack != 0)
16484441f8Garrett D'Amore				fatal(_("unexpected EOF (pending }'s)"));
16584441f8Garrett D'Amore			return (link);
16684441f8Garrett D'Amore		}
16784441f8Garrett D'Amore
16884441f8Garrett D'Amoresemicolon:	EATSPACE();
16984441f8Garrett D'Amore		if (p) {
17084441f8Garrett D'Amore			if (*p == '#' || *p == '\0')
17184441f8Garrett D'Amore				continue;
17284441f8Garrett D'Amore			else if (*p == ';') {
17384441f8Garrett D'Amore				p++;
17484441f8Garrett D'Amore				goto semicolon;
17584441f8Garrett D'Amore			}
17684441f8Garrett D'Amore		}
17784441f8Garrett D'Amore		if ((*link = cmd = malloc(sizeof (struct s_command))) == NULL)
17884441f8Garrett D'Amore			err(1, "malloc");
17984441f8Garrett D'Amore		link = &cmd->next;
18084441f8Garrett D'Amore		cmd->startline = cmd->nonsel = 0;
18184441f8Garrett D'Amore		/* First parse the addresses */
18284441f8Garrett D'Amore		naddr = 0;
18384441f8Garrett D'Amore
18484441f8Garrett D'Amore/* Valid characters to start an address */
18584441f8Garrett D'Amore#define	addrchar(c)	(strchr("0123456789/\\$", (c)))
18684441f8Garrett D'Amore		if (addrchar(*p)) {
18784441f8Garrett D'Amore			naddr++;
18884441f8Garrett D'Amore			if ((cmd->a1 = malloc(sizeof (struct s_addr))) == NULL)
18984441f8Garrett D'Amore				err(1, "malloc");
19084441f8Garrett D'Amore			p = compile_addr(p, cmd->a1);
19184441f8Garrett D'Amore			EATSPACE();				/* EXTENSION */
19284441f8Garrett D'Amore			if (*p == ',') {
19384441f8Garrett D'Amore				p++;
19484441f8Garrett D'Amore				EATSPACE();			/* EXTENSION */
19584441f8Garrett D'Amore				naddr++;
19684441f8Garrett D'Amore				if ((cmd->a2 = malloc(sizeof (struct s_addr)))
19784441f8Garrett D'Amore				    == NULL)
19884441f8Garrett D'Amore					err(1, "malloc");
19984441f8Garrett D'Amore				p = compile_addr(p, cmd->a2);
20084441f8Garrett D'Amore				EATSPACE();
20184441f8Garrett D'Amore			} else
20284441f8Garrett D'Amore				cmd->a2 = 0;
20384441f8Garrett D'Amore		} else
20484441f8Garrett D'Amore			cmd->a1 = cmd->a2 = 0;
20584441f8Garrett D'Amore
20684441f8Garrett D'Amorenonsel:		/* Now parse the command */
20784441f8Garrett D'Amore		if (!*p)
20884441f8Garrett D'Amore			fatal(_("command expected"));
20984441f8Garrett D'Amore		cmd->code = *p;
21084441f8Garrett D'Amore		for (fp = cmd_fmts; fp->code; fp++)
21184441f8Garrett D'Amore			if (fp->code == *p)
21284441f8Garrett D'Amore				break;
21384441f8Garrett D'Amore		if (!fp->code)
21484441f8Garrett D'Amore			fatal(_("invalid command code %c"), *p);
21584441f8Garrett D'Amore		if (naddr > fp->naddr)
21684441f8Garrett D'Amore			fatal(_("command %c expects up to %d address(es), "
21784441f8Garrett D'Amore			    "found %d"), *p, fp->naddr, naddr);
21884441f8Garrett D'Amore		switch (fp->args) {
21984441f8Garrett D'Amore		case NONSEL:			/* ! */
22084441f8Garrett D'Amore			p++;
22184441f8Garrett D'Amore			EATSPACE();
22284441f8Garrett D'Amore			cmd->nonsel = 1;
22384441f8Garrett D'Amore			goto nonsel;
22484441f8Garrett D'Amore		case GROUP:			/* { */
22584441f8Garrett D'Amore			p++;
22684441f8Garrett D'Amore			EATSPACE();
22784441f8Garrett D'Amore			cmd->next = stack;
22884441f8Garrett D'Amore			stack = cmd;
22984441f8Garrett D'Amore			link = &cmd->u.c;
23084441f8Garrett D'Amore			if (*p)
23184441f8Garrett D'Amore				goto semicolon;
23284441f8Garrett D'Amore			break;
23384441f8Garrett D'Amore		case ENDGROUP:
23484441f8Garrett D'Amore			/*
23584441f8Garrett D'Amore			 * Short-circuit command processing, since end of
23684441f8Garrett D'Amore			 * group is really just a noop.
23784441f8Garrett D'Amore			 */
23884441f8Garrett D'Amore			cmd->nonsel = 1;
23984441f8Garrett D'Amore			if (stack == 0)
24084441f8Garrett D'Amore				fatal(_("unexpected }"));
24184441f8Garrett D'Amore			cmd2 = stack;
24284441f8Garrett D'Amore			stack = cmd2->next;
24384441f8Garrett D'Amore			cmd2->next = cmd;
24484441f8Garrett D'Amore			/*FALLTHROUGH*/
24584441f8Garrett D'Amore		case EMPTY:		/* d D g G h H l n N p P q x = \0 */
24684441f8Garrett D'Amore			p++;
24784441f8Garrett D'Amore			EATSPACE();
24884441f8Garrett D'Amore			if (*p == ';') {
24984441f8Garrett D'Amore				p++;
25084441f8Garrett D'Amore				link = &cmd->next;
25184441f8Garrett D'Amore				goto semicolon;
25284441f8Garrett D'Amore			}
25384441f8Garrett D'Amore			if (*p)
25484441f8Garrett D'Amore				fatal(_("extra characters at the end of %c "
25584441f8Garrett D'Amore				    "command"), cmd->code);
25684441f8Garrett D'Amore			break;
25784441f8Garrett D'Amore		case TEXT:			/* a c i */
25884441f8Garrett D'Amore			p++;
25984441f8Garrett D'Amore			EATSPACE();
26084441f8Garrett D'Amore			if (*p != '\\')
26184441f8Garrett D'Amore				fatal(_("command %c expects \\ "
26284441f8Garrett D'Amore				    "followed by text"), cmd->code);
26384441f8Garrett D'Amore			p++;
26484441f8Garrett D'Amore			EATSPACE();
26584441f8Garrett D'Amore			if (*p)
26684441f8Garrett D'Amore				fatal(_("extra characters after \\ "
26784441f8Garrett D'Amore				    "at the end of %c command"),
26884441f8Garrett D'Amore				    cmd->code);
26984441f8Garrett D'Amore			cmd->t = compile_text();
27084441f8Garrett D'Amore			break;
27184441f8Garrett D'Amore		case COMMENT:			/* \0 # */
27284441f8Garrett D'Amore			break;
27384441f8Garrett D'Amore		case WFILE:			/* w */
27484441f8Garrett D'Amore			p++;
27584441f8Garrett D'Amore			EATSPACE();
27684441f8Garrett D'Amore			if (*p == '\0')
27784441f8Garrett D'Amore				fatal(_("filename expected"));
27884441f8Garrett D'Amore			cmd->t = duptoeol(p, "w command");
27984441f8Garrett D'Amore			if (aflag)
28084441f8Garrett D'Amore				cmd->u.fd = -1;
28184441f8Garrett D'Amore			else if ((cmd->u.fd = open(p,
28284441f8Garrett D'Amore			    O_WRONLY|O_APPEND|O_CREAT|O_TRUNC, 0666)) == -1)
28384441f8Garrett D'Amore				err(1, "%s", p);
28484441f8Garrett D'Amore			break;
28584441f8Garrett D'Amore		case RFILE:			/* r */
28684441f8Garrett D'Amore			p++;
28784441f8Garrett D'Amore			EATSPACE();
28884441f8Garrett D'Amore			if (*p == '\0')
28984441f8Garrett D'Amore				fatal(_("filename expected"));
29084441f8Garrett D'Amore			else
29184441f8Garrett D'Amore				cmd->t = duptoeol(p, "read command");
29284441f8Garrett D'Amore			break;
29384441f8Garrett D'Amore		case BRANCH:			/* b t */
29484441f8Garrett D'Amore			p++;
29584441f8Garrett D'Amore			EATSPACE();
29684441f8Garrett D'Amore			if (*p == '\0')
29784441f8Garrett D'Amore				cmd->t = NULL;
29884441f8Garrett D'Amore			else
29984441f8Garrett D'Amore				cmd->t = duptoeol(p, "branch");
30084441f8Garrett D'Amore			break;
30184441f8Garrett D'Amore		case LABEL:			/* : */
30284441f8Garrett D'Amore			p++;
30384441f8Garrett D'Amore			EATSPACE();
30484441f8Garrett D'Amore			cmd->t = duptoeol(p, "label");
30584441f8Garrett D'Amore			if (strlen(p) == 0)
30684441f8Garrett D'Amore				fatal(_("empty label"));
30784441f8Garrett D'Amore			enterlabel(cmd);
30884441f8Garrett D'Amore			break;
30984441f8Garrett D'Amore		case SUBST:			/* s */
31084441f8Garrett D'Amore			p++;
31184441f8Garrett D'Amore			if (*p == '\0' || *p == '\\')
31284441f8Garrett D'Amore				fatal(_("substitute pattern can not "
31384441f8Garrett D'Amore				    "be delimited by newline or backslash"));
31484441f8Garrett D'Amore			if ((cmd->u.s = calloc(1, sizeof (struct s_subst))) ==
31584441f8Garrett D'Amore			    NULL)
31684441f8Garrett D'Amore				err(1, "malloc");
31784441f8Garrett D'Amore			p = compile_delimited(p, re, 0);
31884441f8Garrett D'Amore			if (p == NULL)
31984441f8Garrett D'Amore				fatal(_("unterminated substitute pattern"));
32084441f8Garrett D'Amore
32184441f8Garrett D'Amore			/* Compile RE with no case sensitivity temporarily */
32284441f8Garrett D'Amore			if (*re == '\0')
32384441f8Garrett D'Amore				cmd->u.s->re = NULL;
32484441f8Garrett D'Amore			else
32584441f8Garrett D'Amore				cmd->u.s->re = compile_re(re, 0);
32684441f8Garrett D'Amore			--p;
32784441f8Garrett D'Amore			p = compile_subst(p, cmd->u.s);
32884441f8Garrett D'Amore			p = compile_flags(p, cmd->u.s);
32984441f8Garrett D'Amore
33084441f8Garrett D'Amore			/* Recompile RE with case sens. from "I" flag if any */
33184441f8Garrett D'Amore			if (*re == '\0')
33284441f8Garrett D'Amore				cmd->u.s->re = NULL;
33384441f8Garrett D'Amore			else
33484441f8Garrett D'Amore				cmd->u.s->re = compile_re(re, cmd->u.s->icase);
33584441f8Garrett D'Amore			EATSPACE();
33684441f8Garrett D'Amore			if (*p == ';') {
33784441f8Garrett D'Amore				p++;
33884441f8Garrett D'Amore				link = &cmd->next;
33984441f8Garrett D'Amore				goto semicolon;
34084441f8Garrett D'Amore			}
34184441f8Garrett D'Amore			break;
34284441f8Garrett D'Amore		case TR:			/* y */
34384441f8Garrett D'Amore			p++;
34484441f8Garrett D'Amore			p = compile_tr(p, &cmd->u.y);
34584441f8Garrett D'Amore			EATSPACE();
34684441f8Garrett D'Amore			if (*p == ';') {
34784441f8Garrett D'Amore				p++;
34884441f8Garrett D'Amore				link = &cmd->next;
34984441f8Garrett D'Amore				goto semicolon;
35084441f8Garrett D'Amore			}
35184441f8Garrett D'Amore			if (*p)
35284441f8Garrett D'Amore				fatal(_("extra text at the end of a "
35384441f8Garrett D'Amore				    "transform command"));
35484441f8Garrett D'Amore			break;
35584441f8Garrett D'Amore		}
35684441f8Garrett D'Amore	}
35784441f8Garrett D'Amore}
35884441f8Garrett D'Amore
35984441f8Garrett D'Amore/*
36084441f8Garrett D'Amore * Get a delimited string.  P points to the delimeter of the string; d points
36184441f8Garrett D'Amore * to a buffer area.  Newline and delimiter escapes are processed; other
36284441f8Garrett D'Amore * escapes are ignored.
36384441f8Garrett D'Amore *
36484441f8Garrett D'Amore * Returns a pointer to the first character after the final delimiter or NULL
36584441f8Garrett D'Amore * in the case of a non-terminated string.  The character array d is filled
36684441f8Garrett D'Amore * with the processed string.
36784441f8Garrett D'Amore */
36884441f8Garrett D'Amorestatic char *
36984441f8Garrett D'Amorecompile_delimited(char *p, char *d, int is_tr)
37084441f8Garrett D'Amore{
37184441f8Garrett D'Amore	char c;
37284441f8Garrett D'Amore
37384441f8Garrett D'Amore	c = *p++;
37484441f8Garrett D'Amore	if (c == '\0')
37584441f8Garrett D'Amore		return (NULL);
37684441f8Garrett D'Amore	else if (c == '\\')
37784441f8Garrett D'Amore		fatal(_("\\ can not be used as a string delimiter"));
37884441f8Garrett D'Amore	else if (c == '\n')
37984441f8Garrett D'Amore		fatal(_("newline can not be used as a string delimiter"));
38084441f8Garrett D'Amore	while (*p) {
38184441f8Garrett D'Amore		if (*p == '[' && *p != c) {
38284441f8Garrett D'Amore			if ((d = compile_ccl(&p, d)) == NULL)
38384441f8Garrett D'Amore				fatal(_("unbalanced brackets ([])"));
38484441f8Garrett D'Amore			continue;
38584441f8Garrett D'Amore		} else if (*p == '\\' && p[1] == '[') {
38684441f8Garrett D'Amore			*d++ = *p++;
38784441f8Garrett D'Amore		} else if (*p == '\\' && p[1] == c)
38884441f8Garrett D'Amore			p++;
38984441f8Garrett D'Amore		else if (*p == '\\' && p[1] == 'n') {
39084441f8Garrett D'Amore			*d++ = '\n';
39184441f8Garrett D'Amore			p += 2;
39284441f8Garrett D'Amore			continue;
39384441f8Garrett D'Amore		} else if (*p == '\\' && p[1] == '\\') {
39484441f8Garrett D'Amore			if (is_tr)
39584441f8Garrett D'Amore				p++;
39684441f8Garrett D'Amore			else
39784441f8Garrett D'Amore				*d++ = *p++;
39884441f8Garrett D'Amore		} else if (*p == c) {
39984441f8Garrett D'Amore			*d = '\0';
40084441f8Garrett D'Amore			return (p + 1);
40184441f8Garrett D'Amore		}
40284441f8Garrett D'Amore		*d++ = *p++;
40384441f8Garrett D'Amore	}
40484441f8Garrett D'Amore	return (NULL);
40584441f8Garrett D'Amore}
40684441f8Garrett D'Amore
40784441f8Garrett D'Amore
40884441f8Garrett D'Amore/* compile_ccl: expand a POSIX character class */
40984441f8Garrett D'Amorestatic char *
41084441f8Garrett D'Amorecompile_ccl(char **sp, char *t)
41184441f8Garrett D'Amore{
41284441f8Garrett D'Amore	int c, d;
41384441f8Garrett D'Amore	char *s = *sp;
41484441f8Garrett D'Amore
41584441f8Garrett D'Amore	*t++ = *s++;
41684441f8Garrett D'Amore	if (*s == '^')
41784441f8Garrett D'Amore		*t++ = *s++;
41884441f8Garrett D'Amore	if (*s == ']')
41984441f8Garrett D'Amore		*t++ = *s++;
42084441f8Garrett D'Amore	for (; *s && (*t = *s) != ']'; s++, t++)
42184441f8Garrett D'Amore		if (*s == '[' &&
42284441f8Garrett D'Amore		    ((d = *(s+1)) == '.' || d == ':' || d == '=')) {
42384441f8Garrett D'Amore			*++t = *++s, t++, s++;
42484441f8Garrett D'Amore			for (c = *s; (*t = *s) != ']' || c != d; s++, t++)
42584441f8Garrett D'Amore				if ((c = *s) == '\0')
42684441f8Garrett D'Amore					return (NULL);
42784441f8Garrett D'Amore		}
42884441f8Garrett D'Amore	return ((*s == ']') ? *sp = ++s, ++t : NULL);
42984441f8Garrett D'Amore}
43084441f8Garrett D'Amore
43184441f8Garrett D'Amore/*
43284441f8Garrett D'Amore * Compiles the regular expression in RE and returns a pointer to the compiled
43384441f8Garrett D'Amore * regular expression.
43484441f8Garrett D'Amore * Cflags are passed to regcomp.
43584441f8Garrett D'Amore */
43684441f8Garrett D'Amorestatic regex_t *
43784441f8Garrett D'Amorecompile_re(char *re, int case_insensitive)
43884441f8Garrett D'Amore{
43984441f8Garrett D'Amore	regex_t *rep;
44084441f8Garrett D'Amore	int eval, flags;
44184441f8Garrett D'Amore
44284441f8Garrett D'Amore
44384441f8Garrett D'Amore	flags = rflags;
44484441f8Garrett D'Amore	if (case_insensitive)
44584441f8Garrett D'Amore		flags |= REG_ICASE;
44684441f8Garrett D'Amore	if ((rep = malloc(sizeof (regex_t))) == NULL)
44784441f8Garrett D'Amore		err(1, "malloc");
44884441f8Garrett D'Amore	if ((eval = regcomp(rep, re, flags)) != 0)
44984441f8Garrett D'Amore		fatal(_("RE error: %s"), strregerror(eval, rep));
45084441f8Garrett D'Amore	if (maxnsub < rep->re_nsub)
45184441f8Garrett D'Amore		maxnsub = rep->re_nsub;
45284441f8Garrett D'Amore	return (rep);
45384441f8Garrett D'Amore}
45484441f8Garrett D'Amore
45584441f8Garrett D'Amore/*
45684441f8Garrett D'Amore * Compile the substitution string of a regular expression and set res to
45784441f8Garrett D'Amore * point to a saved copy of it.  Nsub is the number of parenthesized regular
45884441f8Garrett D'Amore * expressions.
45984441f8Garrett D'Amore */
46084441f8Garrett D'Amorestatic char *
46184441f8Garrett D'Amorecompile_subst(char *p, struct s_subst *s)
46284441f8Garrett D'Amore{
46384441f8Garrett D'Amore	static char lbuf[_POSIX2_LINE_MAX + 1];
46484441f8Garrett D'Amore	int asize;
46584441f8Garrett D'Amore	uintptr_t size;
46684441f8Garrett D'Amore	uchar_t ref;
46784441f8Garrett D'Amore	char c, *text, *op, *sp;
46884441f8Garrett D'Amore	int more = 1, sawesc = 0;
46984441f8Garrett D'Amore
47084441f8Garrett D'Amore	c = *p++;			/* Terminator character */
47184441f8Garrett D'Amore	if (c == '\0')
47284441f8Garrett D'Amore		return (NULL);
47384441f8Garrett D'Amore
47484441f8Garrett D'Amore	s->maxbref = 0;
47584441f8Garrett D'Amore	s->linenum = linenum;
47684441f8Garrett D'Amore	asize = 2 * _POSIX2_LINE_MAX + 1;
47784441f8Garrett D'Amore	if ((text = malloc(asize)) == NULL)
47884441f8Garrett D'Amore		err(1, "malloc");
47984441f8Garrett D'Amore	size = 0;
48084441f8Garrett D'Amore	do {
48184441f8Garrett D'Amore		op = sp = text + size;
48284441f8Garrett D'Amore		for (; *p; p++) {
48384441f8Garrett D'Amore			if (*p == '\\' || sawesc) {
48484441f8Garrett D'Amore				/*
48584441f8Garrett D'Amore				 * If this is a continuation from the last
48684441f8Garrett D'Amore				 * buffer, we won't have a character to
48784441f8Garrett D'Amore				 * skip over.
48884441f8Garrett D'Amore				 */
48984441f8Garrett D'Amore				if (sawesc)
49084441f8Garrett D'Amore					sawesc = 0;
49184441f8Garrett D'Amore				else
49284441f8Garrett D'Amore					p++;
49384441f8Garrett D'Amore
49484441f8Garrett D'Amore				if (*p == '\0') {
49584441f8Garrett D'Amore					/*
49684441f8Garrett D'Amore					 * This escaped character is continued
49784441f8Garrett D'Amore					 * in the next part of the line.  Note
49884441f8Garrett D'Amore					 * this fact, then cause the loop to
49984441f8Garrett D'Amore					 * exit w/ normal EOL case and reenter
50084441f8Garrett D'Amore					 * above with the new buffer.
50184441f8Garrett D'Amore					 */
50284441f8Garrett D'Amore					sawesc = 1;
50384441f8Garrett D'Amore					p--;
50484441f8Garrett D'Amore					continue;
50584441f8Garrett D'Amore				} else if (strchr("123456789", *p) != NULL) {
50684441f8Garrett D'Amore					*sp++ = '\\';
50784441f8Garrett D'Amore					ref = *p - '0';
50884441f8Garrett D'Amore					if (s->re != NULL &&
50984441f8Garrett D'Amore					    ref > s->re->re_nsub)
51084441f8Garrett D'Amore						fatal(_("not defined in "
51184441f8Garrett D'Amore						    "the RE: \\%c"), *p);
51284441f8Garrett D'Amore					if (s->maxbref < ref)
51384441f8Garrett D'Amore						s->maxbref = ref;
51484441f8Garrett D'Amore				} else if (*p == '&' || *p == '\\')
51584441f8Garrett D'Amore					*sp++ = '\\';
51684441f8Garrett D'Amore			} else if (*p == c) {
51784441f8Garrett D'Amore				if (*++p == '\0' && more) {
51884441f8Garrett D'Amore					if (cu_fgets(lbuf, sizeof (lbuf),
51984441f8Garrett D'Amore					    &more))
52084441f8Garrett D'Amore						p = lbuf;
52184441f8Garrett D'Amore				}
52284441f8Garrett D'Amore				*sp++ = '\0';
52384441f8Garrett D'Amore				size += (uintptr_t)sp - (uintptr_t)op;
52484441f8Garrett D'Amore				if ((s->new = realloc(text, size)) == NULL)
52584441f8Garrett D'Amore					err(1, "realloc");
52684441f8Garrett D'Amore				return (p);
52784441f8Garrett D'Amore			} else if (*p == '\n') {
52884441f8Garrett D'Amore				fatal(_("unescaped newline inside "
52984441f8Garrett D'Amore				    "substitute pattern"));
53084441f8Garrett D'Amore				/* NOTREACHED */
53184441f8Garrett D'Amore			}
53284441f8Garrett D'Amore			*sp++ = *p;
53384441f8Garrett D'Amore		}
53484441f8Garrett D'Amore		size += (uintptr_t)sp - (uintptr_t)op;
53584441f8Garrett D'Amore		if (asize - size < _POSIX2_LINE_MAX + 1) {
53684441f8Garrett D'Amore			asize *= 2;
53784441f8Garrett D'Amore			if ((text = realloc(text, asize)) == NULL)
53884441f8Garrett D'Amore				err(1, "realloc");
53984441f8Garrett D'Amore		}
54084441f8Garrett D'Amore	} while (cu_fgets(p = lbuf, sizeof (lbuf), &more));
54184441f8Garrett D'Amore	fatal(_("unterminated substitute in regular expression"));
54284441f8Garrett D'Amore	return (NULL);
54384441f8Garrett D'Amore}
54484441f8Garrett D'Amore
54584441f8Garrett D'Amore/*
54684441f8Garrett D'Amore * Compile the flags of the s command
54784441f8Garrett D'Amore */
54884441f8Garrett D'Amorestatic char *
54984441f8Garrett D'Amorecompile_flags(char *p, struct s_subst *s)
55084441f8Garrett D'Amore{
55184441f8Garrett D'Amore	int gn;			/* True if we have seen g or n */
55284441f8Garrett D'Amore	unsigned long nval;
55384441f8Garrett D'Amore	char wfile[_POSIX2_LINE_MAX + 1], *q;
55484441f8Garrett D'Amore
55584441f8Garrett D'Amore	s->n = 1;				/* Default */
55684441f8Garrett D'Amore	s->p = 0;
55784441f8Garrett D'Amore	s->wfile = NULL;
55884441f8Garrett D'Amore	s->wfd = -1;
55984441f8Garrett D'Amore	s->icase = 0;
56084441f8Garrett D'Amore	gn = 0;
56184441f8Garrett D'Amore	for (;;) {
56284441f8Garrett D'Amore		EATSPACE();			/* EXTENSION */
56384441f8Garrett D'Amore		switch (*p) {
56484441f8Garrett D'Amore		case 'g':
56584441f8Garrett D'Amore			if (gn)
56684441f8Garrett D'Amore				fatal(_("more than one number or "
56784441f8Garrett D'Amore				    "'g' in substitute flags"));
56884441f8Garrett D'Amore			gn = 1;
56984441f8Garrett D'Amore			s->n = 0;
57084441f8Garrett D'Amore			break;
57184441f8Garrett D'Amore		case '\0':
57284441f8Garrett D'Amore		case '\n':
57384441f8Garrett D'Amore		case ';':
57484441f8Garrett D'Amore			return (p);
57584441f8Garrett D'Amore		case 'p':
57684441f8Garrett D'Amore			s->p = 1;
57784441f8Garrett D'Amore			break;
57884441f8Garrett D'Amore		case 'I':
57984441f8Garrett D'Amore			s->icase = 1;
58084441f8Garrett D'Amore			break;
58184441f8Garrett D'Amore		case '1': case '2': case '3':
58284441f8Garrett D'Amore		case '4': case '5': case '6':
58384441f8Garrett D'Amore		case '7': case '8': case '9':
58484441f8Garrett D'Amore			if (gn)
58584441f8Garrett D'Amore				fatal(_("more than one number or "
58684441f8Garrett D'Amore				    "'g' in substitute flags"));
58784441f8Garrett D'Amore			gn = 1;
58884441f8Garrett D'Amore			errno = 0;
58984441f8Garrett D'Amore			nval = strtol(p, &p, 10);
59084441f8Garrett D'Amore			if (errno == ERANGE || nval > INT_MAX)
59184441f8Garrett D'Amore				fatal(_("overflow in the 'N' substitute flag"));
59284441f8Garrett D'Amore			s->n = nval;
59384441f8Garrett D'Amore			p--;
59484441f8Garrett D'Amore			break;
59584441f8Garrett D'Amore		case 'w':
59684441f8Garrett D'Amore			p++;
59784441f8Garrett D'Amore#ifdef HISTORIC_PRACTICE
59884441f8Garrett D'Amore			if (*p != ' ') {
59984441f8Garrett D'Amore				fatal(_("space missing before w wfile"));
60084441f8Garrett D'Amore				return (p);
60184441f8Garrett D'Amore			}
60284441f8Garrett D'Amore#endif
60384441f8Garrett D'Amore			EATSPACE();
60484441f8Garrett D'Amore			q = wfile;
60584441f8Garrett D'Amore			while (*p) {
60684441f8Garrett D'Amore				if (*p == '\n')
60784441f8Garrett D'Amore					break;
60884441f8Garrett D'Amore				*q++ = *p++;
60984441f8Garrett D'Amore			}
61084441f8Garrett D'Amore			*q = '\0';
61184441f8Garrett D'Amore			if (q == wfile)
61284441f8Garrett D'Amore				fatal(_("no wfile specified"));
61384441f8Garrett D'Amore			s->wfile = strdup(wfile);
61484441f8Garrett D'Amore			if (!aflag && (s->wfd = open(wfile,
61584441f8Garrett D'Amore			    O_WRONLY|O_APPEND|O_CREAT|O_TRUNC, 0666)) == -1)
61684441f8Garrett D'Amore				err(1, "%s", wfile);
61784441f8Garrett D'Amore			return (p);
61884441f8Garrett D'Amore		default:
61984441f8Garrett D'Amore			fatal(_("bad flag in substitute command: '%c'"), *p);
62084441f8Garrett D'Amore			break;
62184441f8Garrett D'Amore		}
62284441f8Garrett D'Amore		p++;
62384441f8Garrett D'Amore	}
62484441f8Garrett D'Amore}
62584441f8Garrett D'Amore
62684441f8Garrett D'Amore/*
62784441f8Garrett D'Amore * Compile a translation set of strings into a lookup table.
62884441f8Garrett D'Amore */
62984441f8Garrett D'Amorestatic char *
63084441f8Garrett D'Amorecompile_tr(char *p, struct s_tr **py)
63184441f8Garrett D'Amore{
63284441f8Garrett D'Amore	struct s_tr *y;
63384441f8Garrett D'Amore	int i;
63484441f8Garrett D'Amore	const char *op, *np;
63584441f8Garrett D'Amore	char old[_POSIX2_LINE_MAX + 1];
63684441f8Garrett D'Amore	char new[_POSIX2_LINE_MAX + 1];
63784441f8Garrett D'Amore	size_t oclen, oldlen, nclen, newlen;
63884441f8Garrett D'Amore	mbstate_t mbs1, mbs2;
63984441f8Garrett D'Amore
64084441f8Garrett D'Amore	if ((*py = y = malloc(sizeof (*y))) == NULL)
64184441f8Garrett D'Amore		err(1, NULL);
64284441f8Garrett D'Amore	y->multis = NULL;
64384441f8Garrett D'Amore	y->nmultis = 0;
64484441f8Garrett D'Amore
64584441f8Garrett D'Amore	if (*p == '\0' || *p == '\\')
64684441f8Garrett D'Amore		fatal(_("transform pattern can not be delimited by "
64784441f8Garrett D'Amore		    "newline or backslash"));
64884441f8Garrett D'Amore	p = compile_delimited(p, old, 1);
64984441f8Garrett D'Amore	if (p == NULL)
65084441f8Garrett D'Amore		fatal(_("unterminated transform source string"));
65184441f8Garrett D'Amore	p = compile_delimited(p - 1, new, 1);
65284441f8Garrett D'Amore	if (p == NULL)
65384441f8Garrett D'Amore		fatal(_("unterminated transform target string"));
65484441f8Garrett D'Amore	EATSPACE();
65584441f8Garrett D'Amore	op = old;
65684441f8Garrett D'Amore	oldlen = mbsrtowcs(NULL, &op, 0, NULL);
65784441f8Garrett D'Amore	if (oldlen == (size_t)-1)
65884441f8Garrett D'Amore		err(1, NULL);
65984441f8Garrett D'Amore	np = new;
66084441f8Garrett D'Amore	newlen = mbsrtowcs(NULL, &np, 0, NULL);
66184441f8Garrett D'Amore	if (newlen == (size_t)-1)
66284441f8Garrett D'Amore		err(1, NULL);
66384441f8Garrett D'Amore	if (newlen != oldlen)
66484441f8Garrett D'Amore		fatal(_("transform strings are not the same length"));
66584441f8Garrett D'Amore	if (MB_CUR_MAX == 1) {
66684441f8Garrett D'Amore		/*
66784441f8Garrett D'Amore		 * The single-byte encoding case is easy: generate a
66884441f8Garrett D'Amore		 * lookup table.
66984441f8Garrett D'Amore		 */
67084441f8Garrett D'Amore		for (i = 0; i <= UCHAR_MAX; i++)
67184441f8Garrett D'Amore			y->bytetab[i] = (char)i;
67284441f8Garrett D'Amore		for (; *op; op++, np++)
67384441f8Garrett D'Amore			y->bytetab[(uchar_t)*op] = *np;
67484441f8Garrett D'Amore	} else {
67584441f8Garrett D'Amore		/*
67684441f8Garrett D'Amore		 * Multi-byte encoding case: generate a lookup table as
67784441f8Garrett D'Amore		 * above, but only for single-byte characters. The first
67884441f8Garrett D'Amore		 * bytes of multi-byte characters have their lookup table
67984441f8Garrett D'Amore		 * entries set to 0, which causes do_tr() to search through
68084441f8Garrett D'Amore		 * an auxiliary vector of multi-byte mappings.
68184441f8Garrett D'Amore		 */
68284441f8Garrett D'Amore		(void) memset(&mbs1, 0, sizeof (mbs1));
68384441f8Garrett D'Amore		(void) memset(&mbs2, 0, sizeof (mbs2));
68484441f8Garrett D'Amore		for (i = 0; i <= UCHAR_MAX; i++)
68584441f8Garrett D'Amore			y->bytetab[i] = (btowc(i) != WEOF) ? (uchar_t)i : 0;
68684441f8Garrett D'Amore		while (*op != '\0') {
68784441f8Garrett D'Amore			oclen = mbrlen(op, MB_LEN_MAX, &mbs1);
68884441f8Garrett D'Amore			if (oclen == (size_t)-1 || oclen == (size_t)-2)
68984441f8Garrett D'Amore				errx(1, "%s", strerror(EILSEQ));
69084441f8Garrett D'Amore			nclen = mbrlen(np, MB_LEN_MAX, &mbs2);
69184441f8Garrett D'Amore			if (nclen == (size_t)-1 || nclen == (size_t)-2)
69284441f8Garrett D'Amore				errx(1, "%s", strerror(EILSEQ));
69384441f8Garrett D'Amore			if (oclen == 1 && nclen == 1)
69484441f8Garrett D'Amore				y->bytetab[(uchar_t)*op] = *np;
69584441f8Garrett D'Amore			else {
69684441f8Garrett D'Amore				y->bytetab[(uchar_t)*op] = 0;
69784441f8Garrett D'Amore				y->multis = realloc(y->multis,
69884441f8Garrett D'Amore				    (y->nmultis + 1) * sizeof (*y->multis));
69984441f8Garrett D'Amore				if (y->multis == NULL)
70084441f8Garrett D'Amore					err(1, NULL);
70184441f8Garrett D'Amore				i = y->nmultis++;
70284441f8Garrett D'Amore				y->multis[i].fromlen = oclen;
70384441f8Garrett D'Amore				(void) memcpy(y->multis[i].from, op, oclen);
70484441f8Garrett D'Amore				y->multis[i].tolen = nclen;
70584441f8Garrett D'Amore				(void) memcpy(y->multis[i].to, np, nclen);
70684441f8Garrett D'Amore			}
70784441f8Garrett D'Amore			op += oclen;
70884441f8Garrett D'Amore			np += nclen;
70984441f8Garrett D'Amore		}
71084441f8Garrett D'Amore	}
71184441f8Garrett D'Amore	return (p);
71284441f8Garrett D'Amore}
71384441f8Garrett D'Amore
71484441f8Garrett D'Amore/*
71584441f8Garrett D'Amore * Compile the text following an a or i command.
71684441f8Garrett D'Amore */
71784441f8Garrett D'Amorestatic char *
71884441f8Garrett D'Amorecompile_text(void)
71984441f8Garrett D'Amore{
72084441f8Garrett D'Amore	int esc_nl;
72184441f8Garrett D'Amore	uintptr_t size, asize;
72284441f8Garrett D'Amore	char *text, *p, *op, *s;
72384441f8Garrett D'Amore	char lbuf[_POSIX2_LINE_MAX + 1];
72484441f8Garrett D'Amore
72584441f8Garrett D'Amore	asize = 2 * _POSIX2_LINE_MAX + 1;
72684441f8Garrett D'Amore	if ((text = malloc(asize)) == NULL)
72784441f8Garrett D'Amore		err(1, "malloc");
72884441f8Garrett D'Amore	size = 0;
72984441f8Garrett D'Amore	while (cu_fgets(lbuf, sizeof (lbuf), NULL)) {
73084441f8Garrett D'Amore		op = s = text + size;
73184441f8Garrett D'Amore		p = lbuf;
73284441f8Garrett D'Amore		EATSPACE();
73384441f8Garrett D'Amore		for (esc_nl = 0; *p != '\0'; p++) {
73484441f8Garrett D'Amore			if (*p == '\\' && p[1] != '\0' && *++p == '\n')
73584441f8Garrett D'Amore				esc_nl = 1;
73684441f8Garrett D'Amore			*s++ = *p;
73784441f8Garrett D'Amore		}
73884441f8Garrett D'Amore		size += (uintptr_t)s - (uintptr_t)op;
73984441f8Garrett D'Amore		if (!esc_nl) {
74084441f8Garrett D'Amore			*s = '\0';
74184441f8Garrett D'Amore			break;
74284441f8Garrett D'Amore		}
74384441f8Garrett D'Amore		if (asize - size < _POSIX2_LINE_MAX + 1) {
74484441f8Garrett D'Amore			asize *= 2;
74584441f8Garrett D'Amore			if ((text = realloc(text, asize)) == NULL)
74684441f8Garrett D'Amore				err(1, "realloc");
74784441f8Garrett D'Amore		}
74884441f8Garrett D'Amore	}
74984441f8Garrett D'Amore	text[size] = '\0';
75084441f8Garrett D'Amore	if ((p = realloc(text, size + 1)) == NULL)
75184441f8Garrett D'Amore		err(1, "realloc");
75284441f8Garrett D'Amore	return (p);
75384441f8Garrett D'Amore}
75484441f8Garrett D'Amore
75584441f8Garrett D'Amore/*
75684441f8Garrett D'Amore * Get an address and return a pointer to the first character after
75784441f8Garrett D'Amore * it.  Fill the structure pointed to according to the address.
75884441f8Garrett D'Amore */
75984441f8Garrett D'Amorestatic char *
76084441f8Garrett D'Amorecompile_addr(char *p, struct s_addr *a)
76184441f8Garrett D'Amore{
76284441f8Garrett D'Amore	char *end, re[_POSIX2_LINE_MAX + 1];
76384441f8Garrett D'Amore	int icase;
76484441f8Garrett D'Amore
76584441f8Garrett D'Amore	icase = 0;
76684441f8Garrett D'Amore
76784441f8Garrett D'Amore	a->type = 0;
76884441f8Garrett D'Amore	switch (*p) {
76984441f8Garrett D'Amore	case '\\':				/* Context address */
77084441f8Garrett D'Amore		++p;
77184441f8Garrett D'Amore		/* FALLTHROUGH */
77284441f8Garrett D'Amore	case '/':				/* Context address */
77384441f8Garrett D'Amore		p = compile_delimited(p, re, 0);
77484441f8Garrett D'Amore		if (p == NULL)
77584441f8Garrett D'Amore			fatal(_("unterminated regular expression"));
77684441f8Garrett D'Amore
77784441f8Garrett D'Amore		/* Check for case insensitive regexp flag */
77884441f8Garrett D'Amore		if (*p == 'I') {
77984441f8Garrett D'Amore			icase = 1;
78084441f8Garrett D'Amore			p++;
78184441f8Garrett D'Amore		}
78284441f8Garrett D'Amore		if (*re == '\0')
78384441f8Garrett D'Amore			a->u.r = NULL;
78484441f8Garrett D'Amore		else
78584441f8Garrett D'Amore			a->u.r = compile_re(re, icase);
78684441f8Garrett D'Amore		a->type = AT_RE;
78784441f8Garrett D'Amore		return (p);
78884441f8Garrett D'Amore
78984441f8Garrett D'Amore	case '$':				/* Last line */
79084441f8Garrett D'Amore		a->type = AT_LAST;
79184441f8Garrett D'Amore		return (p + 1);
79284441f8Garrett D'Amore
79384441f8Garrett D'Amore	case '+':				/* Relative line number */
79484441f8Garrett D'Amore		a->type = AT_RELLINE;
79584441f8Garrett D'Amore		p++;
79684441f8Garrett D'Amore		/* FALLTHROUGH */
79784441f8Garrett D'Amore						/* Line number */
79884441f8Garrett D'Amore	case '0': case '1': case '2': case '3': case '4':
79984441f8Garrett D'Amore	case '5': case '6': case '7': case '8': case '9':
80084441f8Garrett D'Amore		if (a->type == 0)
80184441f8Garrett D'Amore			a->type = AT_LINE;
80284441f8Garrett D'Amore		a->u.l = strtol(p, &end, 10);
80384441f8Garrett D'Amore		return (end);
80484441f8Garrett D'Amore	default:
80584441f8Garrett D'Amore		fatal(_("expected context address"));
80684441f8Garrett D'Amore		return (NULL);
80784441f8Garrett D'Amore	}
80884441f8Garrett D'Amore}
80984441f8Garrett D'Amore
81084441f8Garrett D'Amore/*
81184441f8Garrett D'Amore * duptoeol --
81284441f8Garrett D'Amore *	Return a copy of all the characters up to \n or \0.
81384441f8Garrett D'Amore */
81484441f8Garrett D'Amorestatic char *
81584441f8Garrett D'Amoreduptoeol(char *s, const char *ctype)
81684441f8Garrett D'Amore{
81784441f8Garrett D'Amore	size_t len;
81884441f8Garrett D'Amore	int ws;
81984441f8Garrett D'Amore	char *p, *start;
82084441f8Garrett D'Amore
82184441f8Garrett D'Amore	ws = 0;
82284441f8Garrett D'Amore	for (start = s; *s != '\0' && *s != '\n'; ++s)
82384441f8Garrett D'Amore		ws = isspace((unsigned char)*s);
82484441f8Garrett D'Amore	*s = '\0';
82584441f8Garrett D'Amore	if (ws)
82684441f8Garrett D'Amore		warnx(_("%lu: %s: whitespace after %s"), linenum, fname, ctype);
82784441f8Garrett D'Amore	len = (uintptr_t)s - (uintptr_t)start + 1;
82884441f8Garrett D'Amore	if ((p = malloc(len)) == NULL)
82984441f8Garrett D'Amore		err(1, "malloc");
83084441f8Garrett D'Amore	return (memmove(p, start, len));
83184441f8Garrett D'Amore}
83284441f8Garrett D'Amore
83384441f8Garrett D'Amore/*
83484441f8Garrett D'Amore * Convert goto label names to addresses, and count a and r commands, in
83584441f8Garrett D'Amore * the given subset of the script.  Free the memory used by labels in b
83684441f8Garrett D'Amore * and t commands (but not by :).
83784441f8Garrett D'Amore *
83884441f8Garrett D'Amore * TODO: Remove } nodes
83984441f8Garrett D'Amore */
84084441f8Garrett D'Amorestatic void
84184441f8Garrett D'Amorefixuplabel(struct s_command *cp, struct s_command *end)
84284441f8Garrett D'Amore{
84384441f8Garrett D'Amore
84484441f8Garrett D'Amore	for (; cp != end; cp = cp->next)
84584441f8Garrett D'Amore		switch (cp->code) {
84684441f8Garrett D'Amore		case 'a':
84784441f8Garrett D'Amore		case 'r':
84884441f8Garrett D'Amore			appendnum++;
84984441f8Garrett D'Amore			break;
85084441f8Garrett D'Amore		case 'b':
85184441f8Garrett D'Amore		case 't':
85284441f8Garrett D'Amore			/* Resolve branch target. */
85384441f8Garrett D'Amore			if (cp->t == NULL) {
85484441f8Garrett D'Amore				cp->u.c = NULL;
85584441f8Garrett D'Amore				break;
85684441f8Garrett D'Amore			}
85784441f8Garrett D'Amore			if ((cp->u.c = findlabel(cp->t)) == NULL)
85884441f8Garrett D'Amore				fatal(_("undefined label '%s'"), cp->t);
85984441f8Garrett D'Amore			free(cp->t);
86084441f8Garrett D'Amore			break;
86184441f8Garrett D'Amore		case '{':
86284441f8Garrett D'Amore			/* Do interior commands. */
86384441f8Garrett D'Amore			fixuplabel(cp->u.c, cp->next);
86484441f8Garrett D'Amore			break;
86584441f8Garrett D'Amore		}
86684441f8Garrett D'Amore}
86784441f8Garrett D'Amore
86884441f8Garrett D'Amore/*
86984441f8Garrett D'Amore * Associate the given command label for later lookup.
87084441f8Garrett D'Amore */
87184441f8Garrett D'Amorestatic void
87284441f8Garrett D'Amoreenterlabel(struct s_command *cp)
87384441f8Garrett D'Amore{
87484441f8Garrett D'Amore	struct labhash **lhp, *lh;
87584441f8Garrett D'Amore	uchar_t *p;
87684441f8Garrett D'Amore	uint_t h, c;
87784441f8Garrett D'Amore
87884441f8Garrett D'Amore	for (h = 0, p = (uchar_t *)cp->t; (c = *p) != 0; p++)
87984441f8Garrett D'Amore		h = (h << 5) + h + c;
88084441f8Garrett D'Amore	lhp = &labels[h & LHMASK];
88184441f8Garrett D'Amore	for (lh = *lhp; lh != NULL; lh = lh->lh_next)
88284441f8Garrett D'Amore		if (lh->lh_hash == h && strcmp(cp->t, lh->lh_cmd->t) == 0)
88384441f8Garrett D'Amore			fatal(_("duplicate label '%s'"), cp->t);
88484441f8Garrett D'Amore	if ((lh = malloc(sizeof (*lh))) == NULL)
88584441f8Garrett D'Amore		err(1, "malloc");
88684441f8Garrett D'Amore	lh->lh_next = *lhp;
88784441f8Garrett D'Amore	lh->lh_hash = h;
88884441f8Garrett D'Amore	lh->lh_cmd = cp;
88984441f8Garrett D'Amore	lh->lh_ref = 0;
89084441f8Garrett D'Amore	*lhp = lh;
89184441f8Garrett D'Amore}
89284441f8Garrett D'Amore
89384441f8Garrett D'Amore/*
89484441f8Garrett D'Amore * Find the label contained in the command l in the command linked
89584441f8Garrett D'Amore * list cp.  L is excluded from the search.  Return NULL if not found.
89684441f8Garrett D'Amore */
89784441f8Garrett D'Amorestatic struct s_command *
89884441f8Garrett D'Amorefindlabel(char *name)
89984441f8Garrett D'Amore{
90084441f8Garrett D'Amore	struct labhash *lh;
90184441f8Garrett D'Amore	uchar_t *p;
90284441f8Garrett D'Amore	uint_t h, c;
90384441f8Garrett D'Amore
90484441f8Garrett D'Amore	for (h = 0, p = (uchar_t *)name; (c = *p) != 0; p++)
90584441f8Garrett D'Amore		h = (h << 5) + h + c;
90684441f8Garrett D'Amore	for (lh = labels[h & LHMASK]; lh != NULL; lh = lh->lh_next) {
90784441f8Garrett D'Amore		if (lh->lh_hash == h && strcmp(name, lh->lh_cmd->t) == 0) {
90884441f8Garrett D'Amore			lh->lh_ref = 1;
90984441f8Garrett D'Amore			return (lh->lh_cmd);
91084441f8Garrett D'Amore		}
91184441f8Garrett D'Amore	}
91284441f8Garrett D'Amore	return (NULL);
91384441f8Garrett D'Amore}
91484441f8Garrett D'Amore
91584441f8Garrett D'Amore/*
91684441f8Garrett D'Amore * Warn about any unused labels.  As a side effect, release the label hash
91784441f8Garrett D'Amore * table space.
91884441f8Garrett D'Amore */
91984441f8Garrett D'Amorestatic void
92084441f8Garrett D'Amoreuselabel(void)
92184441f8Garrett D'Amore{
92284441f8Garrett D'Amore	struct labhash *lh, *next;
92384441f8Garrett D'Amore	int i;
92484441f8Garrett D'Amore
92584441f8Garrett D'Amore	for (i = 0; i < LHSZ; i++) {
92684441f8Garrett D'Amore		for (lh = labels[i]; lh != NULL; lh = next) {
92784441f8Garrett D'Amore			next = lh->lh_next;
92884441f8Garrett D'Amore			if (!lh->lh_ref)
929