1 /*
2 * This file is part of DGD, https://github.com/dworkin/dgd
3 * Copyright (C) 1993-2010 Dworkin B.V.
4 * Copyright (C) 2010,2012 DGD Authors (see the commit log for details)
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as
8 * published by the Free Software Foundation, either version 3 of the
9 * License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Affero General Public License for more details.
15 *
16 * You should have received a copy of the GNU Affero General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 # define INCLUDE_CTYPE
21 # include "ed.h"
22 # include "edcmd.h"
23
24 /*
25 * This file contains the command parsing functions.
26 */
27
28 /*
29 * These functions are in cmdsub.c
30 */
31 extern Int cb_search (cmdbuf*, Int, Int, int);
32 extern int cb_print (cmdbuf*);
33 extern int cb_list (cmdbuf*);
34 extern int cb_number (cmdbuf*);
35 extern int cb_page (cmdbuf*);
36 extern int cb_assign (cmdbuf*);
37 extern int cb_mark (cmdbuf*);
38 extern int cb_append (cmdbuf*);
39 extern int cb_insert (cmdbuf*);
40 extern int cb_change (cmdbuf*);
41 extern int cb_delete (cmdbuf*);
42 extern int cb_copy (cmdbuf*);
43 extern int cb_move (cmdbuf*);
44 extern int cb_put (cmdbuf*);
45 extern int cb_yank (cmdbuf*);
46 extern int cb_lshift (cmdbuf*);
47 extern int cb_rshift (cmdbuf*);
48 extern int cb_indent (cmdbuf*);
49 extern int cb_join (cmdbuf*);
50 extern int cb_subst (cmdbuf*);
51 extern int cb_file (cmdbuf*);
52 extern int cb_read (cmdbuf*);
53 extern int cb_edit (cmdbuf*);
54 extern int cb_quit (cmdbuf*);
55 extern int cb_write (cmdbuf*);
56 extern int cb_wq (cmdbuf*);
57 extern int cb_xit (cmdbuf*);
58 extern int cb_set (cmdbuf*);
59
60 cmdbuf *ccb; /* editor command buffer */
61
62 /*
63 * NAME: cmdbuf->new()
64 * DESCRIPTION: create and initialize a command edit buffer
65 */
cb_new(char * tmpfile)66 cmdbuf *cb_new(char *tmpfile)
67 {
68 cmdbuf *cb;
69
70 m_static();
71 cb = ALLOC(cmdbuf, 1);
72 memset(cb, '\0', sizeof(cmdbuf));
73 cb->edbuf = eb_new(tmpfile);
74 cb->regexp = rx_new();
75 cb->vars = va_new();
76 m_dynamic();
77
78 cb->this = 0;
79 cb->undo = (block) -1; /* not 0! */
80 return cb;
81 }
82
83 /*
84 * NAME: cmdbuf->del()
85 * DESCRIPTION: delete a command edit buffer
86 */
cb_del(cmdbuf * cb)87 void cb_del(cmdbuf *cb)
88 {
89 eb_del(cb->edbuf);
90 rx_del(cb->regexp);
91 va_del(cb->vars);
92 FREE(cb);
93 }
94
95 /*
96 * NAME: skipst()
97 * DESCRIPTION: skip white space in a string. return a pointer to the first
98 * character after the white space (could be '\0')
99 */
skipst(char * p)100 char *skipst(char *p)
101 {
102 while (*p == ' ' || *p == HT) {
103 p++;
104 }
105 return p;
106 }
107
108 /*
109 * NAME: pattern()
110 * DESCRIPTION: scan a pattern and copy it to a buffer.
111 */
pattern(char * pat,int delim,char * buffer)112 char *pattern(char *pat, int delim, char *buffer)
113 {
114 char *p;
115 unsigned int size;
116
117 p = pat;
118 while (*p != '\0') {
119 if (*p == delim) {
120 break;
121 }
122 switch (*p++) {
123 case '\\':
124 if (*p != '\0') {
125 p++;
126 }
127 break;
128
129 case '[':
130 while (*p != '\0' && *p != ']') {
131 if (*p++ == '\\') {
132 if (*p == '\0') {
133 break;
134 }
135 p++;
136 }
137 }
138 break;
139 }
140 }
141 size = p - pat;
142 if (size >= STRINGSZ) {
143 error("Regular expression too large");
144 }
145 if (size > 0) {
146 memcpy(buffer, pat, size);
147 }
148 buffer[size] = '\0';
149 if (*p != '\0') {
150 p++;
151 }
152 return p;
153 }
154
155 /*
156 * NAME: cmdbuf->pattern()
157 * DESCRIPTION: compile a regular expression, up to a delimeter
158 */
cb_pattern(cmdbuf * cb,char delim)159 static void cb_pattern(cmdbuf *cb, char delim)
160 {
161 char buffer[STRINGSZ];
162
163 cb->cmd = pattern(cb->cmd, delim, buffer);
164 if (buffer[0] == '\0') {
165 if (!cb->regexp->valid) {
166 error("No previous regular expression");
167 }
168 } else {
169 char *err;
170
171 err = rx_comp(cb->regexp, buffer);
172 if (err != (char *) NULL) {
173 error(err);
174 }
175 }
176 }
177
178 /*
179 * NAME: cmdbuf->address()
180 * DESCRIPTION: parse an address. First is the first line to search from if the
181 * address is a search pattern.
182 */
cb_address(cmdbuf * cb,Int first)183 static Int cb_address(cmdbuf *cb, Int first)
184 {
185 Int l;
186 char *p;
187
188 l = 0;
189
190 switch (*(p = cb->cmd)) {
191 case '0':
192 case '1':
193 case '2':
194 case '3':
195 case '4':
196 case '5':
197 case '6':
198 case '7':
199 case '8':
200 case '9':
201 do {
202 l *= 10;
203 l += *p++ - '0';
204 } while (isdigit(*p));
205 cb->cmd = p;
206 break;
207
208 case '.':
209 cb->cmd++;
210 case '+':
211 case '-':
212 l = cb->this;
213 break;
214
215 case '$':
216 l = cb->edbuf->lines;
217 cb->cmd++;
218 break;
219
220 case '\'':
221 if (islower(*++p)) {
222 l = cb->mark[*p - 'a'];
223 } else {
224 error("Marks are a-z");
225 }
226 if (l == 0) {
227 error("Undefined mark referenced");
228 }
229 cb->cmd += 2;
230 break;
231
232 case '/':
233 cb->cmd++;
234 cb_pattern(cb, *p);
235 l = cb->edbuf->lines;
236 if (l == 0) {
237 return 1; /* force out-of-range error */
238 }
239 if (first < l) {
240 l = cb_search(cb, first + 1, l, FALSE);
241 if (l != 0) {
242 break;
243 }
244 }
245 l = cb_search(cb, (Int) 1, first, FALSE);
246 if (l == 0) {
247 error("Pattern not found");
248 }
249 break;
250
251 case '?':
252 cb->cmd++;
253 cb_pattern(cb, *p);
254 if (first > 1) {
255 l = cb_search(cb, (Int) 1, first - 1, TRUE);
256 }
257 if (l == 0) {
258 l = cb->edbuf->lines;
259 if (l == 0) {
260 return 1; /* force out-of-range error */
261 }
262 l = cb_search(cb, first, l, TRUE);
263 if (l == 0) {
264 error("Pattern not found");
265 }
266 }
267 break;
268
269 default:
270 return -1;
271 }
272
273 cb->cmd = skipst(cb->cmd);
274 while (cb->cmd[0] == '+' || cb->cmd[0] == '-') {
275 Int r;
276
277 p = skipst(cb->cmd + 1);
278 if (!isdigit(*p)) {
279 r = 1;
280 } else {
281 r = 0;
282 do {
283 r *= 10;
284 r += *p++ - '0';
285 } while (isdigit(*p));
286 }
287 if (cb->cmd[0] == '+') {
288 l += r;
289 } else {
290 l -= r;
291 }
292 cb->cmd = skipst(p);
293 }
294 if (l < 0) {
295 error("Negative address");
296 }
297 return l;
298 }
299
300 /*
301 * NAME: cmdbuf->range()
302 * DESCRIPTION: parse line range from the command buffer. Valid range for an
303 * address is 0-$
304 */
cb_range(cmdbuf * cb)305 static void cb_range(cmdbuf *cb)
306 {
307 cb->first = -1;
308 cb->last = -1;
309 cb->cmd = skipst(cb->cmd);
310 if (cb->cmd[0] == '\0') {
311 return;
312 }
313 if (cb->cmd[0] == '%') {
314 cb->first = 1;
315 cb->last = cb->edbuf->lines;
316 cb->cmd = skipst(cb->cmd + 1);
317 } else {
318 cb->first = cb_address(cb, cb->this);
319 if (cb->cmd[0] == ',' || cb->cmd[0] == ';') {
320 if (cb->first < 0) {
321 cb->first = cb->this;
322 } else if (cb->cmd[0] == ';') {
323 cb->this = cb->first;
324 }
325 cb->cmd = skipst(cb->cmd + 1);
326 cb->last = cb_address(cb, cb->this);
327 if (cb->last < 0) {
328 cb->last = cb->this;
329 }
330 }
331 }
332 }
333
334 /*
335 * NAME: cmdbuf->count()
336 * DESCRIPTION: set the line range according to the (optional) count in the
337 * command buffer
338 */
cb_count(cmdbuf * cb)339 void cb_count(cmdbuf *cb)
340 {
341 char *p;
342
343 p = cb->cmd;
344 if (isdigit(*p)) {
345 Int count;
346
347 count = 0;
348 do {
349 count *= 10;
350 count += *p++ - '0';
351 } while (isdigit(*p));
352 cb->cmd = skipst(p);
353
354 if (cb->first < 0) {
355 cb->first = cb->this;
356 }
357 cb->last = cb->first + count - 1;
358 if (cb->last < cb->first || cb->last > cb->edbuf->lines) {
359 error("Not that many lines in buffer");
360 }
361 }
362 }
363
364 /*
365 * NAME: not_in_global()
366 * DESCRIPTION: error if currently executing a global command
367 */
not_in_global(cmdbuf * cb)368 void not_in_global(cmdbuf *cb)
369 {
370 if (cb->flags & CB_GLOBAL) {
371 error("Command not allowed in global");
372 }
373 }
374
375 /*
376 * NAME: cmdbuf->do()
377 * DESCRIPTION: copy the present command buffer status in the undo buffer
378 */
cb_do(cmdbuf * cb,Int this)379 void cb_do(cmdbuf *cb, Int this)
380 {
381 cb->undo = cb->edbuf->buffer;
382 cb->uthis = this;
383 memcpy(cb->umark, cb->mark, sizeof(cb->mark));
384 }
385
386 /*
387 * NAME: cmdbuf->undo()
388 * DESCRIPTION: undo the effects of a previous command by exchanging the
389 * command buffer status with the undo buffer
390 */
cb_undo(cmdbuf * cb)391 int cb_undo(cmdbuf *cb)
392 {
393 block b;
394 Int this, mark[26];
395
396 not_in_global(cb);
397 if (cb->undo == (block) -1) {
398 error("Nothing to undo");
399 }
400
401 b = cb->undo;
402 cb->undo = cb->edbuf->buffer;
403 cb->edbuf->lines = (b == (block) 0) ? 0 : bk_size(cb->edbuf->lb, b);
404 cb->edbuf->buffer = b;
405
406 this = cb->uthis;
407 if (this == 0 && b != (block) 0) {
408 this = 1;
409 }
410 cb->uthis = cb->othis;
411 cb->this = cb->othis = this;
412
413 memcpy(mark, cb->umark, sizeof(mark));
414 memcpy(cb->umark, cb->mark, sizeof(mark));
415 memcpy(cb->mark, mark, sizeof(mark));
416
417 cb->edit++;
418 return RET_FLAGS;
419 }
420
421 /*
422 * NAME: cmdbuf->buf()
423 * DESCRIPTION: put a block in the appropriate buffers
424 */
cb_buf(cmdbuf * cb,block b)425 void cb_buf(cmdbuf *cb, block b)
426 {
427 if (isupper(cb->a_buffer)) {
428 block *zbuf;
429
430 /*
431 * copy or append to named buffer
432 */
433 zbuf = &cb->zbuf[cb->a_buffer - 'A'];
434 if (*zbuf != (block) 0) {
435 *zbuf = bk_cat(cb->edbuf->lb, *zbuf, b);
436 } else {
437 *zbuf = b;
438 }
439 } else if (islower(cb->a_buffer)) {
440 cb->zbuf[cb->a_buffer - 'a'] = b;
441 }
442 /*
443 * always put it in the default yank buffer too
444 */
445 cb->buf = b;
446 }
447
448
449 /*
450 * Commands are allowed to affect the first or the last or all of the next lines
451 * to be examined by a global command. The lines must stay in a contiguous
452 * block.
453 */
454
455 /*
456 * NAME: add()
457 * DESCRIPTION: add a block of lines to the edit buffer
458 */
add(cmdbuf * cb,Int ln,block b,Int size)459 void add(cmdbuf *cb, Int ln, block b, Int size)
460 {
461 Int *m;
462
463 /* global checks */
464 if (cb->flags & CB_GLOBAL) {
465 if (ln < cb->glob_next) {
466 cb->glob_next += size;
467 } else if (ln < cb->glob_next + cb->glob_size - 1) {
468 error("Illegal add in global");
469 }
470 }
471
472 eb_put(cb->edbuf, ln, b);
473
474 /* adjust marks of lines after new block */
475 for (m = cb->mark; m < &cb->mark[26]; m++) {
476 if (*m > ln) {
477 *m += size;
478 }
479 }
480
481 cb->this = cb->othis = ln + size;
482 }
483
484 /*
485 * NAME: delete()
486 * DESCRIPTION: delete a block of lines from the edit buffer
487 */
delete(cmdbuf * cb,Int first,Int last)488 block delete(cmdbuf *cb, Int first, Int last)
489 {
490 Int size, *m;
491
492 size = last - first + 1;
493
494 /* global checks */
495 if (cb->flags & CB_GLOBAL) {
496 if (last < cb->glob_next) {
497 cb->glob_next -= size;
498 } else if (first <= cb->glob_next) {
499 cb->glob_size -= last - cb->glob_next + 1;
500 cb->glob_next = first;
501 } else if (last >= cb->glob_next + cb->glob_size) {
502 cb->glob_size = first - cb->glob_next;
503 } else {
504 error("Illegal delete in global");
505 }
506 }
507
508 /* adjust & erase marks */
509 for (m = cb->mark; m < &cb->mark[26]; m++) {
510 if (*m >= first) {
511 if (*m > last) {
512 *m -= size;
513 } else {
514 *m = 0;
515 }
516 }
517 }
518
519 cb->othis = first;
520 if (last == cb->edbuf->lines) {
521 cb->othis--;
522 }
523 cb->this = cb->othis;
524
525 return eb_delete(cb->edbuf, first, last);
526 }
527
528 /*
529 * NAME: change()
530 * DESCRIPTION: replace a subrange of lines by a block
531 */
change(cmdbuf * cb,Int first,Int last,block b)532 void change(cmdbuf *cb, Int first, Int last, block b)
533 {
534 Int offset, *m;
535
536 offset = last - first + 1;
537 if (b != (block) 0) {
538 offset -= bk_size(cb->edbuf->lb, b);
539 }
540
541 /* global checks */
542 if (cb->flags & CB_GLOBAL) {
543 if (last < cb->glob_next) {
544 cb->glob_next -= offset;
545 } else if (first <= cb->glob_next) {
546 cb->glob_size -= last - cb->glob_next + 1;
547 cb->glob_next = last - offset + 1;
548 } else if (last >= cb->glob_next + cb->glob_size) {
549 cb->glob_size = first - cb->glob_next;
550 } else {
551 error("Illegal change in global");
552 }
553 }
554
555 /* adjust marks. If the marks of the changed lines have to be erased,
556 the calling routine must handle it. */
557 for (m = cb->mark; m < &cb->mark[26]; m++) {
558 if (*m > last) {
559 *m -= offset;
560 }
561 }
562
563 cb->othis = first;
564 cb->this = last - offset;
565 if (cb->this == 0 && last != cb->edbuf->lines) {
566 cb->this = 1;
567 }
568
569 eb_change(cb->edbuf, first, last, b);
570 }
571
572
573 /*
574 * NAME: startblock()
575 * DESCRIPTION: start a block of lines
576 */
startblock(cmdbuf * cb)577 void startblock(cmdbuf *cb)
578 {
579 eb_startblock(cb->edbuf);
580 }
581
582 /*
583 * NAME: addblock()
584 * DESCRIPTION: add a line to the current block of lines
585 */
addblock(cmdbuf * cb,char * text)586 void addblock(cmdbuf *cb, char *text)
587 {
588 eb_addblock(cb->edbuf, text);
589 }
590
591 /*
592 * NAME: endblock()
593 * DESCRIPTION: finish the current block
594 */
endblock(cmdbuf * cb)595 void endblock(cmdbuf *cb)
596 {
597 eb_endblock(cb->edbuf);
598
599 if (cb->flags & CB_CHANGE) {
600 if (cb->first <= cb->last) {
601 change(cb, cb->first, cb->last, cb->edbuf->flines);
602 } else if (cb->first == 0 && cb->edbuf->lines != 0) {
603 cb->this = cb->othis = 1;
604 } else {
605 cb->this = cb->othis = cb->first;
606 }
607 } else {
608 if (cb->edbuf->flines != (block) 0) {
609 add(cb, cb->first, cb->edbuf->flines,
610 bk_size(cb->edbuf->lb, cb->edbuf->flines));
611 } else if (cb->first == 0 && cb->edbuf->lines != 0) {
612 cb->this = cb->othis = 1;
613 } else {
614 cb->this = cb->othis = cb->first;
615 }
616 }
617
618 cb->flags &= ~(CB_INSERT | CB_CHANGE);
619 cb->edit++;
620 }
621
622
623 /*
624 * NAME: find()
625 * DESCRIPTION: match a pattern in a global command
626 */
find(char * text)627 static void find(char *text)
628 {
629 cmdbuf *cb;
630
631 cb = ccb;
632 cb->glob_next++;
633 cb->glob_size--;
634 if (rx_exec(cb->glob_rx, text, 0, cb->ignorecase) != cb->reverse) {
635 longjmp(cb->env, TRUE);
636 }
637 }
638
639 /*
640 * NAME: cmdbuf->global()
641 * DESCRIPTION: do a global command
642 */
cb_global(cmdbuf * cb)643 int cb_global(cmdbuf *cb)
644 {
645 char *p;
646 char buffer[STRINGSZ], delim;
647 block undo;
648 Int uthis, umark[26];
649 bool aborted;
650
651 not_in_global(cb); /* no recursion please */
652
653 /* get the regular expression */
654 delim = cb->cmd[0];
655 if (delim != '\0' && !isalnum(delim)) {
656 cb->cmd = pattern(cb->cmd + 1, delim, buffer);
657 } else {
658 buffer[0] = '\0';
659 }
660 if (buffer[0] == '\0') {
661 error("Missing regular expression for global");
662 }
663
664 /* keep global undo status */
665 undo = cb->edbuf->buffer;
666 uthis = cb->first;
667 memcpy(umark, cb->mark, sizeof(cb->mark));
668
669 /*
670 * A local error context is created, so the regular expression buffer
671 * can be deallocated in case of an error.
672 */
673 cb->glob_rx = rx_new();
674 if (!ec_push((ec_ftn) NULL)) {
675 /* compile regexp */
676 p = rx_comp(cb->glob_rx, buffer);
677 if (p != (char *) NULL) {
678 error(p);
679 }
680
681 /* get the command to be done in global */
682 p = skipst(cb->cmd);
683 cb->cmd = p + strlen(p);
684 if (*p == '\0') {
685 p = "p"; /* default: print lines */
686 }
687 cb->flags |= CB_GLOBAL;
688 cb->reverse = (cb->flags & CB_EXCL) != 0;
689 cb->ignorecase = IGNORECASE(cb->vars);
690 cb->glob_next = cb->first;
691 cb->glob_size = cb->last - cb->first + 1;
692
693 do {
694 if (setjmp(cb->env)) {
695 /* found: do the commands */
696 cb->this = cb->glob_next - 1;
697 cb_command(cb, p);
698 } else {
699 /* search */
700 eb_range(cb->edbuf, cb->glob_next,
701 cb->glob_next + cb->glob_size - 1, find, FALSE);
702 }
703 } while (cb->glob_size > 0);
704
705 /* pop error context */
706 ec_pop();
707 aborted = FALSE;
708 } else {
709 aborted = TRUE;
710 }
711 /* come here if global is finished or in case of an error */
712
713 /* clean up regular expression */
714 rx_del(cb->glob_rx);
715
716 /* set undo status */
717 cb->undo = undo;
718 cb->uthis = uthis;
719 memcpy(cb->umark, umark, sizeof(umark));
720
721 /* no longer in global */
722 cb->flags &= ~CB_GLOBAL;
723
724 if (aborted) {
725 error((char *) NULL);
726 }
727 return 0;
728 }
729
730 /*
731 * NAME: cmdbuf->vglobal()
732 * DESCRIPTION: v == g!
733 */
cb_vglobal(cmdbuf * cb)734 int cb_vglobal(cmdbuf *cb)
735 {
736 cb->flags |= CB_EXCL;
737 return cb_global(cb);
738 }
739
740
741 typedef struct {
742 char flags; /* type of command */
743 char chr; /* first char of command */
744 char *cmd; /* full command string */
745 int (*ftn)(); /* command function */
746 } cmd;
747
748 # define CM_LNMASK 0x03
749 # define CM_LNNONE 0x00 /* range checking in function */
750 # define CM_LN0 0x01 /* (.)0 */
751 # define CM_LNDOT 0x02 /* (.,.) */
752 # define CM_LNRNG 0x03 /* (1,$) */
753
754 # define CM_EXCL 0x04
755
756 # define CM_BUFFER 0x10 /* buffer argument */
757 # define CM_ADDR 0x20 /* address argument */
758 # define CM_COUNT 0x40 /* count argument */
759
760 static cmd ed_commands[] = {
761 { CM_LN0, 'a', "append", cb_append },
762 # define CM_ASSIGN 1
763 { CM_LNNONE, '=', (char *) NULL,
764 cb_assign },
765 { CM_LNDOT | CM_COUNT, 'c', "change", cb_change },
766 { CM_LNDOT | CM_BUFFER | CM_COUNT, 'd', "delete", cb_delete },
767 { CM_LNNONE | CM_EXCL, 'e', "edit", cb_edit },
768 { CM_LNNONE, 'f', "file", cb_file },
769 { CM_LNRNG | CM_EXCL, 'g', "global", cb_global },
770 { CM_LN0 | CM_BUFFER, 0, "put", cb_put },
771 { CM_LN0, 'i', "insert", cb_insert },
772 { CM_LNNONE | CM_EXCL | CM_COUNT, 'j', "join", cb_join },
773 { CM_LNDOT, 'k', "mark", cb_mark },
774 { CM_LNDOT | CM_COUNT, 'l', "list", cb_list },
775 { CM_LNDOT | CM_ADDR, 'm', "move", cb_move },
776 # define CM_NUMBER 13
777 { CM_LNDOT | CM_COUNT, '#', "number", cb_number },
778 { CM_LNRNG | CM_EXCL, 0, "wq", cb_wq },
779 { CM_LNDOT | CM_COUNT, 'p', "print", cb_print },
780 { CM_LNNONE | CM_EXCL, 'q', "quit", cb_quit },
781 { CM_LN0, 'r', "read", cb_read },
782 { CM_LNDOT, 's', "substitute",
783 cb_subst },
784 { CM_LNDOT | CM_ADDR, 't', "copy", cb_copy },
785 { CM_LNNONE, 'u', "undo", cb_undo },
786 { CM_LNRNG, 'v', (char *) NULL,
787 cb_vglobal },
788 { CM_LNRNG | CM_EXCL, 'w', "write", cb_write },
789 { CM_LNRNG, 'x', "xit", cb_xit },
790 { CM_LNDOT | CM_BUFFER | CM_COUNT, 'y', "yank", cb_yank },
791 { CM_LNNONE, 'z', (char *) NULL,
792 cb_page },
793 { CM_LNNONE, 0, "set", cb_set },
794 # define CM_LSHIFT 27
795 { CM_LNDOT | CM_COUNT, '<', (char *) NULL,
796 cb_lshift },
797 # define CM_RSHIFT 28
798 { CM_LNDOT | CM_COUNT, '>', (char *) NULL,
799 cb_rshift },
800 # define CM_INDENT 29
801 { CM_LNRNG, 'I', (char *) NULL,
802 cb_indent },
803 };
804
805 # define NR_CMD 27 /* not including <, > and I */
806
807
808 /*
809 * NAME: cmdbuf->command()
810 * DESCRIPTION: Parse and execute an editor command. Return TRUE if this command
811 * did not terminate the editor. Multiple commands may be
812 * specified, separated by |
813 */
cb_command(cmdbuf * cb,char * command)814 bool cb_command(cmdbuf *cb, char *command)
815 {
816 cb->cmd = command;
817 ccb = cb;
818
819 for (;;) {
820 if (cb->flags & CB_INSERT) {
821 /* insert mode */
822 if (strlen(command) >= MAX_LINE_SIZE) {
823 endblock(cb);
824 error("Line too long");
825 }
826 if (strcmp(command, ".") == 0) {
827 /* finish block */
828 endblock(cb);
829 } else {
830 /* add the "command" to the current block */
831 addblock(cb, command);
832 }
833 } else {
834 cmd *cm;
835 char *p;
836 int ltype, ret;
837
838 cb->flags &= ~(CB_EXCL | CB_NUMBER | CB_LIST);
839
840 cb->a_addr = -1;
841 cb->a_buffer = 0;
842
843 /*
844 * parse the command line: [range] [command] [arguments]
845 */
846
847 cb->cmd = skipst(cb->cmd);
848 if (cb->cmd[0] == '\0') {
849 /* no command: print next line */
850 if (cb->this == cb->edbuf->lines) {
851 error("End-of-file");
852 }
853 cm = &ed_commands['p' - 'a'];
854 cb->first = cb->last = cb->this + 1;
855 } else {
856 /* parse [range] */
857 cb_range(cb);
858 p = cb->cmd = skipst(cb->cmd);
859 cm = (cmd *) NULL;
860
861 /* parse [command] */
862 if (*p == 'k') {
863 p++;
864 } else {
865 while (isalpha(*p)) {
866 p++;
867 }
868 }
869 if (p == cb->cmd) {
870 /* length == 0 */
871 switch (*p++) {
872 case '=':
873 cm = &ed_commands[CM_ASSIGN];
874 break;
875
876 case '#':
877 cm = &ed_commands[CM_NUMBER];
878 break;
879
880 case '<':
881 cm = &ed_commands[CM_LSHIFT];
882 break;
883
884 case '>':
885 cm = &ed_commands[CM_RSHIFT];
886 break;
887
888 case '\0':
889 case '|':
890 cm = &ed_commands['p' - 'a'];
891 default:
892 --p;
893 break;
894 }
895 } else if (p - cb->cmd == 1) {
896 /* length == 1 */
897 if (ed_commands[p[-1] - 'a'].chr == p[-1]) {
898 cm = &ed_commands[p[-1] - 'a'];
899 } else if (p[-1] == 'I') {
900 cm = &ed_commands[CM_INDENT];
901 }
902 } else {
903 /* length > 1 */
904 cm = ed_commands;
905 for (;;) {
906 if (cm->cmd &&
907 strncmp(cb->cmd, cm->cmd, p - cb->cmd) == 0) {
908 break;
909 }
910 if (++cm == &ed_commands[NR_CMD]) {
911 cm = (cmd *) NULL;
912 break;
913 }
914 }
915 }
916
917 if (cm == (cmd *) NULL) {
918 error("No such command");
919 }
920
921 /* CM_EXCL */
922 if ((cm->flags & CM_EXCL) && *p == '!') {
923 cb->flags |= CB_EXCL;
924 p++;
925 }
926
927 p = skipst(p);
928
929 /* CM_BUFFER */
930 if ((cm->flags & CM_BUFFER) && isalpha(*p)) {
931 cb->a_buffer = *p;
932 p = skipst(p + 1);
933 }
934
935 cb->cmd = p;
936
937 /* CM_COUNT */
938 if (cm->flags & CM_COUNT) {
939 cb_count(cb);
940 }
941
942 /* CM_ADDR */
943 if (cm->flags & CM_ADDR) {
944 cb->a_addr = cb_address(cb, cb->this);
945 if (cb->a_addr < 0) {
946 error("Command requires a trailing address");
947 }
948 cb->cmd = skipst(cb->cmd);
949 }
950 }
951
952 /*
953 * check/adjust line range
954 */
955 ltype = cm->flags & CM_LNMASK;
956 if (ltype != CM_LN0) {
957 if ((ltype == CM_LNDOT || ltype == CM_LNRNG) &&
958 cb->edbuf->lines == 0) {
959 error("No lines in buffer");
960 }
961 if (cb->first == 0) {
962 error("Nonzero address required on this command");
963 }
964 }
965 switch (ltype) {
966 case CM_LNDOT:
967 case CM_LN0:
968 if (cb->first < 0) {
969 cb->first = cb->this;
970 }
971 if (cb->last < 0) {
972 cb->last = cb->first;
973 }
974 break;
975
976 case CM_LNRNG:
977 if (cb->first < 0) {
978 cb->first = 1;
979 cb->last = cb->edbuf->lines;
980 } else if ( cb->last < 0) {
981 cb->last = cb->first;
982 }
983 break;
984 }
985 if (cb->first > cb->edbuf->lines || cb->last > cb->edbuf->lines ||
986 cb->a_addr > cb->edbuf->lines) {
987 error("Not that many lines in buffer");
988 }
989 if (cb->last >= 0 && cb->last < cb->first) {
990 error("Inverted address range");
991 }
992
993 ret = (*cm->ftn)(cb);
994
995 p = skipst(cb->cmd);
996
997 if (ret == RET_FLAGS) {
998 for (;;) {
999 switch (*p++) {
1000 case '-':
1001 --cb->this;
1002 continue;
1003
1004 case '+':
1005 cb->this++;
1006 continue;
1007
1008 case 'p':
1009 /* ignore */
1010 continue;
1011
1012 case 'l':
1013 cb->flags |= CB_LIST;
1014 continue;
1015
1016 case '#':
1017 cb->flags |= CB_NUMBER;
1018 continue;
1019 }
1020 --p;
1021 break;
1022 }
1023
1024 if (cb->this <= 0) {
1025 cb->this = 1;
1026 }
1027 if (cb->this > cb->edbuf->lines) {
1028 cb->this = cb->edbuf->lines;
1029 }
1030 if (cb->this != 0 && !(cb->flags & CB_GLOBAL)) {
1031 /* no autoprint in global */
1032 cb->first = cb->last = cb->this;
1033 cb_print(cb);
1034 }
1035 p = skipst(cb->cmd);
1036 }
1037
1038 /* another command? */
1039 if (*p == '|' && (cb->flags & CB_GLOBAL) && ret != RET_QUIT) {
1040 cb->cmd = p + 1;
1041 continue;
1042 }
1043 /* it has to be finished now */
1044 if (*p != '\0') {
1045 error("Illegal characters after command");
1046 }
1047 if (ret == RET_QUIT) {
1048 return FALSE;
1049 }
1050 }
1051
1052 return TRUE;
1053 }
1054 }
1055