ed: fix --help and reorder functions, no code changes
No binary (build failed)
—
(log yok)
| @@ -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; |
| } | |