2 * Copyright (c) 2014 by David I. Bell
3 * Permission is granted to use, distribute, or modify this source,
4 * provided that this copyright notice remains intact.
6 * The "ed" built-in command (much simplified)
11 #define USERSIZE 1024 /* max line length typed in by user */
12 #define INITBUF_SIZE 1024 /* initial buffer size */
18 typedef struct LINE LINE;
30 static LINE * curLine;
35 static char * fileName;
36 static char searchString[USERSIZE];
38 static char * bufBase;
44 static void doCommands(void);
45 static void subCommand(const char * cmd, NUM num1, NUM num2);
46 static BOOL getNum(const char ** retcp, BOOL * retHaveNum, NUM * retNum);
47 static BOOL setCurNum(NUM num);
48 static BOOL initEdit(void);
49 static void termEdit(void);
50 static void addLines(NUM num);
51 static BOOL insertLine(NUM num, const char * data, LEN len);
52 static BOOL deleteLines(NUM num1, NUM num2);
53 static BOOL printLines(NUM num1, NUM num2, BOOL expandFlag);
54 static BOOL writeLines(const char * file, NUM num1, NUM num2);
55 static BOOL readLines(const char * file, NUM num);
56 static NUM searchLines(const char * str, NUM num1, NUM num2);
57 static LINE * findLine(NUM num);
60 (const LINE * lp, const char * str, LEN len, LEN offset);
64 do_ed(int argc, const char ** argv)
71 fileName = strdup(argv[1]);
75 fprintf(stderr, "No memory\n");
81 if (!readLines(fileName, 1))
102 * Read commands until we are told to stop.
123 if (fgets(buf, sizeof(buf), stdin) == NULL)
131 endbuf = &buf[len - 1];
135 fprintf(stderr, "Command line too long\n");
141 while ((len != EOF) && (len != '\n'));
146 while ((endbuf > buf) && isBlank(endbuf[-1]))
159 if ((curNum == 0) && (lastNum > 0))
162 curLine = lines.next;
165 if (!getNum(&cp, &have1, &num1))
175 if (!getNum(&cp, &have2, &num2))
201 deleteLines(num1, num2);
206 deleteLines(num1, num2);
210 if (*cp && !isBlank(*cp))
212 fprintf(stderr, "Bad file command\n");
222 printf("\"%s\"\n", fileName);
224 printf("No file name\n");
229 newname = strdup(cp);
233 fprintf(stderr, "No memory for file name\n");
251 if ((*cp < 'a') || (*cp > 'a') || cp[1])
253 fprintf(stderr, "Bad mark name\n");
257 marks[*cp - 'a'] = num2;
261 printLines(num1, num2, TRUE);
265 printLines(num1, num2, FALSE);
274 fprintf(stderr, "Bad quit command\n");
281 printf("Really quit? ");
286 if (fgets(buf, sizeof(buf), stdin) == NULL)
294 if ((*cp == 'y') || (*cp == 'Y'))
300 if (*cp && !isBlank(*cp))
302 fprintf(stderr, "Bad read command\n");
311 fprintf(stderr, "No file name\n");
318 if (readLines(cp, num1 + 1))
321 if (fileName == NULL)
322 fileName = strdup(cp);
327 subCommand(cp, num1, num2);
331 if (*cp && !isBlank(*cp))
333 fprintf(stderr, "Bad write command\n");
350 fprintf(stderr, "No file name specified\n");
354 writeLines(cp, num1, num2);
361 printLines(curNum-21, curNum, FALSE);
364 printLines(curNum-11, curNum+10, FALSE);
367 printLines(curNum, curNum+21, FALSE);
375 fprintf(stderr, "No arguments allowed\n");
379 printLines(curNum, curNum, FALSE);
383 if (setCurNum(curNum - 1))
384 printLines(curNum, curNum, FALSE);
389 printf("%d\n", num1);
395 printLines(num2, num2, FALSE);
399 if (setCurNum(curNum + 1))
400 printLines(curNum, curNum, FALSE);
405 fprintf(stderr, "Unimplemented command\n");
413 * Do the substitute command.
414 * The current line is set to the last substitution done.
417 subCommand(const char * cmd, NUM num1, NUM num2)
435 if ((num1 < 1) || (num2 > lastNum) || (num1 > num2))
437 fprintf(stderr, "Bad line range for substitute\n");
448 * Copy the command so we can modify it.
453 if (isBlank(*cp) || (*cp == '\0'))
455 fprintf(stderr, "Bad delimiter for substitute\n");
463 cp = strchr(cp, delim);
467 fprintf(stderr, "Missing 2nd delimiter for substitute\n");
475 cp = strchr(cp, delim);
482 while (*cp) switch (*cp++)
493 fprintf(stderr, "Unknown option for substitute\n");
500 if (searchString[0] == '\0')
502 fprintf(stderr, "No previous search string\n");
507 oldStr = searchString;
510 if (oldStr != searchString)
511 strcpy(searchString, oldStr);
518 oldLen = strlen(oldStr);
519 newLen = strlen(newStr);
520 deltaLen = newLen - oldLen;
526 offset = findString(lp, oldStr, oldLen, offset);
532 printLines(num1, num1, FALSE);
543 needPrint = printFlag;
548 * If the replacement string is the same size or shorter
549 * than the old string, then the substitution is easy.
553 memcpy(&lp->data[offset], newStr, newLen);
557 memcpy(&lp->data[offset + newLen],
558 &lp->data[offset + oldLen],
559 lp->len - offset - oldLen);
571 printLines(num1, num1, FALSE);
582 * The new string is larger, so allocate a new line
583 * structure and use that. Link it in in place of
584 * the old line structure.
586 nlp = (LINE *) malloc(sizeof(LINE) + lp->len + deltaLen);
590 fprintf(stderr, "Cannot get memory for line\n");
595 nlp->len = lp->len + deltaLen;
597 memcpy(nlp->data, lp->data, offset);
599 memcpy(&nlp->data[offset], newStr, newLen);
601 memcpy(&nlp->data[offset + newLen],
602 &lp->data[offset + oldLen],
603 lp->len - offset - oldLen);
605 nlp->next = lp->next;
606 nlp->prev = lp->prev;
607 nlp->prev->next = nlp;
608 nlp->next->prev = nlp;
623 printLines(num1, num1, FALSE);
632 fprintf(stderr, "No substitutions found for \"%s\"\n", oldStr);
637 * Search a line for the specified string starting at the specified
638 * offset in the line. Returns the offset of the found string, or -1.
641 findString( const LINE * lp, const char * str, LEN len, LEN offset)
647 cp = &lp->data[offset];
648 left = lp->len - offset;
652 ncp = memchr(cp, *str, left);
664 if (memcmp(cp, str, len) == 0)
665 return (cp - lp->data);
676 * Add lines which are typed in by the user.
677 * The lines are inserted just before the specified line number.
678 * The lines are terminated by a line containing a single dot (ugly!),
679 * or by an end of file.
685 char buf[USERSIZE + 1];
687 while (fgets(buf, sizeof(buf), stdin))
689 if ((buf[0] == '.') && (buf[1] == '\n') && (buf[2] == '\0'))
697 if (buf[len - 1] != '\n')
699 fprintf(stderr, "Line too long\n");
705 while ((len != EOF) && (len != '\n'));
710 if (!insertLine(num++, buf, len))
717 * Parse a line number argument if it is present. This is a sum
718 * or difference of numbers, '.', '$', 'x, or a search string.
719 * Returns TRUE if successful (whether or not there was a number).
720 * Returns FALSE if there was a parsing error, with a message output.
721 * Whether there was a number is returned indirectly, as is the number.
722 * The character pointer which stopped the scan is also returned.
725 getNum(const char ** retcp, BOOL * retHaveNum, NUM * retNum)
762 if ((*cp < 'a') || (*cp > 'z'))
764 fprintf(stderr, "Bad mark name\n");
770 num = marks[*cp++ - 'a'];
775 endStr = strchr(str, '/');
780 cp += (endStr - str);
785 num = searchLines(str, curNum, lastNum);
797 *retHaveNum = haveNum;
805 while (isDecimal(*cp))
806 num = num * 10 + *cp++ - '0';
831 *retHaveNum = haveNum;
841 * Initialize everything for editing.
848 bufSize = INITBUF_SIZE;
849 bufBase = malloc(bufSize);
853 fprintf(stderr, "No memory for buffer\n");
869 searchString[0] = '\0';
871 for (i = 0; i < 26; i++)
897 searchString[0] = '\0';
900 deleteLines(1, lastNum);
909 * Read lines from a file at the specified line number.
910 * Returns TRUE if the file was successfully read.
913 readLines(const char * file, NUM num)
922 if ((num < 1) || (num > lastNum + 1))
924 fprintf(stderr, "Bad line for read\n");
944 printf("\"%s\", ", file);
951 printf("INTERRUPTED, ");
956 cp = memchr(bufPtr, '\n', bufUsed);
960 len = (cp - bufPtr) + 1;
962 if (!insertLine(num, bufPtr, len))
978 if (bufPtr != bufBase)
980 memcpy(bufBase, bufPtr, bufUsed);
981 bufPtr = bufBase + bufUsed;
984 if (bufUsed >= bufSize)
986 len = (bufSize * 3) / 2;
987 cp = realloc(bufBase, len);
991 fprintf(stderr, "No memory for buffer\n");
998 bufPtr = bufBase + bufUsed;
1002 cc = read(fd, bufPtr, bufSize - bufUsed);
1019 if (!insertLine(num, bufPtr, bufUsed))
1027 charCount += bufUsed;
1032 printf("%d lines%s, %d chars\n", lineCount,
1033 (bufUsed ? " (incomplete)" : ""), charCount);
1040 * Write the specified lines out to the specified file.
1041 * Returns TRUE if successful, or FALSE on an error with a message output.
1044 writeLines(const char * file, NUM num1, NUM num2)
1051 if ((num1 < 1) || (num2 > lastNum) || (num1 > num2))
1053 fprintf(stderr, "Bad line range for write\n");
1061 fd = creat(file, 0666);
1069 printf("\"%s\", ", file);
1072 lp = findLine(num1);
1081 while (num1++ <= num2)
1083 if (write(fd, lp->data, lp->len) != lp->len)
1091 charCount += lp->len;
1103 printf("%d lines, %d chars\n", lineCount, charCount);
1110 * Print lines in a specified range.
1111 * The last line printed becomes the current line.
1112 * If expandFlag is TRUE, then the line is printed specially to
1113 * show magic characters.
1116 printLines(NUM num1, NUM num2, BOOL expandFlag)
1123 if ((num1 < 1) || (num2 > lastNum) || (num1 > num2))
1125 fprintf(stderr, "Bad line range for print\n");
1130 lp = findLine(num1);
1135 while (!intFlag && (num1 <= num2))
1139 tryWrite(STDOUT, lp->data, lp->len);
1147 * Show control characters and characters with the
1148 * high bit set specially.
1153 if ((count > 0) && (cp[count - 1] == '\n'))
1162 fputs("M-", stdout);
1181 fputs("$\n", stdout);
1192 * Insert a new line with the specified text.
1193 * The line is inserted so as to become the specified line,
1194 * thus pushing any existing and further lines down one.
1195 * The inserted line is also set to become the current line.
1196 * Returns TRUE if successful.
1199 insertLine(NUM num, const char * data, LEN len)
1204 if ((num < 1) || (num > lastNum + 1))
1206 fprintf(stderr, "Inserting at bad line number\n");
1211 newLp = (LINE *) malloc(sizeof(LINE) + len - 1);
1215 fprintf(stderr, "Failed to allocate memory for line\n");
1220 memcpy(newLp->data, data, len);
1231 free((char *) newLp);
1238 newLp->prev = lp->prev;
1239 lp->prev->next = newLp;
1245 return setCurNum(num);
1250 * Delete lines from the given range.
1253 deleteLines(NUM num1, NUM num2)
1260 if ((num1 < 1) || (num2 > lastNum) || (num1 > num2))
1262 fprintf(stderr, "Bad line numbers for delete\n");
1267 lp = findLine(num1);
1272 if ((curNum >= num1) && (curNum <= num2))
1275 setCurNum(num2 + 1);
1277 setCurNum(num1 - 1);
1282 count = num2 - num1 + 1;
1309 * Search for a line which contains the specified string.
1310 * If the string is NULL, then the previously searched for string
1311 * is used. The currently searched for string is saved for future use.
1312 * Returns the line number which matches, or 0 if there was no match
1313 * with an error printed.
1316 searchLines(const char * str, NUM num1, NUM num2)
1321 if ((num1 < 1) || (num2 > lastNum) || (num1 > num2))
1323 fprintf(stderr, "Bad line numbers for search\n");
1330 if (searchString[0] == '\0')
1332 fprintf(stderr, "No previous search string\n");
1340 if (str != searchString)
1341 strcpy(searchString, str);
1345 lp = findLine(num1);
1350 while (num1 <= num2)
1352 if (findString(lp, str, len, 0) >= 0)
1359 fprintf(stderr, "Cannot find string \"%s\"\n", str);
1366 * Return a pointer to the specified line number.
1374 if ((num < 1) || (num > lastNum))
1376 fprintf(stderr, "Line number %d does not exist\n", num);
1384 curLine = lines.next;
1393 if (num < (curNum / 2))
1398 else if (num > ((curNum + lastNum) / 2))
1421 * Set the current line number.
1422 * Returns TRUE if successful.