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