Home | History | Annotate | Line # | Download | only in ex
ex_global.c revision 1.2
      1 /*	$NetBSD: ex_global.c,v 1.2 2013/11/22 15:52:05 christos Exp $ */
      2 /*-
      3  * Copyright (c) 1992, 1993, 1994
      4  *	The Regents of the University of California.  All rights reserved.
      5  * Copyright (c) 1992, 1993, 1994, 1995, 1996
      6  *	Keith Bostic.  All rights reserved.
      7  *
      8  * See the LICENSE file for redistribution information.
      9  */
     10 
     11 #include "config.h"
     12 
     13 #ifndef lint
     14 static const char sccsid[] = "Id: ex_global.c,v 10.30 2001/06/25 15:19:16 skimo Exp  (Berkeley) Date: 2001/06/25 15:19:16 ";
     15 #endif /* not lint */
     16 
     17 #include <sys/types.h>
     18 #include <sys/queue.h>
     19 
     20 #include <bitstring.h>
     21 #include <ctype.h>
     22 #include <errno.h>
     23 #include <limits.h>
     24 #include <stdio.h>
     25 #include <stdlib.h>
     26 #include <string.h>
     27 #include <unistd.h>
     28 
     29 #include "../common/common.h"
     30 
     31 enum which {GLOBAL, V};
     32 
     33 static int ex_g_setup __P((SCR *, EXCMD *, enum which));
     34 
     35 /*
     36  * ex_global -- [line [,line]] g[lobal][!] /pattern/ [commands]
     37  *	Exec on lines matching a pattern.
     38  *
     39  * PUBLIC: int ex_global __P((SCR *, EXCMD *));
     40  */
     41 int
     42 ex_global(SCR *sp, EXCMD *cmdp)
     43 {
     44 	return (ex_g_setup(sp,
     45 	    cmdp, FL_ISSET(cmdp->iflags, E_C_FORCE) ? V : GLOBAL));
     46 }
     47 
     48 /*
     49  * ex_v -- [line [,line]] v /pattern/ [commands]
     50  *	Exec on lines not matching a pattern.
     51  *
     52  * PUBLIC: int ex_v __P((SCR *, EXCMD *));
     53  */
     54 int
     55 ex_v(SCR *sp, EXCMD *cmdp)
     56 {
     57 	return (ex_g_setup(sp, cmdp, V));
     58 }
     59 
     60 /*
     61  * ex_g_setup --
     62  *	Ex global and v commands.
     63  */
     64 static int
     65 ex_g_setup(SCR *sp, EXCMD *cmdp, enum which cmd)
     66 {
     67 	CHAR_T *ptrn, *p, *t;
     68 	EXCMD *ecp;
     69 	MARK myabs;
     70 	RANGE *rp;
     71 	busy_t btype;
     72 	db_recno_t start, end;
     73 	regmatch_t match[1];
     74 	size_t len;
     75 	int cnt, delim, eval;
     76 	CHAR_T *dbp;
     77 
     78 	NEEDFILE(sp, cmdp);
     79 
     80 	if (F_ISSET(sp, SC_EX_GLOBAL)) {
     81 		msgq_wstr(sp, M_ERR, cmdp->cmd->name,
     82 	"124|The %s command can't be used as part of a global or v command");
     83 		return (1);
     84 	}
     85 
     86 	/*
     87 	 * Skip leading white space.  Historic vi allowed any non-alphanumeric
     88 	 * to serve as the global command delimiter.
     89 	 */
     90 	if (cmdp->argc == 0)
     91 		goto usage;
     92 	for (p = cmdp->argv[0]->bp; ISBLANK(*p); ++p);
     93 	if (*p == '\0' || ISALNUM((UCHAR_T)*p) ||
     94 	    *p == '\\' || *p == '|' || *p == '\n') {
     95 usage:		ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE);
     96 		return (1);
     97 	}
     98 	delim = *p++;
     99 
    100 	/*
    101 	 * Get the pattern string, toss escaped characters.
    102 	 *
    103 	 * QUOTING NOTE:
    104 	 * Only toss an escaped character if it escapes a delimiter.
    105 	 */
    106 	for (ptrn = t = p;;) {
    107 		if (p[0] == '\0' || p[0] == delim) {
    108 			if (p[0] == delim)
    109 				++p;
    110 			/*
    111 			 * !!!
    112 			 * Nul terminate the pattern string -- it's passed
    113 			 * to regcomp which doesn't understand anything else.
    114 			 */
    115 			*t = L('\0');
    116 			break;
    117 		}
    118 		if (p[0] == L('\\')) {
    119 			if (p[1] == delim)
    120 				++p;
    121 			else if (p[1] == L('\\'))
    122 				*t++ = *p++;
    123 		}
    124 		*t++ = *p++;
    125 	}
    126 
    127 	/* If the pattern string is empty, use the last one. */
    128 	if (*ptrn == L('\0')) {
    129 		if (sp->re == NULL) {
    130 			ex_emsg(sp, NULL, EXM_NOPREVRE);
    131 			return (1);
    132 		}
    133 
    134 		/* Re-compile the RE if necessary. */
    135 		if (!F_ISSET(sp, SC_RE_SEARCH) &&
    136 		    re_compile(sp, sp->re, sp->re_len,
    137 		    NULL, NULL, &sp->re_c, SEARCH_CSEARCH | SEARCH_MSG))
    138 			return (1);
    139 	} else {
    140 		/* Compile the RE. */
    141 		if (re_compile(sp, ptrn, t - ptrn, &sp->re,
    142 		    &sp->re_len, &sp->re_c, SEARCH_CSEARCH | SEARCH_MSG))
    143 			return (1);
    144 
    145 		/*
    146 		 * Set saved RE.  Historic practice is that globals set
    147 		 * direction as well as the RE.
    148 		 */
    149 		sp->searchdir = FORWARD;
    150 	}
    151 
    152 	/* The global commands always set the previous context mark. */
    153 	myabs.lno = sp->lno;
    154 	myabs.cno = sp->cno;
    155 	if (mark_set(sp, ABSMARK1, &myabs, 1))
    156 		return (1);
    157 
    158 	/* Get an EXCMD structure. */
    159 	CALLOC_RET(sp, ecp, EXCMD *, 1, sizeof(EXCMD));
    160 	CIRCLEQ_INIT(&ecp->rq);
    161 
    162 	/*
    163 	 * Get a copy of the command string; the default command is print.
    164 	 * Don't worry about a set of <blank>s with no command, that will
    165 	 * default to print in the ex parser.  We need to have two copies
    166 	 * because the ex parser may step on the command string when it's
    167 	 * parsing it.
    168 	 */
    169 	if ((len = cmdp->argv[0]->len - (p - cmdp->argv[0]->bp)) == 0) {
    170 		p = __UNCONST(L("p"));
    171 		len = 1;
    172 	}
    173 
    174 	MALLOC_RET(sp, ecp->cp, CHAR_T *, (len * 2) * sizeof(CHAR_T));
    175 	ecp->o_cp = ecp->cp;
    176 	ecp->o_clen = len;
    177 	MEMCPYW(ecp->cp + len, p, len);
    178 	ecp->range_lno = OOBLNO;
    179 	FL_SET(ecp->agv_flags, cmd == GLOBAL ? AGV_GLOBAL : AGV_V);
    180 	LIST_INSERT_HEAD(&sp->wp->ecq, ecp, q);
    181 
    182 	/*
    183 	 * For each line...  The semantics of global matching are that we first
    184 	 * have to decide which lines are going to get passed to the command,
    185 	 * and then pass them to the command, ignoring other changes.  There's
    186 	 * really no way to do this in a single pass, since arbitrary line
    187 	 * creation, deletion and movement can be done in the ex command.  For
    188 	 * example, a good vi clone test is ":g/X/mo.-3", or "g/X/.,.+1d".
    189 	 * What we do is create linked list of lines that are tracked through
    190 	 * each ex command.  There's a callback routine which the DB interface
    191 	 * routines call when a line is created or deleted.  This doesn't help
    192 	 * the layering much.
    193 	 */
    194 	btype = BUSY_ON;
    195 	cnt = INTERRUPT_CHECK;
    196 	for (start = cmdp->addr1.lno,
    197 	    end = cmdp->addr2.lno; start <= end; ++start) {
    198 		if (cnt-- == 0) {
    199 			if (INTERRUPTED(sp)) {
    200 				LIST_REMOVE(ecp, q);
    201 				free(ecp->cp);
    202 				free(ecp);
    203 				break;
    204 			}
    205 			search_busy(sp, btype);
    206 			btype = BUSY_UPDATE;
    207 			cnt = INTERRUPT_CHECK;
    208 		}
    209 		if (db_get(sp, start, DBG_FATAL, &dbp, &len))
    210 			return (1);
    211 		match[0].rm_so = 0;
    212 		match[0].rm_eo = len;
    213 		switch (eval =
    214 		    regexec(&sp->re_c, dbp, 0, match, REG_STARTEND)) {
    215 		case 0:
    216 			if (cmd == V)
    217 				continue;
    218 			break;
    219 		case REG_NOMATCH:
    220 			if (cmd == GLOBAL)
    221 				continue;
    222 			break;
    223 		default:
    224 			re_error(sp, eval, &sp->re_c);
    225 			break;
    226 		}
    227 
    228 		/* If follows the last entry, extend the last entry's range. */
    229 		if ((rp = ecp->rq.cqh_last) != (void *)&ecp->rq &&
    230 		    rp->stop == start - 1) {
    231 			++rp->stop;
    232 			continue;
    233 		}
    234 
    235 		/* Allocate a new range, and append it to the list. */
    236 		CALLOC(sp, rp, RANGE *, 1, sizeof(RANGE));
    237 		if (rp == NULL)
    238 			return (1);
    239 		rp->start = rp->stop = start;
    240 		CIRCLEQ_INSERT_TAIL(&ecp->rq, rp, q);
    241 	}
    242 	search_busy(sp, BUSY_OFF);
    243 	return (0);
    244 }
    245 
    246 /*
    247  * ex_g_insdel --
    248  *	Update the ranges based on an insertion or deletion.
    249  *
    250  * PUBLIC: int ex_g_insdel __P((SCR *, lnop_t, db_recno_t));
    251  */
    252 int
    253 ex_g_insdel(SCR *sp, lnop_t op, db_recno_t lno)
    254 {
    255 	EXCMD *ecp;
    256 	RANGE *nrp, *rp;
    257 
    258 	/* All insert/append operations are done as inserts. */
    259 	if (op == LINE_APPEND)
    260 		abort();
    261 
    262 	if (op == LINE_RESET)
    263 		return (0);
    264 
    265 	for (ecp = sp->wp->ecq.lh_first; ecp != NULL; ecp = ecp->q.le_next) {
    266 		if (!FL_ISSET(ecp->agv_flags, AGV_AT | AGV_GLOBAL | AGV_V))
    267 			continue;
    268 		for (rp = ecp->rq.cqh_first; rp != (void *)&ecp->rq; rp = nrp) {
    269 			nrp = rp->q.cqe_next;
    270 
    271 			/* If range less than the line, ignore it. */
    272 			if (rp->stop < lno)
    273 				continue;
    274 
    275 			/*
    276 			 * If range greater than the line, decrement or
    277 			 * increment the range.
    278 			 */
    279 			if (rp->start > lno) {
    280 				if (op == LINE_DELETE) {
    281 					--rp->start;
    282 					--rp->stop;
    283 				} else {
    284 					++rp->start;
    285 					++rp->stop;
    286 				}
    287 				continue;
    288 			}
    289 
    290 			/*
    291 			 * Lno is inside the range, decrement the end point
    292 			 * for deletion, and split the range for insertion.
    293 			 * In the latter case, since we're inserting a new
    294 			 * element, neither range can be exhausted.
    295 			 */
    296 			if (op == LINE_DELETE) {
    297 				if (rp->start > --rp->stop) {
    298 					CIRCLEQ_REMOVE(&ecp->rq, rp, q);
    299 					free(rp);
    300 				}
    301 			} else {
    302 				CALLOC_RET(sp, nrp, RANGE *, 1, sizeof(RANGE));
    303 				nrp->start = lno + 1;
    304 				nrp->stop = rp->stop + 1;
    305 				rp->stop = lno - 1;
    306 				CIRCLEQ_INSERT_AFTER(&ecp->rq, rp, nrp, q);
    307 				rp = nrp;
    308 			}
    309 		}
    310 
    311 		/*
    312 		 * If the command deleted/inserted lines, the cursor moves to
    313 		 * the line after the deleted/inserted line.
    314 		 */
    315 		ecp->range_lno = lno;
    316 	}
    317 	return (0);
    318 }
    319