xref: /minix/minix/usr.bin/mined/mined2.c (revision eb95f895)
1 /*
2  * Part 2 of the mined editor.
3  */
4 
5 /*  ========================================================================  *
6  *				Move Commands				      *
7  *  ========================================================================  */
8 
9 #include "mined.h"
10 #include <string.h>
11 
12 /*
13  * Move one line up.
14  */
15 void UP1(void)
16 {
17   if (y == 0) {		/* Top line of screen. Scroll one line */
18   	(void) reverse_scroll();
19   	move_to(x, y);
20   }
21   else			/* Move to previous line */
22   	move_to(x, y - 1);
23 }
24 
25 /*
26  * Move one line down.
27  */
28 void DN1(void)
29 {
30   if (y == last_y) {	/* Last line of screen. Scroll one line */
31 	if (bot_line->next == tail && bot_line->text[0] != '\n') {
32 		dummy_line();		/* Create new empty line */
33 		DN1();
34 		return;
35 	}
36 	else {
37 		(void) forward_scroll();
38 		move_to(x, y);
39 	}
40   }
41   else			/* Move to next line */
42   	move_to(x, y + 1);
43 }
44 
45 /*
46  * Move left one position.
47  */
48 void LF1(void)
49 {
50   if (x == 0 && get_shift(cur_line->shift_count) == 0) {/* Begin of line */
51 	if (cur_line->prev != header) {
52 		UP1();					/* Move one line up */
53 		move_to(LINE_END, y);
54 	}
55   }
56   else
57   	move_to(x - 1, y);
58 }
59 
60 /*
61  * Move right one position.
62  */
63 void RT1(void)
64 {
65   if (*cur_text == '\n') {
66   	if (cur_line->next != tail) {		/* Last char of file */
67 		DN1();				/* Move one line down */
68 		move_to(LINE_START, y);
69 	}
70   }
71   else
72   	move_to(x + 1, y);
73 }
74 
75 /*
76  * Move to coordinates [0, 0] on screen.
77  */
78 void HIGH(void)
79 {
80   move_to(0, 0);
81 }
82 
83 /*
84  * Move to coordinates [0, YMAX] on screen.
85  */
86 void LOW(void)
87 {
88   move_to(0, last_y);
89 }
90 
91 /*
92  * Move to begin of line.
93  */
94 void BL(void)
95 {
96   move_to(LINE_START, y);
97 }
98 
99 /*
100  * Move to end of line.
101  */
102 void EL(void)
103 {
104   move_to(LINE_END, y);
105 }
106 
107 /*
108  * GOTO() prompts for a linenumber and moves to that line.
109  */
110 void GOTO(void)
111 {
112   int number;
113   LINE *line;
114 
115   if (get_number("Please enter line number.", &number) == ERRORS)
116   	return;
117 
118   if (number <= 0 || (line = proceed(header->next, number - 1)) == tail)
119   	error("Illegal line number: ", num_out((long) number));
120   else
121   	move_to(x, find_y(line));
122 }
123 
124 /*
125  * Scroll forward one page or to eof, whatever comes first. (Bot_line becomes
126  * top_line of display.) Try to leave the cursor on the same line. If this is
127  * not possible, leave cursor on the line halfway the page.
128  */
129 void PD(void)
130 {
131   register int i;
132 
133   for (i = 0; i < screenmax; i++)
134   	if (forward_scroll() == ERRORS)
135   		break;			/* EOF reached */
136   if (y - i < 0)				/* Line no longer on screen */
137   	move_to(0, screenmax >> 1);
138   else
139   	move_to(0, y - i);
140 }
141 
142 
143 /*
144  * Scroll backwards one page or to top of file, whatever comes first. (Top_line
145  * becomes bot_line of display).  The very bottom line (YMAX) is always blank.
146  * Try to leave the cursor on the same line. If this is not possible, leave
147  * cursor on the line halfway the page.
148  */
149 void PU(void)
150 {
151   register int i;
152 
153   for (i = 0; i < screenmax; i++)
154   	if (reverse_scroll() == ERRORS)
155   		break;			/* Top of file reached */
156   set_cursor(0, ymax);			/* Erase very bottom line */
157   tputs(CE, 0, _putch);
158   if (y + i > screenmax)			/* line no longer on screen */
159   	move_to(0, screenmax >> 1);
160   else
161   	move_to(0, y + i);
162 }
163 
164 /*
165  * Go to top of file, scrolling if possible, else redrawing screen.
166  */
167 void HO(void)
168 {
169   if (proceed(top_line, -screenmax) == header)
170   	PU();			/* It fits. Let PU do it */
171   else {
172   	reset(header->next, 0);/* Reset top_line, etc. */
173   	RD();			/* Display full page */
174   }
175   move_to(LINE_START, 0);
176 }
177 
178 /*
179  * Go to last line of file, scrolling if possible, else redrawing screen
180  */
181 void EF(void)
182 {
183   if (tail->prev->text[0] != '\n')
184 	dummy_line();
185   if (proceed(bot_line, screenmax) == tail)
186   	PD();			/* It fits. Let PD do it */
187   else {
188   	reset(proceed(tail->prev, -screenmax), screenmax);
189   	RD();			/* Display full page */
190   }
191   move_to(LINE_START, last_y);
192 }
193 
194 /*
195  * Scroll one line up. Leave the cursor on the same line (if possible).
196  */
197 void SU(void)
198 {
199   if (top_line->prev == header)	/* Top of file. Can't scroll */
200   	return;
201 
202   (void) reverse_scroll();
203   set_cursor(0, ymax);		/* Erase very bottom line */
204   tputs(CE, 0, _putch);
205   move_to(x, (y == screenmax) ? screenmax : y + 1);
206 }
207 
208 /*
209  * Scroll one line down. Leave the cursor on the same line (if possible).
210  */
211 void SD(void)
212 {
213   if (forward_scroll() != ERRORS)
214   	move_to(x, (y == 0) ? 0 : y - 1);
215   else
216   	set_cursor(x, y);
217 }
218 
219 /*
220  * Perform a forward scroll. It returns ERRORS if we're at the last line of the
221  * file.
222  */
223 int forward_scroll(void)
224 {
225   if (bot_line->next == tail)		/* Last line of file. No dice */
226   	return ERRORS;
227   top_line = top_line->next;
228   bot_line = bot_line->next;
229   cur_line = cur_line->next;
230   set_cursor(0, ymax);
231   line_print(bot_line);
232 
233   return FINE;
234 }
235 
236 /*
237  * Perform a backwards scroll. It returns ERRORS if we're at the first line
238  * of the file.
239  */
240 int reverse_scroll(void)
241 {
242   if (top_line->prev == header)
243   	return ERRORS;		/* Top of file. Can't scroll */
244 
245   if (last_y != screenmax)	/* Reset last_y if necessary */
246   	last_y++;
247   else
248   	bot_line = bot_line->prev;	/* Else adjust bot_line */
249   top_line = top_line->prev;
250   cur_line = cur_line->prev;
251 
252 /* Perform the scroll */
253   set_cursor(0, 0);
254   tputs(AL, 0, _putch);
255   set_cursor(0, 0);
256   line_print(top_line);
257 
258   return FINE;
259 }
260 
261 /*
262  * A word is defined as a number of non-blank characters separated by tabs
263  * spaces or linefeeds.
264  */
265 
266 /*
267  * MP() moves to the start of the previous word. A word is defined as a
268  * number of non-blank characters separated by tabs spaces or linefeeds.
269  */
270 void MP(void)
271 {
272   move_previous_word(NO_DELETE);
273 }
274 
275 void move_previous_word(FLAG remove)
276 {
277   register char *begin_line;
278   register char *textp;
279   char start_char = *cur_text;
280   char *start_pos = cur_text;
281 
282 /* Fist check if we're at the beginning of line. */
283   if (cur_text == cur_line->text) {
284   	if (cur_line->prev == header)
285   		return;
286   	start_char = '\0';
287   }
288 
289   LF1();
290 
291   begin_line = cur_line->text;
292   textp = cur_text;
293 
294 /* Check if we're in the middle of a word. */
295   if (!alpha(*textp) || !alpha(start_char)) {
296   	while (textp != begin_line && (white_space(*textp) || *textp == '\n'))
297   		textp--;
298   }
299 
300 /* Now we're at the end of previous word. Skip non-blanks until a blank comes */
301   while (textp != begin_line && alpha(*textp))
302   	textp--;
303 
304 /* Go to the next char if we're not at the beginning of the line */
305   if (textp != begin_line && *textp != '\n')
306   	textp++;
307 
308 /* Find the x-coordinate of this address, and move to it */
309   move_address(textp);
310   if (remove == DELETE)
311   	delete(cur_line, textp, cur_line, start_pos);
312 }
313 
314 /*
315  * MN() moves to the start of the next word. A word is defined as a number of
316  * non-blank characters separated by tabs spaces or linefeeds. Always keep in
317  * mind that the pointer shouldn't pass the '\n'.
318  */
319 void MN(void)
320 {
321   move_next_word(NO_DELETE);
322 }
323 
324 void move_next_word(FLAG remove)
325 {
326   register char *textp = cur_text;
327 
328 /* Move to the end of the current word. */
329   while (*textp != '\n' && alpha(*textp))
330   	textp++;
331 
332 /* Skip all white spaces */
333   while (*textp != '\n' && white_space(*textp))
334   	textp++;
335 /* If we're deleting. delete the text in between */
336   if (remove == DELETE) {
337   	delete(cur_line, cur_text, cur_line, textp);
338   	return;
339   }
340 
341 /* If we're at end of line. move to the first word on the next line. */
342   if (*textp == '\n' && cur_line->next != tail) {
343   	DN1();
344   	move_to(LINE_START, y);
345   	textp = cur_text;
346   	while (*textp != '\n' && white_space(*textp))
347   		textp++;
348   }
349   move_address(textp);
350 }
351 
352 /*  ========================================================================  *
353  *				Modify Commands				      *
354  *  ========================================================================  */
355 
356 /*
357  * DCC deletes the character under the cursor.  If this character is a '\n' the
358  * current line is joined with the next one.
359  * If this character is the only character of the line, the current line will
360  * be deleted.
361  */
362 void DCC(void)
363 {
364   if (*cur_text == '\n')
365   	delete(cur_line,cur_text, cur_line->next,cur_line->next->text);
366   else
367   	delete(cur_line, cur_text, cur_line, cur_text + 1);
368 }
369 
370 /*
371  * DPC deletes the character on the left side of the cursor.  If the cursor is
372  * at the beginning of the line, the last character if the previous line is
373  * deleted.
374  */
375 void DPC(void)
376 {
377   if (x == 0 && cur_line->prev == header)
378   	return;			/* Top of file */
379 
380   LF1();				/* Move one left */
381   DCC();				/* Delete character under cursor */
382 }
383 
384 /*
385  * DLN deletes all characters until the end of the line. If the current
386  * character is a '\n', then delete that char.
387  */
388 void DLN(void)
389 {
390   if (*cur_text == '\n')
391   	DCC();
392   else
393   	delete(cur_line, cur_text, cur_line, cur_text + length_of(cur_text) -1);
394 }
395 
396 /*
397  * DNW() deletes the next word (as described in MN())
398  */
399 void DNW(void)
400 {
401   if (*cur_text == '\n')
402   	DCC();
403   else
404   	move_next_word(DELETE);
405 }
406 
407 /*
408  * DPW() deletes the next word (as described in MP())
409  */
410 void DPW(void)
411 {
412   if (cur_text == cur_line->text)
413   	DPC();
414   else
415   	move_previous_word(DELETE);
416 }
417 
418 /*
419  * Insert character `character' at current location.
420  */
421 void S(int character)
422 {
423   static char buffer[2];
424 
425   buffer[0] = character;
426 /* Insert the character */
427   if (insert(cur_line, cur_text, buffer) == ERRORS)
428   	return;
429 
430 /* Fix screen */
431   if (character == '\n') {
432   	set_cursor(0, y);
433   	if (y == screenmax) {		/* Can't use display */
434   		line_print(cur_line);
435   		(void) forward_scroll();
436   	}
437   	else {
438   		reset(top_line, y);	/* Reset pointers */
439   		display(0, y, cur_line, last_y - y);
440   	}
441   	move_to(0, (y == screenmax) ? y : y + 1);
442   }
443   else if (x + 1 == XBREAK)/* If line must be shifted, just call move_to*/
444   	move_to(x + 1, y);
445   else {			 /* else display rest of line */
446   	put_line(cur_line, x, FALSE);
447   	move_to(x + 1, y);
448   }
449 }
450 
451 /*
452  * CTL inserts a control-char at the current location. A message that this
453  * function is called is displayed at the status line.
454  */
455 void CTL(void)
456 {
457   register char ctrl;
458 
459   status_line("Enter control character.", NULL);
460   if ((ctrl = getch()) >= '\01' && ctrl <= '\037') {
461   	S(ctrl);		/* Insert the char */
462 	clear_status();
463   }
464   else
465 	error ("Unknown control character", NULL);
466 }
467 
468 /*
469  * LIB insert a line at the current position and moves back to the end of
470  * the previous line.
471  */
472 void LIB(void)
473 {
474   S('\n');	  		/* Insert the line */
475   UP1();			/* Move one line up */
476   move_to(LINE_END, y);		/* Move to end of this line */
477 }
478 
479 /*
480  * Line_insert() inserts a new line with text pointed to by `string'.
481  * It returns the address of the new line.
482  */
483 LINE *line_insert(register LINE *line, char *string, int len)
484 {
485   register LINE *new_line;
486 
487 /* Allocate space for LINE structure and text */
488   new_line = install_line(string, len);
489 
490 /* Install the line into the double linked list */
491   new_line->prev = line;
492   new_line->next = line->next;
493   line->next = new_line;
494   new_line->next->prev = new_line;
495 
496 /* Increment nlines */
497   nlines++;
498 
499   return new_line;
500 }
501 
502 /*
503  * Insert() insert the string `string' at the given line and location.
504  */
505 int insert(register LINE *line, char *location, char *string)
506 {
507   register char *bufp = text_buffer;	/* Buffer for building line */
508   register char *textp = line->text;
509 
510   if (length_of(textp) + length_of(string) >= MAX_CHARS) {
511   	error("Line too long", NULL);
512   	return ERRORS;
513   }
514 
515   modified = TRUE;			/* File has been modified */
516 
517 /* Copy part of line until `location' has been reached */
518   while (textp != location)
519   	*bufp++ = *textp++;
520 
521 /* Insert string at this location */
522   while (*string != '\0')
523   	*bufp++ = *string++;
524   *bufp = '\0';
525 
526   if (*(string - 1) == '\n')		/* Insert a new line */
527   	(void) line_insert(line, location, length_of(location));
528   else					/* Append last part of line */
529   	copy_string(bufp, location);
530 
531 /* Install the new text in this line */
532   free_space(line->text);
533   line->text = alloc(length_of(text_buffer) + 1);
534   copy_string(line->text, text_buffer);
535 
536   return FINE;
537 }
538 
539 /*
540  * Line_delete() deletes the argument line out of the line list. The pointer to
541  * the next line is returned.
542  */
543 LINE *line_delete(register LINE *line)
544 {
545   register LINE *next_line = line->next;
546 
547 /* Delete the line */
548   line->prev->next = line->next;
549   line->next->prev = line->prev;
550 
551 /* Free allocated space */
552   free_space(line->text);
553   free_space((char*)line);
554 
555 /* Decrement nlines */
556   nlines--;
557 
558   return next_line;
559 }
560 
561 /*
562  * Delete() deletes all the characters (including newlines) between the
563  * startposition and endposition and fixes the screen accordingly. It
564  * returns the number of lines deleted.
565  */
566 void delete(register LINE *start_line, char *start_textp, LINE *end_line,
567 							char *end_textp)
568 {
569   register char *textp = start_line->text;
570   register char *bufp = text_buffer;	/* Storage for new line->text */
571   LINE *line, *stop;
572   int line_cnt = 0;			/* Nr of lines deleted */
573   int count = 0;
574   int shift = 0;				/* Used in shift calculation */
575   int nx = x;
576 
577   modified = TRUE;			/* File has been modified */
578 
579 /* Set up new line. Copy first part of start line until start_position. */
580   while (textp < start_textp) {
581   	*bufp++ = *textp++;
582   	count++;
583   }
584 
585 /* Check if line doesn't exceed MAX_CHARS */
586   if (count + length_of(end_textp) >= MAX_CHARS) {
587   	error("Line too long", NULL);
588   	return;
589   }
590 
591 /* Copy last part of end_line if end_line is not tail */
592   copy_string(bufp, (end_textp != NULL) ? end_textp : "\n");
593 
594 /* Delete all lines between start and end_position (including end_line) */
595   line = start_line->next;
596   stop = end_line->next;
597   while (line != stop && line != tail) {
598   	line = line_delete(line);
599   	line_cnt++;
600   }
601 
602 /* Check if last line of file should be deleted */
603   if (end_textp == NULL && length_of(start_line->text) == 1 && nlines > 1) {
604   	start_line = start_line->prev;
605   	(void) line_delete(start_line->next);
606   	line_cnt++;
607   }
608   else {	/* Install new text */
609   	free_space(start_line->text);
610   	start_line->text = alloc(length_of(text_buffer) + 1);
611   	copy_string(start_line->text, text_buffer);
612   }
613 
614 /* Fix screen. First check if line is shifted. Perhaps we should shift it back*/
615   if (get_shift(start_line->shift_count)) {
616   	shift = (XBREAK - count_chars(start_line)) / SHIFT_SIZE;
617   	if (shift > 0) {		/* Shift line `shift' back */
618   		if (shift >= get_shift(start_line->shift_count))
619   			start_line->shift_count = 0;
620   		else
621   			start_line->shift_count -= shift;
622   		nx += shift * SHIFT_SIZE;/* Reset x value */
623   	}
624   }
625 
626   if (line_cnt == 0) {		    /* Check if only one line changed */
627   	if (shift > 0) {	    /* Reprint whole line */
628   		set_cursor(0, y);
629   		line_print(start_line);
630   	}
631   	else {			    /* Just display last part of line */
632   		set_cursor(x, y);
633   		put_line(start_line, x, TRUE);
634   	}
635   	move_to(nx, y);	   /* Reset cur_text */
636   	return;
637   }
638 
639   shift = last_y;	   /* Save value */
640   reset(top_line, y);
641   display(0, y, start_line, shift - y);
642   move_to((line_cnt == 1) ? nx : 0, y);
643 }
644 
645 /*  ========================================================================  *
646  *				Yank Commands				      *
647  *  ========================================================================  */
648 
649 LINE *mark_line;			/* For marking position. */
650 char *mark_text;
651 int lines_saved;			/* Nr of lines in buffer */
652 
653 /*
654  * PT() inserts the buffer at the current location.
655  */
656 void PT(void)
657 {
658   register int fd;		/* File descriptor for buffer */
659 
660   if ((fd = scratch_file(READ)) == ERRORS)
661   	error("Buffer is empty.", NULL);
662   else {
663   	file_insert(fd, FALSE);/* Insert the buffer */
664   	(void) close(fd);
665   }
666 }
667 
668 /*
669  * IF() prompt for a filename and inserts the file at the current location
670  * in the file.
671  */
672 void IF(void)
673 {
674   register int fd;		/* File descriptor of file */
675   char name[LINE_LEN];		/* Buffer for file name */
676 
677 /* Get the file name */
678   if (get_file("Get and insert file:", name) != FINE)
679   	return;
680 
681   if ((fd = open(name, 0)) < 0)
682   	error("Cannot open ", name);
683   else {
684   	file_insert(fd, TRUE);	/* Insert the file */
685   	(void) close(fd);
686   }
687 }
688 
689 /*
690  * File_insert() inserts a an opened file (as given by filedescriptor fd)
691  * at the current location.
692  */
693 void file_insert(int fd, FLAG old_pos)
694 {
695   char line_buffer[MAX_CHARS];		/* Buffer for next line */
696   register LINE *line = cur_line;
697   register int line_count = nlines;	/* Nr of lines inserted */
698   LINE *page = cur_line;
699   int ret = ERRORS;
700 
701 /* Get the first piece of text (might be ended with a '\n') from fd */
702   if (get_line(fd, line_buffer) == ERRORS)
703   	return;				/* Empty file */
704 
705 /* Insert this text at the current location. */
706   if (insert(line, cur_text, line_buffer) == ERRORS)
707   	return;
708 
709 /* Repeat getting lines (and inserting lines) until EOF is reached */
710   while ((ret = get_line(fd, line_buffer)) != ERRORS && ret != NO_LINE)
711   	line = line_insert(line, line_buffer, ret);
712 
713   if (ret == NO_LINE) {		/* Last line read not ended by a '\n' */
714   	line = line->next;
715   	(void) insert(line, line->text, line_buffer);
716   }
717 
718 /* Calculate nr of lines added */
719   line_count = nlines - line_count;
720 
721 /* Fix the screen */
722   if (line_count == 0) {		/* Only one line changed */
723   	set_cursor(0, y);
724   	line_print(line);
725   	move_to((old_pos == TRUE) ? x : x + length_of(line_buffer), y);
726   }
727   else {				/* Several lines changed */
728   	reset(top_line, y);	/* Reset pointers */
729   	while (page != line && page != bot_line->next)
730   		page = page->next;
731   	if (page != bot_line->next || old_pos == TRUE)
732   		display(0, y, cur_line, screenmax - y);
733   	if (old_pos == TRUE)
734   		move_to(x, y);
735   	else if (ret == NO_LINE)
736 		move_to(length_of(line_buffer), find_y(line));
737 	else
738 		move_to(0, find_y(line->next));
739   }
740 
741 /* If nr of added line >= REPORT, print the count */
742   if (line_count >= REPORT)
743   	status_line(num_out((long) line_count), " lines added.");
744 }
745 
746 /*
747  * WB() writes the buffer (yank_file) into another file, which
748  * is prompted for.
749  */
750 void WB(void)
751 {
752   register int new_fd;		/* Filedescriptor to copy file */
753   int yank_fd;			/* Filedescriptor to buffer */
754   register int cnt;		/* Count check for read/write */
755   int ret = 0;			/* Error check for write */
756   char file[LINE_LEN];		/* Output file */
757 
758 /* Checkout the buffer */
759   if ((yank_fd = scratch_file(READ)) == ERRORS) {
760   	error("Buffer is empty.", NULL);
761   	return;
762   }
763 
764 /* Get file name */
765   if (get_file("Write buffer to file:", file) != FINE)
766   	return;
767 
768 /* Creat the new file */
769   if ((new_fd = creat(file, 0644)) < 0) {
770   	error("Cannot create ", file);
771   	return;
772   }
773 
774   status_line("Writing ", file);
775 
776 /* Copy buffer into file */
777   while ((cnt = read(yank_fd, text_buffer, sizeof(text_buffer))) > 0)
778   	if (write(new_fd, text_buffer, cnt) != cnt) {
779   		bad_write(new_fd);
780   		ret = ERRORS;
781   		break;
782   	}
783 
784 /* Clean up open files and status_line */
785   (void) close(new_fd);
786   (void) close(yank_fd);
787 
788   if (ret != ERRORS)			/* Bad write */
789   	file_status("Wrote", chars_saved, file, lines_saved, TRUE, FALSE);
790 }
791 
792 /*
793  * MA sets mark_line (mark_text) to the current line (text pointer).
794  */
795 void MA(void)
796 {
797   mark_line = cur_line;
798   mark_text = cur_text;
799   status_line("Mark set", NULL);
800 }
801 
802 /*
803  * YA() puts the text between the marked position and the current
804  * in the buffer.
805  */
806 void YA(void)
807 {
808   set_up(NO_DELETE);
809 }
810 
811 /*
812  * DT() is essentially the same as YA(), but in DT() the text is deleted.
813  */
814 void DT(void)
815 {
816   set_up(DELETE);
817 }
818 
819 /*
820  * Set_up is an interface to the actual yank. It calls checkmark () to check
821  * if the marked position is still valid. If it is, yank is called with the
822  * arguments in the right order.
823  */
824 void set_up(FLAG remove)
825 {
826   switch (checkmark()) {
827   	case NOT_VALID :
828   		error("Mark not set.", NULL);
829   		return;
830   	case SMALLER :
831   		yank(mark_line, mark_text, cur_line, cur_text, remove);
832   		break;
833   	case BIGGER :
834   		yank(cur_line, cur_text, mark_line, mark_text, remove);
835   		break;
836   	case SAME :		/* Ignore stupid behaviour */
837   		yank_status = EMPTY;
838   		chars_saved = 0L;
839   		status_line("0 characters saved in buffer.", NULL);
840   		break;
841   }
842 }
843 
844 /*
845  * Check_mark() checks if mark_line and mark_text are still valid pointers. If
846  * they are it returns SMALLER if the marked position is before the current,
847  * BIGGER if it isn't or SAME if somebody didn't get the point.
848  * NOT_VALID is returned when mark_line and/or mark_text are no longer valid.
849  * Legal() checks if mark_text is valid on the mark_line.
850  */
851 FLAG checkmark(void)
852 {
853   register LINE *line;
854   FLAG cur_seen = FALSE;
855 
856 /* Special case: check is mark_line and cur_line are the same. */
857   if (mark_line == cur_line) {
858   	if (mark_text == cur_text)	/* Even same place */
859   		return SAME;
860   	if (legal() == ERRORS)		/* mark_text out of range */
861   		return NOT_VALID;
862   	return (mark_text < cur_text) ? SMALLER : BIGGER;
863   }
864 
865 /* Start looking for mark_line in the line structure */
866   for (line = header->next; line != tail; line = line->next) {
867   	if (line == cur_line)
868   		cur_seen = TRUE;
869   	else if (line == mark_line)
870   		break;
871   }
872 
873 /* If we found mark_line (line != tail) check for legality of mark_text */
874   if (line == tail || legal() == ERRORS)
875   	return NOT_VALID;
876 
877 /* cur_seen is TRUE if cur_line is before mark_line */
878   return (cur_seen == TRUE) ? BIGGER : SMALLER;
879 }
880 
881 /*
882  * Legal() checks if mark_text is still a valid pointer.
883  */
884 int legal(void)
885 {
886   register char *textp = mark_line->text;
887 
888 /* Locate mark_text on mark_line */
889   while (textp != mark_text && *textp++ != '\0')
890   	;
891   return (*textp == '\0') ? ERRORS : FINE;
892 }
893 
894 /*
895  * Yank puts all the text between start_position and end_position into
896  * the buffer.
897  * The caller must check that the arguments to yank() are valid. (E.g. in
898  * the right order)
899  */
900 void yank(LINE *start_line, char *start_textp, LINE *end_line, char *end_textp,
901 								FLAG remove)
902 {
903   register LINE *line = start_line;
904   register char *textp = start_textp;
905   int fd;
906 
907 /* Creat file to hold buffer */
908   if ((fd = scratch_file(WRITE)) == ERRORS)
909   	return;
910 
911   chars_saved = 0L;
912   lines_saved = 0;
913   status_line("Saving text.", NULL);
914 
915 /* Keep writing chars until the end_location is reached. */
916   while (textp != end_textp) {
917   	if (write_char(fd, *textp) == ERRORS) {
918   		(void) close(fd);
919   		return;
920   	}
921   	if (*textp++ == '\n') {	/* Move to the next line */
922   		line = line->next;
923   		textp = line->text;
924   		lines_saved++;
925   	}
926   	chars_saved++;
927   }
928 
929 /* Flush the I/O buffer and close file */
930   if (flush_buffer(fd) == ERRORS) {
931   	(void) close(fd);
932   	return;
933   }
934   (void) close(fd);
935   yank_status = VALID;
936 
937 /*
938  * Check if the text should be deleted as well. If it should, the following
939  * hack is used to save a lot of code. First move back to the start_position.
940  * (This might be the location we're on now!) and them delete the text.
941  * It might be a bit confusing the first time somebody uses it.
942  * Delete() will fix the screen.
943  */
944   if (remove == DELETE) {
945   	move_to(find_x(start_line, start_textp), find_y(start_line));
946   	delete(start_line, start_textp, end_line, end_textp);
947   }
948 
949   status_line(num_out(chars_saved), " characters saved in buffer.");
950 }
951 
952 /*
953  * Scratch_file() creates a uniq file in /usr/tmp. If the file couldn't
954  * be created other combinations of files are tried until a maximum
955  * of MAXTRAILS times. After MAXTRAILS times, an error message is given
956  * and ERRORS is returned.
957  */
958 
959 #define MAXTRAILS	26
960 
961 int scratch_file(FLAG mode)
962 {
963   static int trials = 0;		/* Keep track of trails */
964   register char *y_ptr, *n_ptr;
965   int fd;				/* Filedescriptor to buffer */
966 
967 /* If yank_status == NOT_VALID, scratch_file is called for the first time */
968   if (yank_status == NOT_VALID && mode == WRITE) { /* Create new file */
969   	/* Generate file name. */
970 	y_ptr = &yank_file[11];
971 	n_ptr = num_out((long) getpid());
972 	while ((*y_ptr = *n_ptr++) != '\0')
973 		y_ptr++;
974 	*y_ptr++ = 'a' + trials;
975 	*y_ptr = '\0';
976   	/* Check file existence */
977   	if (access(yank_file, 0) == 0 || (fd = creat(yank_file, 0644)) < 0) {
978   		if (trials++ >= MAXTRAILS) {
979   			error("Unable to creat scratchfile.", NULL);
980   			return ERRORS;
981   		}
982   		else
983   			return scratch_file(mode);/* Have another go */
984   	}
985   }
986   else if ((mode == READ && (fd = open(yank_file, 0)) < 0) ||
987 			(mode == WRITE && (fd = creat(yank_file, 0644)) < 0)) {
988   	yank_status = NOT_VALID;
989   	return ERRORS;
990   }
991 
992   clear_buffer();
993   return fd;
994 }
995 
996 /*  ========================================================================  *
997  *				Search Routines				      *
998  *  ========================================================================  */
999 
1000 /*
1001  * A regular expression consists of a sequence of:
1002  * 	1. A normal character matching that character.
1003  * 	2. A . matching any character.
1004  * 	3. A ^ matching the begin of a line.
1005  * 	4. A $ (as last character of the pattern) mathing the end of a line.
1006  * 	5. A \<character> matching <character>.
1007  * 	6. A number of characters enclosed in [] pairs matching any of these
1008  * 	   characters. A list of characters can be indicated by a '-'. So
1009  * 	   [a-z] matches any letter of the alphabet. If the first character
1010  * 	   after the '[' is a '^' then the set is negated (matching none of
1011  * 	   the characters).
1012  * 	   A ']', '^' or '-' can be escaped by putting a '\' in front of it.
1013  * 	7. If one of the expressions as described in 1-6 is followed by a
1014  * 	   '*' than that expressions matches a sequence of 0 or more of
1015  * 	   that expression.
1016  */
1017 
1018 char typed_expression[LINE_LEN];	/* Holds previous expr. */
1019 
1020 /*
1021  * SF searches forward for an expression.
1022  */
1023 void SF(void)
1024 {
1025   search("Search forward:", FORWARD);
1026 }
1027 
1028 /*
1029  * SF searches backwards for an expression.
1030  */
1031 void SR(void)
1032 {
1033   search("Search reverse:", REVERSE);
1034 }
1035 
1036 /*
1037  * Get_expression() prompts for an expression. If just a return is typed, the
1038  * old expression is used. If the expression changed, compile() is called and
1039  * the returning REGEX structure is returned. It returns NULL upon error.
1040  * The save flag indicates whether the expression should be appended at the
1041  * message pointer.
1042  */
1043 REGEX *get_expression(char *message)
1044 {
1045   static REGEX program;			/* Program of expression */
1046   char exp_buf[LINE_LEN];			/* Buffer for new expr. */
1047 
1048   if (get_string(message, exp_buf, FALSE) == ERRORS)
1049   	return NULL;
1050 
1051   if (exp_buf[0] == '\0' && typed_expression[0] == '\0') {
1052   	error("No previous expression.", NULL);
1053   	return NULL;
1054   }
1055 
1056   if (exp_buf[0] != '\0') {		/* A new expr. is typed */
1057   	copy_string(typed_expression, exp_buf);/* Save expr. */
1058   	compile(exp_buf, &program);	/* Compile new expression */
1059   }
1060 
1061   if (program.status == REG_ERROR) {	/* Error during compiling */
1062   	error(program.result.err_mess, NULL);
1063   	return NULL;
1064   }
1065   return &program;
1066 }
1067 
1068 /*
1069  * GR() a replaces all matches from the current position until the end
1070  * of the file.
1071  */
1072 void GR(void)
1073 {
1074   change("Global replace:", VALID);
1075 }
1076 
1077 /*
1078  * LR() replaces all matches on the current line.
1079  */
1080 void LR(void)
1081 {
1082   change("Line replace:", NOT_VALID);
1083 }
1084 
1085 /*
1086  * Change() prompts for an expression and a substitution pattern and changes
1087  * all matches of the expression into the substitution. change() start looking
1088  * for expressions at the current line and continues until the end of the file
1089  * if the FLAG file is VALID.
1090  */
1091 void change(char *message, FLAG file)
1092 {
1093   char mess_buf[LINE_LEN];	/* Buffer to hold message */
1094   char replacement[LINE_LEN];	/* Buffer to hold subst. pattern */
1095   REGEX *program;			/* Program resulting from compilation */
1096   register LINE *line = cur_line;
1097   register char *textp;
1098   long lines = 0L;		/* Nr of lines on which subs occurred */
1099   long subs = 0L;			/* Nr of subs made */
1100   int page = y;			/* Index to check if line is on screen*/
1101 
1102 /* Save message and get expression */
1103   copy_string(mess_buf, message);
1104   if ((program = get_expression(mess_buf)) == NULL)
1105   	return;
1106 
1107 /* Get substitution pattern */
1108   build_string(mess_buf, "%s %s by:", mess_buf, typed_expression);
1109   if (get_string(mess_buf, replacement, FALSE) == ERRORS)
1110   	return;
1111 
1112   set_cursor(0, ymax);
1113   flush();
1114 /* Substitute until end of file */
1115   do {
1116   	if (line_check(program, line->text, FORWARD)) {
1117   		lines++;
1118   		/* Repeat sub. on this line as long as we find a match*/
1119   		do {
1120   			subs++;	/* Increment subs */
1121   			if ((textp = substitute(line, program,replacement))
1122 								     == NULL)
1123   				return;	/* Line too long */
1124   		} while ((program->status & BEGIN_LINE) != BEGIN_LINE &&
1125 			 (program->status & END_LINE) != END_LINE &&
1126 					  line_check(program, textp, FORWARD));
1127   		/* Check to see if we can print the result */
1128   		if (page <= screenmax) {
1129   			set_cursor(0, page);
1130   			line_print(line);
1131   		}
1132   	}
1133   	if (page <= screenmax)
1134   		page++;
1135   	line = line->next;
1136   } while (line != tail && file == VALID && quit == FALSE);
1137 
1138   copy_string(mess_buf, (quit == TRUE) ? "(Aborted) " : "");
1139 /* Fix the status line */
1140   if (subs == 0L && quit == FALSE)
1141   	error("Pattern not found.", NULL);
1142   else if (lines >= REPORT || quit == TRUE) {
1143   	build_string(mess_buf, "%s %D substitutions on %D lines.", mess_buf,
1144 								   subs, lines);
1145   	status_line(mess_buf, NULL);
1146   }
1147   else if (file == NOT_VALID && subs >= REPORT)
1148   	status_line(num_out(subs), " substitutions.");
1149   else
1150   	clear_status();
1151   move_to (x, y);
1152 }
1153 
1154 /*
1155  * Substitute() replaces the match on this line by the substitute pattern
1156  * as indicated by the program. Every '&' in the replacement is replaced by
1157  * the original match. A \ in the replacement escapes the next character.
1158  */
1159 char *substitute(LINE *line, REGEX *program, char *replacement)
1160 {
1161   register char *textp = text_buffer;
1162   register char *subp = replacement;
1163   char *linep = line->text;
1164   char *amp;
1165 
1166   modified = TRUE;
1167 
1168 /* Copy part of line until the beginning of the match */
1169   while (linep != program->start_ptr)
1170   	*textp++ = *linep++;
1171 
1172 /*
1173  * Replace the match by the substitution pattern. Each occurrence of '&' is
1174  * replaced by the original match. A \ escapes the next character.
1175  */
1176   while (*subp != '\0' && textp < &text_buffer[MAX_CHARS]) {
1177   	if (*subp == '&') {		/* Replace the original match */
1178   		amp = program->start_ptr;
1179   		while (amp < program->end_ptr && textp<&text_buffer[MAX_CHARS])
1180   			*textp++ = *amp++;
1181   		subp++;
1182   	}
1183   	else {
1184   		if (*subp == '\\' && *(subp + 1) != '\0')
1185   			subp++;
1186   		*textp++ = *subp++;
1187   	}
1188   }
1189 
1190 /* Check for line length not exceeding MAX_CHARS */
1191   if (length_of(text_buffer) + length_of(program->end_ptr) >= MAX_CHARS) {
1192   	error("Substitution result: line too big", NULL);
1193   	return NULL;
1194   }
1195 
1196 /* Append last part of line to the new build line */
1197   copy_string(textp, program->end_ptr);
1198 
1199 /* Free old line and install new one */
1200   free_space(line->text);
1201   line->text = alloc(length_of(text_buffer) + 1);
1202   copy_string(line->text, text_buffer);
1203 
1204   return(line->text + (textp - text_buffer));
1205 }
1206 
1207 /*
1208  * Search() calls get_expression to fetch the expression. If this went well,
1209  * the function match() is called which returns the line with the next match.
1210  * If this line is the NULL, it means that a match could not be found.
1211  * Find_x() and find_y() display the right page on the screen, and return
1212  * the right coordinates for x and y. These coordinates are passed to move_to()
1213  */
1214 void search(char *message, FLAG method)
1215 {
1216   register REGEX *program;
1217   register LINE *match_line;
1218 
1219 /* Get the expression */
1220   if ((program = get_expression(message)) == NULL)
1221   	return;
1222 
1223   set_cursor(0, ymax);
1224   flush();
1225 /* Find the match */
1226   if ((match_line = match(program, cur_text, method)) == NULL) {
1227   	if (quit == TRUE)
1228   		status_line("Aborted", NULL);
1229   	else
1230   		status_line("Pattern not found.", NULL);
1231   	return;
1232   }
1233 
1234   move(0, program->start_ptr, find_y(match_line));
1235   clear_status();
1236 }
1237 
1238 /*
1239  * find_y() checks if the matched line is on the current page.  If it is, it
1240  * returns the new y coordinate, else it displays the correct page with the
1241  * matched line in the middle and returns the new y value;
1242  */
1243 int find_y(LINE *match_line)
1244 {
1245   register LINE *line;
1246   register int count = 0;
1247 
1248 /* Check if match_line is on the same page as currently displayed. */
1249   for (line = top_line; line != match_line && line != bot_line->next;
1250   						      line = line->next)
1251   	count++;
1252   if (line != bot_line->next)
1253   	return count;
1254 
1255 /* Display new page, with match_line in center. */
1256   if ((line = proceed(match_line, -(screenmax >> 1))) == header) {
1257   /* Can't display in the middle. Make first line of file top_line */
1258   	count = 0;
1259   	for (line = header->next; line != match_line; line = line->next)
1260   		count++;
1261   	line = header->next;
1262   }
1263   else	/* New page is displayed. Set cursor to middle of page */
1264   	count = screenmax >> 1;
1265 
1266 /* Reset pointers and redraw the screen */
1267   reset(line, 0);
1268   RD();
1269 
1270   return count;
1271 }
1272 
1273 /* Opcodes for characters */
1274 #define	NORMAL		0x0200
1275 #define DOT		0x0400
1276 #define EOLN		0x0800
1277 #define STAR		0x1000
1278 #define BRACKET		0x2000
1279 #define NEGATE		0x0100
1280 #define DONE		0x4000
1281 
1282 /* Mask for opcodes and characters */
1283 #define LOW_BYTE	0x00FF
1284 #define HIGH_BYTE	0xFF00
1285 
1286 /* Previous is the contents of the previous address (ptr) points to */
1287 #define previous(ptr)		(*((ptr) - 1))
1288 
1289 /* Buffer to store outcome of compilation */
1290 int exp_buffer[BLOCK_SIZE];
1291 
1292 /* Errors often used */
1293 char *too_long = "Regular expression too long";
1294 
1295 /*
1296  * Reg_error() is called by compile() is something went wrong. It set the
1297  * status of the structure to error, and assigns the error field of the union.
1298  */
1299 #define reg_error(str)	program->status = REG_ERROR, \
1300   					program->result.err_mess = (str)
1301 /*
1302  * Finished() is called when everything went right during compilation. It
1303  * allocates space for the expression, and copies the expression buffer into
1304  * this field.
1305  */
1306 void finished(register REGEX *program, int *last_exp)
1307 {
1308   register int length = (last_exp - exp_buffer) * sizeof(int);
1309 
1310 /* Allocate space */
1311   program->result.expression = (int *) alloc(length);
1312 /* Copy expression. (expression consists of ints!) */
1313   bcopy(exp_buffer, program->result.expression, length);
1314 }
1315 
1316 /*
1317  * Compile compiles the pattern into a more comprehensible form and returns a
1318  * REGEX structure. If something went wrong, the status field of the structure
1319  * is set to REG_ERROR and an error message is set into the err_mess field of
1320  * the union. If all went well the expression is saved and the expression
1321  * pointer is set to the saved (and compiled) expression.
1322  */
1323 void compile(register char *pattern, REGEX *program)
1324 {
1325   register int *expression = exp_buffer;
1326   int *prev_char;			/* Pointer to previous compiled atom */
1327   int *acct_field;		/* Pointer to last BRACKET start */
1328   FLAG negate;			/* Negate flag for BRACKET */
1329   char low_char;			/* Index for chars in BRACKET */
1330   char c;
1331 
1332 /* Check for begin of line */
1333   if (*pattern == '^') {
1334   	program->status = BEGIN_LINE;
1335   	pattern++;
1336   }
1337   else {
1338   	program->status = 0;
1339 /* If the first character is a '*' we have to assign it here. */
1340   	if (*pattern == '*') {
1341   		*expression++ = '*' + NORMAL;
1342   		pattern++;
1343   	}
1344   }
1345 
1346   for (; ;) {
1347   	switch (c = *pattern++) {
1348   	case '.' :
1349   		*expression++ = DOT;
1350   		break;
1351   	case '$' :
1352   		/*
1353   		 * Only means EOLN if it is the last char of the pattern
1354   		 */
1355   		if (*pattern == '\0') {
1356   			*expression++ = EOLN | DONE;
1357   			program->status |= END_LINE;
1358   			finished(program, expression);
1359   			return;
1360   		}
1361   		else
1362   			*expression++ = NORMAL + '$';
1363   		break;
1364   	case '\0' :
1365   		*expression++ = DONE;
1366   		finished(program, expression);
1367   		return;
1368   	case '\\' :
1369   		/* If last char, it must! mean a normal '\' */
1370   		if (*pattern == '\0')
1371   			*expression++ = NORMAL + '\\';
1372   		else
1373   			*expression++ = NORMAL + *pattern++;
1374   		break;
1375   	case '*' :
1376   		/*
1377   		 * If the previous expression was a [] find out the
1378   		 * begin of the list, and adjust the opcode.
1379   		 */
1380   		prev_char = expression - 1;
1381   		if (*prev_char & BRACKET)
1382   			*(expression - (*acct_field & LOW_BYTE))|= STAR;
1383   		else
1384   			*prev_char |= STAR;
1385   		break;
1386   	case '[' :
1387   		/*
1388   		 * First field in expression gives information about
1389   		 * the list.
1390   		 * The opcode consists of BRACKET and if necessary
1391   		 * NEGATE to indicate that the list should be negated
1392   		 * and/or STAR to indicate a number of sequence of this
1393   		 * list.
1394   		 * The lower byte contains the length of the list.
1395   		 */
1396   		acct_field = expression++;
1397   		if (*pattern == '^') {	/* List must be negated */
1398   			pattern++;
1399   			negate = TRUE;
1400   		}
1401   		else
1402   			negate = FALSE;
1403   		while (*pattern != ']') {
1404   			if (*pattern == '\0') {
1405   				reg_error("Missing ]");
1406   				return;
1407   			}
1408   			if (*pattern == '\\')
1409   				pattern++;
1410   			*expression++ = *pattern++;
1411   			if (*pattern == '-') {
1412   						/* Make list of chars */
1413   				low_char = previous(pattern);
1414   				pattern++;	/* Skip '-' */
1415   				if (low_char++ > *pattern) {
1416   					reg_error("Bad range in [a-z]");
1417   					return;
1418   				}
1419   				/* Build list */
1420   				while (low_char <= *pattern)
1421   					*expression++ = low_char++;
1422   				pattern++;
1423   			}
1424   			if (expression >= &exp_buffer[BLOCK_SIZE]) {
1425   				reg_error(too_long);
1426   				return;
1427   			}
1428   		}
1429   		pattern++;			/* Skip ']' */
1430   		/* Assign length of list in acct field */
1431   		if ((*acct_field = (expression - acct_field)) == 1) {
1432   			reg_error("Empty []");
1433   			return;
1434   		}
1435   		/* Assign negate and bracket field */
1436   		*acct_field |= BRACKET;
1437   		if (negate == TRUE)
1438   			*acct_field |= NEGATE;
1439   		/*
1440   		 * Add BRACKET to opcode of last char in field because
1441   		 * a '*' may be following the list.
1442   		 */
1443   		previous(expression) |= BRACKET;
1444   		break;
1445   	default :
1446   		*expression++ = c + NORMAL;
1447   	}
1448   	if (expression == &exp_buffer[BLOCK_SIZE]) {
1449   		reg_error(too_long);
1450   		return;
1451   	}
1452   }
1453   /* NOTREACHED */
1454 }
1455 
1456 /*
1457  * Match gets as argument the program, pointer to place in current line to
1458  * start from and the method to search for (either FORWARD or REVERSE).
1459  * Match() will look through the whole file until a match is found.
1460  * NULL is returned if no match could be found.
1461  */
1462 LINE *match(REGEX *program, char *string, register FLAG method)
1463 {
1464   register LINE *line = cur_line;
1465   char old_char;				/* For saving chars */
1466 
1467 /* Corrupted program */
1468   if (program->status == REG_ERROR)
1469   	return NULL;
1470 
1471 /* Check part of text first */
1472   if (!(program->status & BEGIN_LINE)) {
1473   	if (method == FORWARD) {
1474   		if (line_check(program, string + 1, method) == MATCH)
1475   			return cur_line;	/* Match found */
1476   	}
1477   	else if (!(program->status & END_LINE)) {
1478   		old_char = *string;	/* Save char and */
1479   		*string = '\n';		/* Assign '\n' for line_check */
1480   		if (line_check(program, line->text, method) == MATCH) {
1481   			*string = old_char; /* Restore char */
1482   			return cur_line;    /* Found match */
1483   		}
1484   		*string = old_char;	/* No match, but restore char */
1485   	}
1486   }
1487 
1488 /* No match in last (or first) part of line. Check out rest of file */
1489   do {
1490   	line = (method == FORWARD) ? line->next : line->prev;
1491   	if (line->text == NULL)	/* Header/tail */
1492   		continue;
1493   	if (line_check(program, line->text, method) == MATCH)
1494   		return line;
1495   } while (line != cur_line && quit == FALSE);
1496 
1497 /* No match found. */
1498   return NULL;
1499 }
1500 
1501 /*
1502  * Line_check() checks the line (or rather string) for a match. Method
1503  * indicates FORWARD or REVERSE search. It scans through the whole string
1504  * until a match is found, or the end of the string is reached.
1505  */
1506 int line_check(register REGEX *program, char *string, FLAG method)
1507 {
1508   register char *textp = string;
1509 
1510 /* Assign start_ptr field. We might find a match right away! */
1511   program->start_ptr = textp;
1512 
1513 /* If the match must be anchored, just check the string. */
1514   if (program->status & BEGIN_LINE)
1515   	return check_string(program, string, NULL);
1516 
1517   if (method == REVERSE) {
1518   	/* First move to the end of the string */
1519   	for (textp = string; *textp != '\n'; textp++)
1520   		;
1521   	/* Start checking string until the begin of the string is met */
1522   	while (textp >= string) {
1523   		program->start_ptr = textp;
1524   		if (check_string(program, textp--, NULL))
1525   			return MATCH;
1526   	}
1527   }
1528   else {
1529   	/* Move through the string until the end of is found */
1530 	while (quit == FALSE && *textp != '\0') {
1531   		program->start_ptr = textp;
1532   		if (check_string(program, textp, NULL))
1533   			return MATCH;
1534 		if (*textp == '\n')
1535 			break;
1536 		textp++;
1537   	}
1538   }
1539 
1540   return NO_MATCH;
1541 }
1542 
1543 /*
1544  * Check() checks of a match can be found in the given string. Whenever a STAR
1545  * is found during matching, then the begin position of the string is marked
1546  * and the maximum number of matches is performed. Then the function star()
1547  * is called which starts to finish the match from this position of the string
1548  * (and expression). Check() return MATCH for a match, NO_MATCH is the string
1549  * couldn't be matched or REG_ERROR for an illegal opcode in expression.
1550  */
1551 int check_string(REGEX *program, register char *string, int *expression)
1552 {
1553   register int opcode;		/* Holds opcode of next expr. atom */
1554   char c;				/* Char that must be matched */
1555   char *mark;			/* For marking position */
1556   int star_fl;			/* A star has been born */
1557 
1558   if (expression == NULL)
1559   	expression = program->result.expression;
1560 
1561 /* Loop until end of string or end of expression */
1562   while (quit == FALSE && !(*expression & DONE) &&
1563 					   *string != '\0' && *string != '\n') {
1564   	c = *expression & LOW_BYTE;	  /* Extract match char */
1565   	opcode = *expression & HIGH_BYTE; /* Extract opcode */
1566   	if ((star_fl = (opcode & STAR))) {  /* Check star occurrence */
1567   		opcode &= ~STAR;	  /* Strip opcode */
1568   		mark = string;		  /* Mark current position */
1569   	}
1570   	expression++;		/* Increment expr. */
1571   	switch (opcode) {
1572   	case NORMAL :
1573   		if (star_fl)
1574   			while (*string++ == c)	/* Skip all matches */
1575   				;
1576   		else if (*string++ != c)
1577   			return NO_MATCH;
1578   		break;
1579   	case DOT :
1580   		string++;
1581   		if (star_fl)			/* Skip to eoln */
1582   			while (*string != '\0' && *string++ != '\n')
1583   				;
1584   		break;
1585   	case NEGATE | BRACKET:
1586   	case BRACKET :
1587   		if (star_fl)
1588   			while (in_list(expression, *string++, c, opcode)
1589 								       == MATCH)
1590   				;
1591   		else if (in_list(expression, *string++, c, opcode) == NO_MATCH)
1592   			return NO_MATCH;
1593   		expression += c - 1;	/* Add length of list */
1594   		break;
1595   	default :
1596   		panic("Corrupted program in check_string()");
1597   	}
1598   	if (star_fl)
1599   		return star(program, mark, string, expression);
1600   }
1601   if (*expression & DONE) {
1602   	program->end_ptr = string;	/* Match ends here */
1603   	/*
1604   	 * We might have found a match. The last thing to do is check
1605   	 * whether a '$' was given at the end of the expression, or
1606   	 * the match was found on a null string. (E.g. [a-z]* always
1607   	 * matches) unless a ^ or $ was included in the pattern.
1608   	 */
1609   	if ((*expression & EOLN) && *string != '\n' && *string != '\0')
1610   		return NO_MATCH;
1611 	if (string == program->start_ptr && !(program->status & BEGIN_LINE)
1612 					 && !(*expression & EOLN))
1613   		return NO_MATCH;
1614   	return MATCH;
1615   }
1616   return NO_MATCH;
1617 }
1618 
1619 /*
1620  * Star() calls check_string() to find out the longest match possible.
1621  * It searches backwards until the (in check_string()) marked position
1622  * is reached, or a match is found.
1623  */
1624 int star(REGEX *program, register char *end_position, register char *string,
1625 							int *expression)
1626 {
1627   do {
1628   	string--;
1629   	if (check_string(program, string, expression))
1630   		return MATCH;
1631   } while (string != end_position);
1632 
1633   return NO_MATCH;
1634 }
1635 
1636 /*
1637  * In_list() checks if the given character is in the list of []. If it is
1638  * it returns MATCH. if it isn't it returns NO_MATCH. These returns values
1639  * are reversed when the NEGATE field in the opcode is present.
1640  */
1641 int in_list(int *list, int c, register int list_length, int opcode)
1642 {
1643   if (c == '\0' || c == '\n')	/* End of string, never matches */
1644   	return NO_MATCH;
1645   while (list_length-- > 1) {	/* > 1, don't check acct_field */
1646   	if ((*list & LOW_BYTE) == c)
1647   		return (opcode & NEGATE) ? NO_MATCH : MATCH;
1648   	list++;
1649   }
1650   return (opcode & NEGATE) ? MATCH : NO_MATCH;
1651 }
1652 
1653 /*
1654  * Dummy_line() adds an empty line at the end of the file. This is sometimes
1655  * useful in combination with the EF and DN command in combination with the
1656  * Yank command set.
1657  */
1658 void dummy_line(void)
1659 {
1660 	(void) line_insert(tail->prev, "\n", 1);
1661 	tail->prev->shift_count = DUMMY;
1662 	if (last_y != screenmax) {
1663 		last_y++;
1664 		bot_line = bot_line->next;
1665 	}
1666 }
1667