[git commit] ed: fix --help and reorder functions, no code changes

Denys Vlasenko vda.linux at googlemail.com
Thu Jul 27 09:17:15 UTC 2017


commit: https://git.busybox.net/busybox/commit/?id=5ea50697fd13eb1caeecf3ceb8d03a1e7f578406
branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master

Signed-off-by: Denys Vlasenko <vda.linux at googlemail.com>
---
 editors/ed.c | 1514 ++++++++++++++++++++++++++++------------------------------
 1 file changed, 741 insertions(+), 773 deletions(-)

diff --git a/editors/ed.c b/editors/ed.c
index c594d3d..a2a389c 100644
--- a/editors/ed.c
+++ b/editors/ed.c
@@ -6,7 +6,6 @@
  *
  * The "ed" built-in command (much simplified)
  */
-
 //config:config ED
 //config:	bool "ed (25 kb)"
 //config:	default y
@@ -19,7 +18,7 @@
 
 //applet:IF_ED(APPLET(ed, BB_DIR_BIN, BB_SUID_DROP))
 
-//usage:#define ed_trivial_usage ""
+//usage:#define ed_trivial_usage "[FILE]"
 //usage:#define ed_full_usage ""
 
 #include "libbb.h"
@@ -32,7 +31,6 @@ typedef struct LINE {
 	char data[1];
 } LINE;
 
-
 #define searchString bb_common_bufsiz1
 
 enum {
@@ -71,22 +69,6 @@ struct globals {
 	SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
 } while (0)
 
-
-static void doCommands(void);
-static void subCommand(const char *cmd, int num1, int num2);
-static int getNum(const char **retcp, smallint *retHaveNum, int *retNum);
-static int setCurNum(int num);
-static void addLines(int num);
-static int insertLine(int num, const char *data, int len);
-static void deleteLines(int num1, int num2);
-static int printLines(int num1, int num2, int expandFlag);
-static int writeLines(const char *file, int num1, int num2);
-static int readLines(const char *file, int num);
-static int searchLines(const char *str, int num1, int num2);
-static LINE *findLine(int num);
-static int findString(const LINE *lp, const char * str, int len, int offset);
-
-
 static int bad_nums(int num1, int num2, const char *for_what)
 {
 	if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) {
@@ -96,7 +78,6 @@ static int bad_nums(int num1, int num2, const char *for_what)
 	return 0;
 }
 
-
 static char *skip_blank(const char *cp)
 {
 	while (isblank(*cp))
@@ -104,949 +85,936 @@ static char *skip_blank(const char *cp)
 	return (char *)cp;
 }
 
-
-int ed_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
-int ed_main(int argc UNUSED_PARAM, char **argv)
+/*
+ * Return a pointer to the specified line number.
+ */
+static LINE *findLine(int num)
 {
-	INIT_G();
+	LINE *lp;
+	int lnum;
 
-	bufSize = INITBUF_SIZE;
-	bufBase = xmalloc(bufSize);
-	bufPtr = bufBase;
-	lines.next = &lines;
-	lines.prev = &lines;
+	if ((num < 1) || (num > lastNum)) {
+		bb_error_msg("line number %d does not exist", num);
+		return NULL;
+	}
 
-	if (argv[1]) {
-		fileName = xstrdup(argv[1]);
-		if (!readLines(fileName, 1)) {
-			return EXIT_SUCCESS;
-		}
-		if (lastNum)
-			setCurNum(1);
-		dirty = FALSE;
+	if (curNum <= 0) {
+		curNum = 1;
+		curLine = lines.next;
 	}
 
-	doCommands();
-	return EXIT_SUCCESS;
+	if (num == curNum)
+		return curLine;
+
+	lp = curLine;
+	lnum = curNum;
+	if (num < (curNum / 2)) {
+		lp = lines.next;
+		lnum = 1;
+	} else if (num > ((curNum + lastNum) / 2)) {
+		lp = lines.prev;
+		lnum = lastNum;
+	}
+
+	while (lnum < num) {
+		lp = lp->next;
+		lnum++;
+	}
+
+	while (lnum > num) {
+		lp = lp->prev;
+		lnum--;
+	}
+	return lp;
 }
 
 /*
- * Read commands until we are told to stop.
+ * Search a line for the specified string starting at the specified
+ * offset in the line.  Returns the offset of the found string, or -1.
  */
-static void doCommands(void)
+static int findString(const LINE *lp, const char *str, int len, int offset)
 {
-	const char *cp;
-	char *endbuf, buf[USERSIZE];
-	int len, num1, num2;
-	smallint have1, have2;
+	int left;
+	const char *cp, *ncp;
 
-	while (TRUE) {
-		/* Returns:
-		 * -1 on read errors or EOF, or on bare Ctrl-D.
-		 * 0  on ctrl-C,
-		 * >0 length of input string, including terminating '\n'
-		 */
-		len = read_line_input(NULL, ": ", buf, sizeof(buf), /*timeout*/ -1);
-		if (len <= 0)
-			return;
-		endbuf = &buf[len - 1];
-		while ((endbuf > buf) && isblank(endbuf[-1]))
-			endbuf--;
-		*endbuf = '\0';
+	cp = &lp->data[offset];
+	left = lp->len - offset;
 
-		cp = skip_blank(buf);
-		have1 = FALSE;
-		have2 = FALSE;
+	while (left >= len) {
+		ncp = memchr(cp, *str, left);
+		if (ncp == NULL)
+			return -1;
+		left -= (ncp - cp);
+		if (left < len)
+			return -1;
+		cp = ncp;
+		if (memcmp(cp, str, len) == 0)
+			return (cp - lp->data);
+		cp++;
+		left--;
+	}
 
-		if ((curNum == 0) && (lastNum > 0)) {
-			curNum = 1;
-			curLine = lines.next;
-		}
+	return -1;
+}
 
-		if (!getNum(&cp, &have1, &num1))
-			continue;
+/*
+ * Search for a line which contains the specified string.
+ * If the string is "", then the previously searched for string
+ * is used.  The currently searched for string is saved for future use.
+ * Returns the line number which matches, or 0 if there was no match
+ * with an error printed.
+ */
+static NOINLINE int searchLines(const char *str, int num1, int num2)
+{
+	const LINE *lp;
+	int len;
 
-		cp = skip_blank(cp);
+	if (bad_nums(num1, num2, "search"))
+		return 0;
 
-		if (*cp == ',') {
-			cp++;
-			if (!getNum(&cp, &have2, &num2))
-				continue;
-			if (!have1)
-				num1 = 1;
-			if (!have2)
-				num2 = lastNum;
-			have1 = TRUE;
-			have2 = TRUE;
+	if (*str == '\0') {
+		if (searchString[0] == '\0') {
+			bb_error_msg("no previous search string");
+			return 0;
 		}
-		if (!have1)
-			num1 = curNum;
-		if (!have2)
-			num2 = num1;
+		str = searchString;
+	}
 
-		switch (*cp++) {
-		case 'a':
-			addLines(num1 + 1);
-			break;
+	if (str != searchString)
+		strcpy(searchString, str);
 
-		case 'c':
-			deleteLines(num1, num2);
-			addLines(num1);
-			break;
+	len = strlen(str);
 
-		case 'd':
-			deleteLines(num1, num2);
-			break;
+	lp = findLine(num1);
+	if (lp == NULL)
+		return 0;
 
-		case 'f':
-			if (*cp && !isblank(*cp)) {
-				bb_error_msg("bad file command");
-				break;
-			}
-			cp = skip_blank(cp);
-			if (*cp == '\0') {
-				if (fileName)
-					printf("\"%s\"\n", fileName);
-				else
-					puts("No file name");
-				break;
-			}
-			free(fileName);
-			fileName = xstrdup(cp);
-			break;
+	while (num1 <= num2) {
+		if (findString(lp, str, len, 0) >= 0)
+			return num1;
+		num1++;
+		lp = lp->next;
+	}
 
-		case 'i':
-			addLines(num1);
-			break;
+	bb_error_msg("can't find string \"%s\"", str);
+	return 0;
+}
 
-		case 'k':
-			cp = skip_blank(cp);
-			if ((*cp < 'a') || (*cp > 'z') || cp[1]) {
-				bb_error_msg("bad mark name");
-				break;
-			}
-			marks[*cp - 'a'] = num2;
-			break;
+/*
+ * Parse a line number argument if it is present.  This is a sum
+ * or difference of numbers, '.', '$', 'x, or a search string.
+ * Returns TRUE if successful (whether or not there was a number).
+ * Returns FALSE if there was a parsing error, with a message output.
+ * Whether there was a number is returned indirectly, as is the number.
+ * The character pointer which stopped the scan is also returned.
+ */
+static int getNum(const char **retcp, smallint *retHaveNum, int *retNum)
+{
+	const char *cp;
+	char *endStr, str[USERSIZE];
+	int value, num;
+	smallint haveNum, minus;
 
-		case 'l':
-			printLines(num1, num2, TRUE);
-			break;
+	cp = *retcp;
+	value = 0;
+	haveNum = FALSE;
+	minus = 0;
 
-		case 'p':
-			printLines(num1, num2, FALSE);
-			break;
+	while (TRUE) {
+		cp = skip_blank(cp);
 
-		case 'q':
-			cp = skip_blank(cp);
-			if (have1 || *cp) {
-				bb_error_msg("bad quit command");
+		switch (*cp) {
+			case '.':
+				haveNum = TRUE;
+				num = curNum;
+				cp++;
 				break;
-			}
-			if (!dirty)
-				return;
-			len = read_line_input(NULL, "Really quit? ", buf, 16, /*timeout*/ -1);
-			/* read error/EOF - no way to continue */
-			if (len < 0)
-				return;
-			cp = skip_blank(buf);
-			if ((*cp | 0x20) == 'y') /* Y or y */
-				return;
-			break;
 
-		case 'r':
-			if (*cp && !isblank(*cp)) {
-				bb_error_msg("bad read command");
-				break;
-			}
-			cp = skip_blank(cp);
-			if (*cp == '\0') {
-				bb_error_msg("no file name");
+			case '$':
+				haveNum = TRUE;
+				num = lastNum;
+				cp++;
 				break;
-			}
-			if (!have1)
-				num1 = lastNum;
-			if (readLines(cp, num1 + 1))
-				break;
-			if (fileName == NULL)
-				fileName = xstrdup(cp);
-			break;
 
-		case 's':
-			subCommand(cp, num1, num2);
-			break;
-
-		case 'w':
-			if (*cp && !isblank(*cp)) {
-				bb_error_msg("bad write command");
-				break;
-			}
-			cp = skip_blank(cp);
-			if (!have1) {
-				num1 = 1;
-				num2 = lastNum;
-			}
-			if (*cp == '\0')
-				cp = fileName;
-			if (cp == NULL) {
-				bb_error_msg("no file name specified");
+			case '\'':
+				cp++;
+				if ((*cp < 'a') || (*cp > 'z')) {
+					bb_error_msg("bad mark name");
+					return FALSE;
+				}
+				haveNum = TRUE;
+				num = marks[*cp++ - 'a'];
 				break;
-			}
-			writeLines(cp, num1, num2);
-			break;
 
-		case 'z':
-			switch (*cp) {
-			case '-':
-				printLines(curNum - 21, curNum, FALSE);
-				break;
-			case '.':
-				printLines(curNum - 11, curNum + 10, FALSE);
+			case '/':
+				strcpy(str, ++cp);
+				endStr = strchr(str, '/');
+				if (endStr) {
+					*endStr++ = '\0';
+					cp += (endStr - str);
+				} else
+					cp = "";
+				num = searchLines(str, curNum, lastNum);
+				if (num == 0)
+					return FALSE;
+				haveNum = TRUE;
 				break;
+
 			default:
-				printLines(curNum, curNum + 21, FALSE);
+				if (!isdigit(*cp)) {
+					*retcp = cp;
+					*retHaveNum = haveNum;
+					*retNum = value;
+					return TRUE;
+				}
+				num = 0;
+				while (isdigit(*cp))
+					num = num * 10 + *cp++ - '0';
+				haveNum = TRUE;
 				break;
-			}
-			break;
+		}
 
-		case '.':
-			if (have1) {
-				bb_error_msg("no arguments allowed");
-				break;
-			}
-			printLines(curNum, curNum, FALSE);
-			break;
+		value += (minus ? -num : num);
 
-		case '-':
-			if (setCurNum(curNum - 1))
-				printLines(curNum, curNum, FALSE);
-			break;
+		cp = skip_blank(cp);
 
-		case '=':
-			printf("%d\n", num1);
-			break;
-		case '\0':
-			if (have1) {
-				printLines(num2, num2, FALSE);
+		switch (*cp) {
+			case '-':
+				minus = 1;
+				cp++;
 				break;
-			}
-			if (setCurNum(curNum + 1))
-				printLines(curNum, curNum, FALSE);
-			break;
 
-		default:
-			bb_error_msg("unimplemented command");
-			break;
+			case '+':
+				minus = 0;
+				cp++;
+				break;
+
+			default:
+				*retcp = cp;
+				*retHaveNum = haveNum;
+				*retNum = value;
+				return TRUE;
 		}
 	}
 }
 
-
 /*
- * Do the substitute command.
- * The current line is set to the last substitution done.
+ * Set the current line number.
+ * Returns TRUE if successful.
  */
-static void subCommand(const char *cmd, int num1, int num2)
+static int setCurNum(int num)
 {
-	char *cp, *oldStr, *newStr, buf[USERSIZE];
-	int delim, oldLen, newLen, deltaLen, offset;
-	LINE *lp, *nlp;
-	int globalFlag, printFlag, didSub, needPrint;
-
-	if (bad_nums(num1, num2, "substitute"))
-		return;
+	LINE *lp;
 
-	globalFlag = FALSE;
-	printFlag = FALSE;
-	didSub = FALSE;
-	needPrint = FALSE;
+	lp = findLine(num);
+	if (lp == NULL)
+		return FALSE;
+	curNum = num;
+	curLine = lp;
+	return TRUE;
+}
 
-	/*
-	 * Copy the command so we can modify it.
-	 */
-	strcpy(buf, cmd);
-	cp = buf;
+/*
+ * Insert a new line with the specified text.
+ * The line is inserted so as to become the specified line,
+ * thus pushing any existing and further lines down one.
+ * The inserted line is also set to become the current line.
+ * Returns TRUE if successful.
+ */
+static int insertLine(int num, const char *data, int len)
+{
+	LINE *newLp, *lp;
 
-	if (isblank(*cp) || (*cp == '\0')) {
-		bb_error_msg("bad delimiter for substitute");
-		return;
+	if ((num < 1) || (num > lastNum + 1)) {
+		bb_error_msg("inserting at bad line number");
+		return FALSE;
 	}
 
-	delim = *cp++;
-	oldStr = cp;
+	newLp = xmalloc(sizeof(LINE) + len - 1);
 
-	cp = strchr(cp, delim);
-	if (cp == NULL) {
-		bb_error_msg("missing 2nd delimiter for substitute");
-		return;
-	}
+	memcpy(newLp->data, data, len);
+	newLp->len = len;
 
-	*cp++ = '\0';
+	if (num > lastNum)
+		lp = &lines;
+	else {
+		lp = findLine(num);
+		if (lp == NULL) {
+			free((char *) newLp);
+			return FALSE;
+		}
+	}
 
-	newStr = cp;
-	cp = strchr(cp, delim);
+	newLp->next = lp;
+	newLp->prev = lp->prev;
+	lp->prev->next = newLp;
+	lp->prev = newLp;
 
-	if (cp)
-		*cp++ = '\0';
-	else
-		cp = (char*)"";
+	lastNum++;
+	dirty = TRUE;
+	return setCurNum(num);
+}
 
-	while (*cp) switch (*cp++) {
-		case 'g':
-			globalFlag = TRUE;
-			break;
-		case 'p':
-			printFlag = TRUE;
-			break;
-		default:
-			bb_error_msg("unknown option for substitute");
-			return;
-	}
+/*
+ * Add lines which are typed in by the user.
+ * The lines are inserted just before the specified line number.
+ * The lines are terminated by a line containing a single dot (ugly!),
+ * or by an end of file.
+ */
+static void addLines(int num)
+{
+	int len;
+	char buf[USERSIZE + 1];
 
-	if (*oldStr == '\0') {
-		if (searchString[0] == '\0') {
-			bb_error_msg("no previous search string");
+	while (1) {
+		/* Returns:
+		 * -1 on read errors or EOF, or on bare Ctrl-D.
+		 * 0  on ctrl-C,
+		 * >0 length of input string, including terminating '\n'
+		 */
+		len = read_line_input(NULL, "", buf, sizeof(buf), /*timeout*/ -1);
+		if (len <= 0) {
+			/* Previously, ctrl-C was exiting to shell.
+			 * Now we exit to ed prompt. Is in important? */
 			return;
 		}
-		oldStr = searchString;
+		if ((buf[0] == '.') && (buf[1] == '\n') && (buf[2] == '\0'))
+			return;
+		if (!insertLine(num++, buf, len))
+			return;
 	}
+}
 
-	if (oldStr != searchString)
-		strcpy(searchString, oldStr);
-
-	lp = findLine(num1);
-	if (lp == NULL)
-		return;
+/*
+ * Read lines from a file at the specified line number.
+ * Returns TRUE if the file was successfully read.
+ */
+static int readLines(const char *file, int num)
+{
+	int fd, cc;
+	int len, lineCount, charCount;
+	char *cp;
 
-	oldLen = strlen(oldStr);
-	newLen = strlen(newStr);
-	deltaLen = newLen - oldLen;
-	offset = 0;
-	nlp = NULL;
+	if ((num < 1) || (num > lastNum + 1)) {
+		bb_error_msg("bad line for read");
+		return FALSE;
+	}
 
-	while (num1 <= num2) {
-		offset = findString(lp, oldStr, oldLen, offset);
+	fd = open(file, 0);
+	if (fd < 0) {
+		bb_simple_perror_msg(file);
+		return FALSE;
+	}
 
-		if (offset < 0) {
-			if (needPrint) {
-				printLines(num1, num1, FALSE);
-				needPrint = FALSE;
-			}
-			offset = 0;
-			lp = lp->next;
-			num1++;
-			continue;
-		}
+	bufPtr = bufBase;
+	bufUsed = 0;
+	lineCount = 0;
+	charCount = 0;
+	cc = 0;
 
-		needPrint = printFlag;
-		didSub = TRUE;
-		dirty = TRUE;
+	printf("\"%s\", ", file);
+	fflush_all();
 
-		/*
-		 * If the replacement string is the same size or shorter
-		 * than the old string, then the substitution is easy.
-		 */
-		if (deltaLen <= 0) {
-			memcpy(&lp->data[offset], newStr, newLen);
-			if (deltaLen) {
-				memcpy(&lp->data[offset + newLen],
-					&lp->data[offset + oldLen],
-					lp->len - offset - oldLen);
+	do {
+		cp = memchr(bufPtr, '\n', bufUsed);
 
-				lp->len += deltaLen;
-			}
-			offset += newLen;
-			if (globalFlag)
-				continue;
-			if (needPrint) {
-				printLines(num1, num1, FALSE);
-				needPrint = FALSE;
+		if (cp) {
+			len = (cp - bufPtr) + 1;
+			if (!insertLine(num, bufPtr, len)) {
+				close(fd);
+				return FALSE;
 			}
-			lp = lp->next;
-			num1++;
+			bufPtr += len;
+			bufUsed -= len;
+			charCount += len;
+			lineCount++;
+			num++;
 			continue;
 		}
 
-		/*
-		 * The new string is larger, so allocate a new line
-		 * structure and use that.  Link it in place of
-		 * the old line structure.
-		 */
-		nlp = xmalloc(sizeof(LINE) + lp->len + deltaLen);
-
-		nlp->len = lp->len + deltaLen;
-
-		memcpy(nlp->data, lp->data, offset);
-		memcpy(&nlp->data[offset], newStr, newLen);
-		memcpy(&nlp->data[offset + newLen],
-			&lp->data[offset + oldLen],
-			lp->len - offset - oldLen);
-
-		nlp->next = lp->next;
-		nlp->prev = lp->prev;
-		nlp->prev->next = nlp;
-		nlp->next->prev = nlp;
-
-		if (curLine == lp)
-			curLine = nlp;
+		if (bufPtr != bufBase) {
+			memcpy(bufBase, bufPtr, bufUsed);
+			bufPtr = bufBase + bufUsed;
+		}
 
-		free(lp);
-		lp = nlp;
+		if (bufUsed >= bufSize) {
+			len = (bufSize * 3) / 2;
+			cp = xrealloc(bufBase, len);
+			bufBase = cp;
+			bufPtr = bufBase + bufUsed;
+			bufSize = len;
+		}
 
-		offset += newLen;
+		cc = safe_read(fd, bufPtr, bufSize - bufUsed);
+		bufUsed += cc;
+		bufPtr = bufBase;
+	} while (cc > 0);
 
-		if (globalFlag)
-			continue;
+	if (cc < 0) {
+		bb_simple_perror_msg(file);
+		close(fd);
+		return FALSE;
+	}
 
-		if (needPrint) {
-			printLines(num1, num1, FALSE);
-			needPrint = FALSE;
+	if (bufUsed) {
+		if (!insertLine(num, bufPtr, bufUsed)) {
+			close(fd);
+			return -1;
 		}
-
-		lp = lp->next;
-		num1++;
+		lineCount++;
+		charCount += bufUsed;
 	}
 
-	if (!didSub)
-		bb_error_msg("no substitutions found for \"%s\"", oldStr);
-}
+	close(fd);
+
+	printf("%d lines%s, %d chars\n", lineCount,
+		(bufUsed ? " (incomplete)" : ""), charCount);
 
+	return TRUE;
+}
 
 /*
- * Search a line for the specified string starting at the specified
- * offset in the line.  Returns the offset of the found string, or -1.
+ * Write the specified lines out to the specified file.
+ * Returns TRUE if successful, or FALSE on an error with a message output.
  */
-static int findString(const LINE *lp, const char *str, int len, int offset)
+static int writeLines(const char *file, int num1, int num2)
 {
-	int left;
-	const char *cp, *ncp;
+	LINE *lp;
+	int fd, lineCount, charCount;
 
-	cp = &lp->data[offset];
-	left = lp->len - offset;
+	if (bad_nums(num1, num2, "write"))
+		return FALSE;
 
-	while (left >= len) {
-		ncp = memchr(cp, *str, left);
-		if (ncp == NULL)
-			return -1;
-		left -= (ncp - cp);
-		if (left < len)
-			return -1;
-		cp = ncp;
-		if (memcmp(cp, str, len) == 0)
-			return (cp - lp->data);
-		cp++;
-		left--;
-	}
+	lineCount = 0;
+	charCount = 0;
 
-	return -1;
-}
+	fd = creat(file, 0666);
+	if (fd < 0) {
+		bb_simple_perror_msg(file);
+		return FALSE;
+	}
 
+	printf("\"%s\", ", file);
+	fflush_all();
 
-/*
- * Add lines which are typed in by the user.
- * The lines are inserted just before the specified line number.
- * The lines are terminated by a line containing a single dot (ugly!),
- * or by an end of file.
- */
-static void addLines(int num)
-{
-	int len;
-	char buf[USERSIZE + 1];
+	lp = findLine(num1);
+	if (lp == NULL) {
+		close(fd);
+		return FALSE;
+	}
 
-	while (1) {
-		/* Returns:
-		 * -1 on read errors or EOF, or on bare Ctrl-D.
-		 * 0  on ctrl-C,
-		 * >0 length of input string, including terminating '\n'
-		 */
-		len = read_line_input(NULL, "", buf, sizeof(buf), /*timeout*/ -1);
-		if (len <= 0) {
-			/* Previously, ctrl-C was exiting to shell.
-			 * Now we exit to ed prompt. Is in important? */
-			return;
+	while (num1++ <= num2) {
+		if (full_write(fd, lp->data, lp->len) != lp->len) {
+			bb_simple_perror_msg(file);
+			close(fd);
+			return FALSE;
 		}
-		if ((buf[0] == '.') && (buf[1] == '\n') && (buf[2] == '\0'))
-			return;
-		if (!insertLine(num++, buf, len))
-			return;
+		charCount += lp->len;
+		lineCount++;
+		lp = lp->next;
+	}
+
+	if (close(fd) < 0) {
+		bb_simple_perror_msg(file);
+		return FALSE;
 	}
-}
 
+	printf("%d lines, %d chars\n", lineCount, charCount);
+	return TRUE;
+}
 
 /*
- * Parse a line number argument if it is present.  This is a sum
- * or difference of numbers, '.', '$', 'x, or a search string.
- * Returns TRUE if successful (whether or not there was a number).
- * Returns FALSE if there was a parsing error, with a message output.
- * Whether there was a number is returned indirectly, as is the number.
- * The character pointer which stopped the scan is also returned.
+ * Print lines in a specified range.
+ * The last line printed becomes the current line.
+ * If expandFlag is TRUE, then the line is printed specially to
+ * show magic characters.
  */
-static int getNum(const char **retcp, smallint *retHaveNum, int *retNum)
+static int printLines(int num1, int num2, int expandFlag)
 {
+	const LINE *lp;
 	const char *cp;
-	char *endStr, str[USERSIZE];
-	int value, num;
-	smallint haveNum, minus;
+	int ch, count;
 
-	cp = *retcp;
-	value = 0;
-	haveNum = FALSE;
-	minus = 0;
+	if (bad_nums(num1, num2, "print"))
+		return FALSE;
 
-	while (TRUE) {
-		cp = skip_blank(cp);
+	lp = findLine(num1);
+	if (lp == NULL)
+		return FALSE;
 
-		switch (*cp) {
-			case '.':
-				haveNum = TRUE;
-				num = curNum;
-				cp++;
-				break;
+	while (num1 <= num2) {
+		if (!expandFlag) {
+			write(STDOUT_FILENO, lp->data, lp->len);
+			setCurNum(num1++);
+			lp = lp->next;
+			continue;
+		}
 
-			case '$':
-				haveNum = TRUE;
-				num = lastNum;
-				cp++;
-				break;
-
-			case '\'':
-				cp++;
-				if ((*cp < 'a') || (*cp > 'z')) {
-					bb_error_msg("bad mark name");
-					return FALSE;
-				}
-				haveNum = TRUE;
-				num = marks[*cp++ - 'a'];
-				break;
+		/*
+		 * Show control characters and characters with the
+		 * high bit set specially.
+		 */
+		cp = lp->data;
+		count = lp->len;
 
-			case '/':
-				strcpy(str, ++cp);
-				endStr = strchr(str, '/');
-				if (endStr) {
-					*endStr++ = '\0';
-					cp += (endStr - str);
-				} else
-					cp = "";
-				num = searchLines(str, curNum, lastNum);
-				if (num == 0)
-					return FALSE;
-				haveNum = TRUE;
-				break;
+		if ((count > 0) && (cp[count - 1] == '\n'))
+			count--;
 
-			default:
-				if (!isdigit(*cp)) {
-					*retcp = cp;
-					*retHaveNum = haveNum;
-					*retNum = value;
-					return TRUE;
-				}
-				num = 0;
-				while (isdigit(*cp))
-					num = num * 10 + *cp++ - '0';
-				haveNum = TRUE;
-				break;
+		while (count-- > 0) {
+			ch = (unsigned char) *cp++;
+			fputc_printable(ch | PRINTABLE_META, stdout);
 		}
 
-		value += (minus ? -num : num);
-
-		cp = skip_blank(cp);
-
-		switch (*cp) {
-			case '-':
-				minus = 1;
-				cp++;
-				break;
-
-			case '+':
-				minus = 0;
-				cp++;
-				break;
+		fputs("$\n", stdout);
 
-			default:
-				*retcp = cp;
-				*retHaveNum = haveNum;
-				*retNum = value;
-				return TRUE;
-		}
+		setCurNum(num1++);
+		lp = lp->next;
 	}
-}
 
+	return TRUE;
+}
 
 /*
- * Read lines from a file at the specified line number.
- * Returns TRUE if the file was successfully read.
+ * Delete lines from the given range.
  */
-static int readLines(const char *file, int num)
+static void deleteLines(int num1, int num2)
 {
-	int fd, cc;
-	int len, lineCount, charCount;
-	char *cp;
+	LINE *lp, *nlp, *plp;
+	int count;
 
-	if ((num < 1) || (num > lastNum + 1)) {
-		bb_error_msg("bad line for read");
-		return FALSE;
-	}
+	if (bad_nums(num1, num2, "delete"))
+		return;
 
-	fd = open(file, 0);
-	if (fd < 0) {
-		bb_simple_perror_msg(file);
-		return FALSE;
-	}
+	lp = findLine(num1);
+	if (lp == NULL)
+		return;
 
-	bufPtr = bufBase;
-	bufUsed = 0;
-	lineCount = 0;
-	charCount = 0;
-	cc = 0;
+	if ((curNum >= num1) && (curNum <= num2)) {
+		if (num2 < lastNum)
+			setCurNum(num2 + 1);
+		else if (num1 > 1)
+			setCurNum(num1 - 1);
+		else
+			curNum = 0;
+	}
 
-	printf("\"%s\", ", file);
-	fflush_all();
+	count = num2 - num1 + 1;
+	if (curNum > num2)
+		curNum -= count;
+	lastNum -= count;
 
-	do {
-		cp = memchr(bufPtr, '\n', bufUsed);
+	while (count-- > 0) {
+		nlp = lp->next;
+		plp = lp->prev;
+		plp->next = nlp;
+		nlp->prev = plp;
+		free(lp);
+		lp = nlp;
+	}
 
-		if (cp) {
-			len = (cp - bufPtr) + 1;
-			if (!insertLine(num, bufPtr, len)) {
-				close(fd);
-				return FALSE;
-			}
-			bufPtr += len;
-			bufUsed -= len;
-			charCount += len;
-			lineCount++;
-			num++;
-			continue;
-		}
+	dirty = TRUE;
+}
 
-		if (bufPtr != bufBase) {
-			memcpy(bufBase, bufPtr, bufUsed);
-			bufPtr = bufBase + bufUsed;
-		}
+/*
+ * Do the substitute command.
+ * The current line is set to the last substitution done.
+ */
+static void subCommand(const char *cmd, int num1, int num2)
+{
+	char *cp, *oldStr, *newStr, buf[USERSIZE];
+	int delim, oldLen, newLen, deltaLen, offset;
+	LINE *lp, *nlp;
+	int globalFlag, printFlag, didSub, needPrint;
 
-		if (bufUsed >= bufSize) {
-			len = (bufSize * 3) / 2;
-			cp = xrealloc(bufBase, len);
-			bufBase = cp;
-			bufPtr = bufBase + bufUsed;
-			bufSize = len;
-		}
+	if (bad_nums(num1, num2, "substitute"))
+		return;
 
-		cc = safe_read(fd, bufPtr, bufSize - bufUsed);
-		bufUsed += cc;
-		bufPtr = bufBase;
-	} while (cc > 0);
+	globalFlag = FALSE;
+	printFlag = FALSE;
+	didSub = FALSE;
+	needPrint = FALSE;
 
-	if (cc < 0) {
-		bb_simple_perror_msg(file);
-		close(fd);
-		return FALSE;
-	}
+	/*
+	 * Copy the command so we can modify it.
+	 */
+	strcpy(buf, cmd);
+	cp = buf;
 
-	if (bufUsed) {
-		if (!insertLine(num, bufPtr, bufUsed)) {
-			close(fd);
-			return -1;
-		}
-		lineCount++;
-		charCount += bufUsed;
+	if (isblank(*cp) || (*cp == '\0')) {
+		bb_error_msg("bad delimiter for substitute");
+		return;
 	}
 
-	close(fd);
-
-	printf("%d lines%s, %d chars\n", lineCount,
-		(bufUsed ? " (incomplete)" : ""), charCount);
+	delim = *cp++;
+	oldStr = cp;
 
-	return TRUE;
-}
+	cp = strchr(cp, delim);
+	if (cp == NULL) {
+		bb_error_msg("missing 2nd delimiter for substitute");
+		return;
+	}
 
+	*cp++ = '\0';
 
-/*
- * Write the specified lines out to the specified file.
- * Returns TRUE if successful, or FALSE on an error with a message output.
- */
-static int writeLines(const char *file, int num1, int num2)
-{
-	LINE *lp;
-	int fd, lineCount, charCount;
+	newStr = cp;
+	cp = strchr(cp, delim);
 
-	if (bad_nums(num1, num2, "write"))
-		return FALSE;
+	if (cp)
+		*cp++ = '\0';
+	else
+		cp = (char*)"";
 
-	lineCount = 0;
-	charCount = 0;
+	while (*cp) switch (*cp++) {
+		case 'g':
+			globalFlag = TRUE;
+			break;
+		case 'p':
+			printFlag = TRUE;
+			break;
+		default:
+			bb_error_msg("unknown option for substitute");
+			return;
+	}
 
-	fd = creat(file, 0666);
-	if (fd < 0) {
-		bb_simple_perror_msg(file);
-		return FALSE;
+	if (*oldStr == '\0') {
+		if (searchString[0] == '\0') {
+			bb_error_msg("no previous search string");
+			return;
+		}
+		oldStr = searchString;
 	}
 
-	printf("\"%s\", ", file);
-	fflush_all();
+	if (oldStr != searchString)
+		strcpy(searchString, oldStr);
 
 	lp = findLine(num1);
-	if (lp == NULL) {
-		close(fd);
-		return FALSE;
-	}
+	if (lp == NULL)
+		return;
 
-	while (num1++ <= num2) {
-		if (full_write(fd, lp->data, lp->len) != lp->len) {
-			bb_simple_perror_msg(file);
-			close(fd);
-			return FALSE;
-		}
-		charCount += lp->len;
-		lineCount++;
-		lp = lp->next;
-	}
-
-	if (close(fd) < 0) {
-		bb_simple_perror_msg(file);
-		return FALSE;
-	}
-
-	printf("%d lines, %d chars\n", lineCount, charCount);
-	return TRUE;
-}
-
-
-/*
- * Print lines in a specified range.
- * The last line printed becomes the current line.
- * If expandFlag is TRUE, then the line is printed specially to
- * show magic characters.
- */
-static int printLines(int num1, int num2, int expandFlag)
-{
-	const LINE *lp;
-	const char *cp;
-	int ch, count;
-
-	if (bad_nums(num1, num2, "print"))
-		return FALSE;
-
-	lp = findLine(num1);
-	if (lp == NULL)
-		return FALSE;
+	oldLen = strlen(oldStr);
+	newLen = strlen(newStr);
+	deltaLen = newLen - oldLen;
+	offset = 0;
+	nlp = NULL;
 
 	while (num1 <= num2) {
-		if (!expandFlag) {
-			write(STDOUT_FILENO, lp->data, lp->len);
-			setCurNum(num1++);
+		offset = findString(lp, oldStr, oldLen, offset);
+
+		if (offset < 0) {
+			if (needPrint) {
+				printLines(num1, num1, FALSE);
+				needPrint = FALSE;
+			}
+			offset = 0;
 			lp = lp->next;
+			num1++;
 			continue;
 		}
 
+		needPrint = printFlag;
+		didSub = TRUE;
+		dirty = TRUE;
+
 		/*
-		 * Show control characters and characters with the
-		 * high bit set specially.
+		 * If the replacement string is the same size or shorter
+		 * than the old string, then the substitution is easy.
 		 */
-		cp = lp->data;
-		count = lp->len;
-
-		if ((count > 0) && (cp[count - 1] == '\n'))
-			count--;
+		if (deltaLen <= 0) {
+			memcpy(&lp->data[offset], newStr, newLen);
+			if (deltaLen) {
+				memcpy(&lp->data[offset + newLen],
+					&lp->data[offset + oldLen],
+					lp->len - offset - oldLen);
 
-		while (count-- > 0) {
-			ch = (unsigned char) *cp++;
-			fputc_printable(ch | PRINTABLE_META, stdout);
+				lp->len += deltaLen;
+			}
+			offset += newLen;
+			if (globalFlag)
+				continue;
+			if (needPrint) {
+				printLines(num1, num1, FALSE);
+				needPrint = FALSE;
+			}
+			lp = lp->next;
+			num1++;
+			continue;
 		}
 
-		fputs("$\n", stdout);
+		/*
+		 * The new string is larger, so allocate a new line
+		 * structure and use that.  Link it in place of
+		 * the old line structure.
+		 */
+		nlp = xmalloc(sizeof(LINE) + lp->len + deltaLen);
 
-		setCurNum(num1++);
-		lp = lp->next;
-	}
+		nlp->len = lp->len + deltaLen;
 
-	return TRUE;
-}
+		memcpy(nlp->data, lp->data, offset);
+		memcpy(&nlp->data[offset], newStr, newLen);
+		memcpy(&nlp->data[offset + newLen],
+			&lp->data[offset + oldLen],
+			lp->len - offset - oldLen);
 
+		nlp->next = lp->next;
+		nlp->prev = lp->prev;
+		nlp->prev->next = nlp;
+		nlp->next->prev = nlp;
 
-/*
- * Insert a new line with the specified text.
- * The line is inserted so as to become the specified line,
- * thus pushing any existing and further lines down one.
- * The inserted line is also set to become the current line.
- * Returns TRUE if successful.
- */
-static int insertLine(int num, const char *data, int len)
-{
-	LINE *newLp, *lp;
+		if (curLine == lp)
+			curLine = nlp;
 
-	if ((num < 1) || (num > lastNum + 1)) {
-		bb_error_msg("inserting at bad line number");
-		return FALSE;
-	}
+		free(lp);
+		lp = nlp;
 
-	newLp = xmalloc(sizeof(LINE) + len - 1);
+		offset += newLen;
 
-	memcpy(newLp->data, data, len);
-	newLp->len = len;
+		if (globalFlag)
+			continue;
 
-	if (num > lastNum)
-		lp = &lines;
-	else {
-		lp = findLine(num);
-		if (lp == NULL) {
-			free((char *) newLp);
-			return FALSE;
+		if (needPrint) {
+			printLines(num1, num1, FALSE);
+			needPrint = FALSE;
 		}
-	}
 
-	newLp->next = lp;
-	newLp->prev = lp->prev;
-	lp->prev->next = newLp;
-	lp->prev = newLp;
+		lp = lp->next;
+		num1++;
+	}
 
-	lastNum++;
-	dirty = TRUE;
-	return setCurNum(num);
+	if (!didSub)
+		bb_error_msg("no substitutions found for \"%s\"", oldStr);
 }
 
-
 /*
- * Delete lines from the given range.
+ * Read commands until we are told to stop.
  */
-static void deleteLines(int num1, int num2)
+static void doCommands(void)
 {
-	LINE *lp, *nlp, *plp;
-	int count;
+	const char *cp;
+	char *endbuf, buf[USERSIZE];
+	int len, num1, num2;
+	smallint have1, have2;
 
-	if (bad_nums(num1, num2, "delete"))
-		return;
+	while (TRUE) {
+		/* Returns:
+		 * -1 on read errors or EOF, or on bare Ctrl-D.
+		 * 0  on ctrl-C,
+		 * >0 length of input string, including terminating '\n'
+		 */
+		len = read_line_input(NULL, ": ", buf, sizeof(buf), /*timeout*/ -1);
+		if (len <= 0)
+			return;
+		endbuf = &buf[len - 1];
+		while ((endbuf > buf) && isblank(endbuf[-1]))
+			endbuf--;
+		*endbuf = '\0';
 
-	lp = findLine(num1);
-	if (lp == NULL)
-		return;
+		cp = skip_blank(buf);
+		have1 = FALSE;
+		have2 = FALSE;
 
-	if ((curNum >= num1) && (curNum <= num2)) {
-		if (num2 < lastNum)
-			setCurNum(num2 + 1);
-		else if (num1 > 1)
-			setCurNum(num1 - 1);
-		else
-			curNum = 0;
-	}
+		if ((curNum == 0) && (lastNum > 0)) {
+			curNum = 1;
+			curLine = lines.next;
+		}
 
-	count = num2 - num1 + 1;
-	if (curNum > num2)
-		curNum -= count;
-	lastNum -= count;
+		if (!getNum(&cp, &have1, &num1))
+			continue;
 
-	while (count-- > 0) {
-		nlp = lp->next;
-		plp = lp->prev;
-		plp->next = nlp;
-		nlp->prev = plp;
-		free(lp);
-		lp = nlp;
-	}
+		cp = skip_blank(cp);
 
-	dirty = TRUE;
-}
+		if (*cp == ',') {
+			cp++;
+			if (!getNum(&cp, &have2, &num2))
+				continue;
+			if (!have1)
+				num1 = 1;
+			if (!have2)
+				num2 = lastNum;
+			have1 = TRUE;
+			have2 = TRUE;
+		}
+		if (!have1)
+			num1 = curNum;
+		if (!have2)
+			num2 = num1;
 
+		switch (*cp++) {
+		case 'a':
+			addLines(num1 + 1);
+			break;
 
-/*
- * Search for a line which contains the specified string.
- * If the string is "", then the previously searched for string
- * is used.  The currently searched for string is saved for future use.
- * Returns the line number which matches, or 0 if there was no match
- * with an error printed.
- */
-static NOINLINE int searchLines(const char *str, int num1, int num2)
-{
-	const LINE *lp;
-	int len;
+		case 'c':
+			deleteLines(num1, num2);
+			addLines(num1);
+			break;
 
-	if (bad_nums(num1, num2, "search"))
-		return 0;
+		case 'd':
+			deleteLines(num1, num2);
+			break;
 
-	if (*str == '\0') {
-		if (searchString[0] == '\0') {
-			bb_error_msg("no previous search string");
-			return 0;
-		}
-		str = searchString;
-	}
+		case 'f':
+			if (*cp && !isblank(*cp)) {
+				bb_error_msg("bad file command");
+				break;
+			}
+			cp = skip_blank(cp);
+			if (*cp == '\0') {
+				if (fileName)
+					printf("\"%s\"\n", fileName);
+				else
+					puts("No file name");
+				break;
+			}
+			free(fileName);
+			fileName = xstrdup(cp);
+			break;
 
-	if (str != searchString)
-		strcpy(searchString, str);
+		case 'i':
+			addLines(num1);
+			break;
 
-	len = strlen(str);
+		case 'k':
+			cp = skip_blank(cp);
+			if ((*cp < 'a') || (*cp > 'z') || cp[1]) {
+				bb_error_msg("bad mark name");
+				break;
+			}
+			marks[*cp - 'a'] = num2;
+			break;
 
-	lp = findLine(num1);
-	if (lp == NULL)
-		return 0;
+		case 'l':
+			printLines(num1, num2, TRUE);
+			break;
 
-	while (num1 <= num2) {
-		if (findString(lp, str, len, 0) >= 0)
-			return num1;
-		num1++;
-		lp = lp->next;
-	}
+		case 'p':
+			printLines(num1, num2, FALSE);
+			break;
 
-	bb_error_msg("can't find string \"%s\"", str);
-	return 0;
-}
+		case 'q':
+			cp = skip_blank(cp);
+			if (have1 || *cp) {
+				bb_error_msg("bad quit command");
+				break;
+			}
+			if (!dirty)
+				return;
+			len = read_line_input(NULL, "Really quit? ", buf, 16, /*timeout*/ -1);
+			/* read error/EOF - no way to continue */
+			if (len < 0)
+				return;
+			cp = skip_blank(buf);
+			if ((*cp | 0x20) == 'y') /* Y or y */
+				return;
+			break;
 
+		case 'r':
+			if (*cp && !isblank(*cp)) {
+				bb_error_msg("bad read command");
+				break;
+			}
+			cp = skip_blank(cp);
+			if (*cp == '\0') {
+				bb_error_msg("no file name");
+				break;
+			}
+			if (!have1)
+				num1 = lastNum;
+			if (readLines(cp, num1 + 1))
+				break;
+			if (fileName == NULL)
+				fileName = xstrdup(cp);
+			break;
 
-/*
- * Return a pointer to the specified line number.
- */
-static LINE *findLine(int num)
-{
-	LINE *lp;
-	int lnum;
+		case 's':
+			subCommand(cp, num1, num2);
+			break;
 
-	if ((num < 1) || (num > lastNum)) {
-		bb_error_msg("line number %d does not exist", num);
-		return NULL;
-	}
+		case 'w':
+			if (*cp && !isblank(*cp)) {
+				bb_error_msg("bad write command");
+				break;
+			}
+			cp = skip_blank(cp);
+			if (!have1) {
+				num1 = 1;
+				num2 = lastNum;
+			}
+			if (*cp == '\0')
+				cp = fileName;
+			if (cp == NULL) {
+				bb_error_msg("no file name specified");
+				break;
+			}
+			writeLines(cp, num1, num2);
+			break;
 
-	if (curNum <= 0) {
-		curNum = 1;
-		curLine = lines.next;
-	}
+		case 'z':
+			switch (*cp) {
+			case '-':
+				printLines(curNum - 21, curNum, FALSE);
+				break;
+			case '.':
+				printLines(curNum - 11, curNum + 10, FALSE);
+				break;
+			default:
+				printLines(curNum, curNum + 21, FALSE);
+				break;
+			}
+			break;
 
-	if (num == curNum)
-		return curLine;
+		case '.':
+			if (have1) {
+				bb_error_msg("no arguments allowed");
+				break;
+			}
+			printLines(curNum, curNum, FALSE);
+			break;
 
-	lp = curLine;
-	lnum = curNum;
-	if (num < (curNum / 2)) {
-		lp = lines.next;
-		lnum = 1;
-	} else if (num > ((curNum + lastNum) / 2)) {
-		lp = lines.prev;
-		lnum = lastNum;
-	}
+		case '-':
+			if (setCurNum(curNum - 1))
+				printLines(curNum, curNum, FALSE);
+			break;
 
-	while (lnum < num) {
-		lp = lp->next;
-		lnum++;
-	}
+		case '=':
+			printf("%d\n", num1);
+			break;
+		case '\0':
+			if (have1) {
+				printLines(num2, num2, FALSE);
+				break;
+			}
+			if (setCurNum(curNum + 1))
+				printLines(curNum, curNum, FALSE);
+			break;
 
-	while (lnum > num) {
-		lp = lp->prev;
-		lnum--;
+		default:
+			bb_error_msg("unimplemented command");
+			break;
+		}
 	}
-	return lp;
 }
 
-
-/*
- * Set the current line number.
- * Returns TRUE if successful.
- */
-static int setCurNum(int num)
+int ed_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int ed_main(int argc UNUSED_PARAM, char **argv)
 {
-	LINE *lp;
+	INIT_G();
 
-	lp = findLine(num);
-	if (lp == NULL)
-		return FALSE;
-	curNum = num;
-	curLine = lp;
-	return TRUE;
+	bufSize = INITBUF_SIZE;
+	bufBase = xmalloc(bufSize);
+	bufPtr = bufBase;
+	lines.next = &lines;
+	lines.prev = &lines;
+
+	if (argv[1]) {
+		fileName = xstrdup(argv[1]);
+		if (!readLines(fileName, 1)) {
+			return EXIT_SUCCESS;
+		}
+		if (lastNum)
+			setCurNum(1);
+		dirty = FALSE;
+	}
+
+	doCommands();
+	return EXIT_SUCCESS;
 }


More information about the busybox-cvs mailing list