1 /* vi: set sw=4 ts=4: */
3 * Copyright (c) 2002 by David I. Bell
4 * Permission is granted to use, distribute, or modify this source,
5 * provided that this copyright notice remains intact.
7 * The "ed" built-in command (much simplified)
10 //usage:#define ed_trivial_usage ""
11 //usage:#define ed_full_usage ""
23 #define searchString bb_common_bufsiz1
26 USERSIZE
= sizeof(searchString
) > 1024 ? 1024
27 : sizeof(searchString
) - 1, /* max line length typed in by user */
28 INITBUF_SIZE
= 1024, /* initial buffer size */
44 #define G (*ptr_to_globals)
45 #define curLine (G.curLine )
46 #define bufBase (G.bufBase )
47 #define bufPtr (G.bufPtr )
48 #define fileName (G.fileName )
49 #define curNum (G.curNum )
50 #define lastNum (G.lastNum )
51 #define bufUsed (G.bufUsed )
52 #define bufSize (G.bufSize )
53 #define dirty (G.dirty )
54 #define lines (G.lines )
55 #define marks (G.marks )
56 #define INIT_G() do { \
57 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
61 static void doCommands(void);
62 static void subCommand(const char *cmd
, int num1
, int num2
);
63 static int getNum(const char **retcp
, smallint
*retHaveNum
, int *retNum
);
64 static int setCurNum(int num
);
65 static void addLines(int num
);
66 static int insertLine(int num
, const char *data
, int len
);
67 static void deleteLines(int num1
, int num2
);
68 static int printLines(int num1
, int num2
, int expandFlag
);
69 static int writeLines(const char *file
, int num1
, int num2
);
70 static int readLines(const char *file
, int num
);
71 static int searchLines(const char *str
, int num1
, int num2
);
72 static LINE
*findLine(int num
);
73 static int findString(const LINE
*lp
, const char * str
, int len
, int offset
);
76 static int bad_nums(int num1
, int num2
, const char *for_what
)
78 if ((num1
< 1) || (num2
> lastNum
) || (num1
> num2
)) {
79 bb_error_msg("bad line range for %s", for_what
);
86 static char *skip_blank(const char *cp
)
94 int ed_main(int argc
, char **argv
) MAIN_EXTERNALLY_VISIBLE
;
95 int ed_main(int argc UNUSED_PARAM
, char **argv
)
99 bufSize
= INITBUF_SIZE
;
100 bufBase
= xmalloc(bufSize
);
106 fileName
= xstrdup(argv
[1]);
107 if (!readLines(fileName
, 1)) {
120 * Read commands until we are told to stop.
122 static void doCommands(void)
125 char *endbuf
, buf
[USERSIZE
];
127 smallint have1
, have2
;
131 * -1 on read errors or EOF, or on bare Ctrl-D.
133 * >0 length of input string, including terminating '\n'
135 len
= read_line_input(NULL
, ": ", buf
, sizeof(buf
), /*timeout*/ -1);
138 endbuf
= &buf
[len
- 1];
139 while ((endbuf
> buf
) && isblank(endbuf
[-1]))
143 cp
= skip_blank(buf
);
147 if ((curNum
== 0) && (lastNum
> 0)) {
149 curLine
= lines
.next
;
152 if (!getNum(&cp
, &have1
, &num1
))
159 if (!getNum(&cp
, &have2
, &num2
))
179 deleteLines(num1
, num2
);
184 deleteLines(num1
, num2
);
188 if (*cp
&& !isblank(*cp
)) {
189 bb_error_msg("bad file command");
195 printf("\"%s\"\n", fileName
);
197 printf("No file name\n");
201 fileName
= xstrdup(cp
);
210 if ((*cp
< 'a') || (*cp
> 'z') || cp
[1]) {
211 bb_error_msg("bad mark name");
214 marks
[*cp
- 'a'] = num2
;
218 printLines(num1
, num2
, TRUE
);
222 printLines(num1
, num2
, FALSE
);
228 bb_error_msg("bad quit command");
233 len
= read_line_input(NULL
, "Really quit? ", buf
, 16, /*timeout*/ -1);
234 /* read error/EOF - no way to continue */
237 cp
= skip_blank(buf
);
238 if ((*cp
| 0x20) == 'y') /* Y or y */
243 if (*cp
&& !isblank(*cp
)) {
244 bb_error_msg("bad read command");
249 bb_error_msg("no file name");
254 if (readLines(cp
, num1
+ 1))
256 if (fileName
== NULL
)
257 fileName
= xstrdup(cp
);
261 subCommand(cp
, num1
, num2
);
265 if (*cp
&& !isblank(*cp
)) {
266 bb_error_msg("bad write command");
277 bb_error_msg("no file name specified");
280 writeLines(cp
, num1
, num2
);
286 printLines(curNum
- 21, curNum
, FALSE
);
289 printLines(curNum
- 11, curNum
+ 10, FALSE
);
292 printLines(curNum
, curNum
+ 21, FALSE
);
299 bb_error_msg("no arguments allowed");
302 printLines(curNum
, curNum
, FALSE
);
306 if (setCurNum(curNum
- 1))
307 printLines(curNum
, curNum
, FALSE
);
311 printf("%d\n", num1
);
315 printLines(num2
, num2
, FALSE
);
318 if (setCurNum(curNum
+ 1))
319 printLines(curNum
, curNum
, FALSE
);
323 bb_error_msg("unimplemented command");
331 * Do the substitute command.
332 * The current line is set to the last substitution done.
334 static void subCommand(const char *cmd
, int num1
, int num2
)
336 char *cp
, *oldStr
, *newStr
, buf
[USERSIZE
];
337 int delim
, oldLen
, newLen
, deltaLen
, offset
;
339 int globalFlag
, printFlag
, didSub
, needPrint
;
341 if (bad_nums(num1
, num2
, "substitute"))
350 * Copy the command so we can modify it.
355 if (isblank(*cp
) || (*cp
== '\0')) {
356 bb_error_msg("bad delimiter for substitute");
363 cp
= strchr(cp
, delim
);
365 bb_error_msg("missing 2nd delimiter for substitute");
372 cp
= strchr(cp
, delim
);
379 while (*cp
) switch (*cp
++) {
387 bb_error_msg("unknown option for substitute");
391 if (*oldStr
== '\0') {
392 if (searchString
[0] == '\0') {
393 bb_error_msg("no previous search string");
396 oldStr
= searchString
;
399 if (oldStr
!= searchString
)
400 strcpy(searchString
, oldStr
);
406 oldLen
= strlen(oldStr
);
407 newLen
= strlen(newStr
);
408 deltaLen
= newLen
- oldLen
;
412 while (num1
<= num2
) {
413 offset
= findString(lp
, oldStr
, oldLen
, offset
);
417 printLines(num1
, num1
, FALSE
);
426 needPrint
= printFlag
;
431 * If the replacement string is the same size or shorter
432 * than the old string, then the substitution is easy.
435 memcpy(&lp
->data
[offset
], newStr
, newLen
);
437 memcpy(&lp
->data
[offset
+ newLen
],
438 &lp
->data
[offset
+ oldLen
],
439 lp
->len
- offset
- oldLen
);
447 printLines(num1
, num1
, FALSE
);
456 * The new string is larger, so allocate a new line
457 * structure and use that. Link it in place of
458 * the old line structure.
460 nlp
= xmalloc(sizeof(LINE
) + lp
->len
+ deltaLen
);
462 nlp
->len
= lp
->len
+ deltaLen
;
464 memcpy(nlp
->data
, lp
->data
, offset
);
465 memcpy(&nlp
->data
[offset
], newStr
, newLen
);
466 memcpy(&nlp
->data
[offset
+ newLen
],
467 &lp
->data
[offset
+ oldLen
],
468 lp
->len
- offset
- oldLen
);
470 nlp
->next
= lp
->next
;
471 nlp
->prev
= lp
->prev
;
472 nlp
->prev
->next
= nlp
;
473 nlp
->next
->prev
= nlp
;
487 printLines(num1
, num1
, FALSE
);
496 bb_error_msg("no substitutions found for \"%s\"", oldStr
);
501 * Search a line for the specified string starting at the specified
502 * offset in the line. Returns the offset of the found string, or -1.
504 static int findString(const LINE
*lp
, const char *str
, int len
, int offset
)
507 const char *cp
, *ncp
;
509 cp
= &lp
->data
[offset
];
510 left
= lp
->len
- offset
;
512 while (left
>= len
) {
513 ncp
= memchr(cp
, *str
, left
);
520 if (memcmp(cp
, str
, len
) == 0)
521 return (cp
- lp
->data
);
531 * Add lines which are typed in by the user.
532 * The lines are inserted just before the specified line number.
533 * The lines are terminated by a line containing a single dot (ugly!),
534 * or by an end of file.
536 static void addLines(int num
)
539 char buf
[USERSIZE
+ 1];
543 * -1 on read errors or EOF, or on bare Ctrl-D.
545 * >0 length of input string, including terminating '\n'
547 len
= read_line_input(NULL
, "", buf
, sizeof(buf
), /*timeout*/ -1);
549 /* Previously, ctrl-C was exiting to shell.
550 * Now we exit to ed prompt. Is in important? */
553 if ((buf
[0] == '.') && (buf
[1] == '\n') && (buf
[2] == '\0'))
555 if (!insertLine(num
++, buf
, len
))
562 * Parse a line number argument if it is present. This is a sum
563 * or difference of numbers, '.', '$', 'x, or a search string.
564 * Returns TRUE if successful (whether or not there was a number).
565 * Returns FALSE if there was a parsing error, with a message output.
566 * Whether there was a number is returned indirectly, as is the number.
567 * The character pointer which stopped the scan is also returned.
569 static int getNum(const char **retcp
, smallint
*retHaveNum
, int *retNum
)
572 char *endStr
, str
[USERSIZE
];
574 smallint haveNum
, minus
;
599 if ((*cp
< 'a') || (*cp
> 'z')) {
600 bb_error_msg("bad mark name");
604 num
= marks
[*cp
++ - 'a'];
609 endStr
= strchr(str
, '/');
612 cp
+= (endStr
- str
);
615 num
= searchLines(str
, curNum
, lastNum
);
624 *retHaveNum
= haveNum
;
630 num
= num
* 10 + *cp
++ - '0';
635 value
+= (minus
? -num
: num
);
652 *retHaveNum
= haveNum
;
661 * Read lines from a file at the specified line number.
662 * Returns TRUE if the file was successfully read.
664 static int readLines(const char *file
, int num
)
667 int len
, lineCount
, charCount
;
670 if ((num
< 1) || (num
> lastNum
+ 1)) {
671 bb_error_msg("bad line for read");
677 bb_simple_perror_msg(file
);
687 printf("\"%s\", ", file
);
691 cp
= memchr(bufPtr
, '\n', bufUsed
);
694 len
= (cp
- bufPtr
) + 1;
695 if (!insertLine(num
, bufPtr
, len
)) {
707 if (bufPtr
!= bufBase
) {
708 memcpy(bufBase
, bufPtr
, bufUsed
);
709 bufPtr
= bufBase
+ bufUsed
;
712 if (bufUsed
>= bufSize
) {
713 len
= (bufSize
* 3) / 2;
714 cp
= xrealloc(bufBase
, len
);
716 bufPtr
= bufBase
+ bufUsed
;
720 cc
= safe_read(fd
, bufPtr
, bufSize
- bufUsed
);
727 bb_simple_perror_msg(file
);
733 if (!insertLine(num
, bufPtr
, bufUsed
)) {
738 charCount
+= bufUsed
;
743 printf("%d lines%s, %d chars\n", lineCount
,
744 (bufUsed
? " (incomplete)" : ""), charCount
);
751 * Write the specified lines out to the specified file.
752 * Returns TRUE if successful, or FALSE on an error with a message output.
754 static int writeLines(const char *file
, int num1
, int num2
)
757 int fd
, lineCount
, charCount
;
759 if (bad_nums(num1
, num2
, "write"))
765 fd
= creat(file
, 0666);
767 bb_simple_perror_msg(file
);
771 printf("\"%s\", ", file
);
780 while (num1
++ <= num2
) {
781 if (full_write(fd
, lp
->data
, lp
->len
) != lp
->len
) {
782 bb_simple_perror_msg(file
);
786 charCount
+= lp
->len
;
792 bb_simple_perror_msg(file
);
796 printf("%d lines, %d chars\n", lineCount
, charCount
);
802 * Print lines in a specified range.
803 * The last line printed becomes the current line.
804 * If expandFlag is TRUE, then the line is printed specially to
805 * show magic characters.
807 static int printLines(int num1
, int num2
, int expandFlag
)
813 if (bad_nums(num1
, num2
, "print"))
820 while (num1
<= num2
) {
822 write(STDOUT_FILENO
, lp
->data
, lp
->len
);
829 * Show control characters and characters with the
830 * high bit set specially.
835 if ((count
> 0) && (cp
[count
- 1] == '\n'))
838 while (count
-- > 0) {
839 ch
= (unsigned char) *cp
++;
840 fputc_printable(ch
| PRINTABLE_META
, stdout
);
843 fputs("$\n", stdout
);
854 * Insert a new line with the specified text.
855 * The line is inserted so as to become the specified line,
856 * thus pushing any existing and further lines down one.
857 * The inserted line is also set to become the current line.
858 * Returns TRUE if successful.
860 static int insertLine(int num
, const char *data
, int len
)
864 if ((num
< 1) || (num
> lastNum
+ 1)) {
865 bb_error_msg("inserting at bad line number");
869 newLp
= xmalloc(sizeof(LINE
) + len
- 1);
871 memcpy(newLp
->data
, data
, len
);
879 free((char *) newLp
);
885 newLp
->prev
= lp
->prev
;
886 lp
->prev
->next
= newLp
;
891 return setCurNum(num
);
896 * Delete lines from the given range.
898 static void deleteLines(int num1
, int num2
)
900 LINE
*lp
, *nlp
, *plp
;
903 if (bad_nums(num1
, num2
, "delete"))
910 if ((curNum
>= num1
) && (curNum
<= num2
)) {
919 count
= num2
- num1
+ 1;
924 while (count
-- > 0) {
938 * Search for a line which contains the specified string.
939 * If the string is "", then the previously searched for string
940 * is used. The currently searched for string is saved for future use.
941 * Returns the line number which matches, or 0 if there was no match
942 * with an error printed.
944 static NOINLINE
int searchLines(const char *str
, int num1
, int num2
)
949 if (bad_nums(num1
, num2
, "search"))
953 if (searchString
[0] == '\0') {
954 bb_error_msg("no previous search string");
960 if (str
!= searchString
)
961 strcpy(searchString
, str
);
969 while (num1
<= num2
) {
970 if (findString(lp
, str
, len
, 0) >= 0)
976 bb_error_msg("can't find string \"%s\"", str
);
982 * Return a pointer to the specified line number.
984 static LINE
*findLine(int num
)
989 if ((num
< 1) || (num
> lastNum
)) {
990 bb_error_msg("line number %d does not exist", num
);
996 curLine
= lines
.next
;
1004 if (num
< (curNum
/ 2)) {
1007 } else if (num
> ((curNum
+ lastNum
) / 2)) {
1012 while (lnum
< num
) {
1017 while (lnum
> num
) {
1026 * Set the current line number.
1027 * Returns TRUE if successful.
1029 static int setCurNum(int num
)