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