commit 520a6bb3849813923d8ba7c9e7632dbafe71e6cf
Author: sin <sin@2f30.org>
Date: Sat, 28 Nov 2015 10:37:39 +0000
Initial commit
Diffstat:
A | ed.c | | | 1022 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
1 file changed, 1022 insertions(+), 0 deletions(-)
diff --git a/ed.c b/ed.c
@@ -0,0 +1,1022 @@
+
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <regex.h>
+#include <unistd.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <limits.h>
+#include <setjmp.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define CMDSIZE 100
+#define REGEXSIZE 100
+#define LINESIZE 80
+#define NUMLINES 32
+#define CACHESIZ 4096
+
+struct hline {
+ off_t seek;
+ char global;
+ int next, prev;
+};
+
+
+char *cmdp;
+char *prompt = "*";
+regex_t *pattern;
+
+int optverbose, optprompt, exstatus, optdiag = 1;
+int marks['z' - 'a'];
+int nlines, line1, line2;
+int curln, lastln;
+jmp_buf savesp;
+char *lasterr;
+unsigned idxsize, lastidx;
+struct hline *zero;
+char *text;
+char *savfname;
+unsigned sizetxt, memtxt;
+int scratch;
+int pflag, modflag, uflag;
+size_t csize;
+
+static void
+error(char *msg)
+{
+ exstatus = 1;
+ lasterr = msg;
+ fputs("?\n", stderr);
+ if (optverbose)
+ fprintf(stderr, "%s\n", msg);
+ longjmp(savesp, 1);
+}
+
+static int
+nextln(int line)
+{
+ ++line;
+ return (line > lastln) ? 0 : line;
+}
+
+static int
+prevln(int line)
+{
+ --line;
+ return (line < 0) ? lastln : line;
+}
+
+static void
+addchar(char c)
+{
+ char *p;
+ unsigned n;
+
+ if (sizetxt >= memtxt) {
+ if ((n = memtxt+LINESIZE) < memtxt ||
+ (p = realloc(text, n)) == NULL) {
+ error("out of memory");
+ }
+ memtxt = n;
+ text = p;
+ }
+ text[sizetxt++] = c;
+}
+
+static int
+makeline(char *s, int *off)
+{
+ char c, *begin = s;
+ struct hline *lp;
+ unsigned n;
+ size_t len;
+
+ if (lastidx >= idxsize) {
+ if ((n = idxsize+NUMLINES) < idxsize ||
+ (lp = realloc(zero, n * sizeof(*lp))) == NULL) {
+ error("out of memory");
+ }
+ zero = lp;
+ idxsize = idxsize + NUMLINES;
+ }
+ lp = &zero[lastidx];
+
+ while ((c = *s) && *s != '\n')
+ ++s;
+ if (c == '\n')
+ ++s;
+ len = s - begin;
+ if (off)
+ *off = len;
+
+ if (len > 0)
+ if ((lp->seek = lseek(scratch, 0, SEEK_END)) < 0 ||
+ write(scratch, begin, len) < 0) {
+ error("input/output error");
+ }
+
+ ++lastidx;
+ return lp - zero;
+}
+
+static int
+getindex(int line)
+{
+ struct hline *lp;
+ int n;
+
+ for (n = 0, lp = zero; n != line; ++n) {
+ lp = &zero[lp->next];
+ }
+ return lp - zero;
+}
+
+static char *
+gettxt(int line)
+{
+ static char buf[CACHESIZ];
+ static off_t lasto;
+ struct hline *lp;
+ off_t off, block;
+ ssize_t n;
+ char *p;
+
+ lp = &zero[getindex(line)];
+ off = lp->seek;
+ sizetxt = 0;
+
+repeat:
+ if (csize == 0 || off < lasto || off - lasto >= csize) {
+ block = off & ~(CACHESIZ-1);
+ fprintf(stderr, "getting a new cache at %ld\n", block);
+ if (lseek(scratch, block, SEEK_SET) < 0 ||
+ (n = read(scratch, buf, CACHESIZ)) < 0) {
+ error("input/output error");
+ }
+ csize = n;
+ lasto = block;
+ }
+ for (p = &buf[off - lasto]; *p != '\n' && p < &buf[csize]; ++p) {
+ ++off;
+ addchar(*p);
+ }
+ if (p == &buf[csize])
+ goto repeat;
+
+ addchar('\n');
+ addchar('\0');
+ return text;
+}
+
+static void
+setglobal(int i, int dir)
+{
+ int lnk = getindex(i);
+ int m = regexec(pattern, gettxt(i), 0, NULL, 0) == 0;
+
+
+ if (m == dir && i >= line1 && i <= line2)
+ zero[lnk].global = 1;
+ else
+ zero[lnk].global = 0;
+}
+
+static void
+relink(int to1, int from1, int from2, int to2)
+{
+ zero[from1].next = to1;
+ zero[from2].prev = to2;
+ modflag = 1;
+}
+
+static void
+inject(char *s)
+{
+ int off, k, cur, begin, end;
+
+ begin = getindex(curln);
+ end = getindex(nextln(curln));
+
+ while (*s) {
+ k = makeline(s, &off);
+ s += off;
+ relink(k, begin, k, begin);
+ relink(end, k, end, k);
+ ++lastln;
+ ++curln;
+ begin = k;
+ }
+}
+
+static void
+clearbuf()
+{
+ if (scratch)
+ close(scratch);
+ remove("ed.tmp");
+ free(zero);
+ zero = NULL;
+ scratch = csize = idxsize = lastidx = curln = lastln = 0;
+ lastln = curln = 0;
+}
+
+static void
+setscratch()
+{
+ int k, flags = O_CREAT | O_TRUNC | O_RDWR | O_EXCL;
+ mode_t mode = S_IRUSR | S_IWUSR;
+
+ clearbuf();
+ if ((scratch = open("ed.tmp", flags, mode)) < 0 ||
+ (k = makeline("", NULL))) {
+ error("input/output error in scratch file");
+ }
+ relink(k, k, k, k);
+ modflag = 0;
+}
+
+static void
+compile()
+{
+ char c, *bp, delim, buff[REGEXSIZE];
+ size_t len;
+
+ for (delim = *cmdp++, bp = cmdp; *cmdp; ++cmdp) {
+ if ((c = *cmdp) == delim)
+ break;
+ else if (c == '\\' && *++cmdp == '\0')
+ break;
+ }
+ if ((len = cmdp - bp) == 0) {
+ if (pattern == NULL)
+ error("no previous pattern");
+ return;
+ }
+ if (!pattern) {
+ if ((pattern = malloc(sizeof(*pattern))) == NULL)
+ error("no memory");
+ }
+ memcpy(buff, bp, len);
+ buff[len] = '\0';
+ if (regcomp(pattern, buff, REG_BASIC))
+ error("incorrect regular expression"); /* TODO: call regerror */
+}
+
+static int
+match(int way)
+{
+ int i;
+
+ i = curln;
+ do {
+ i = (way == '/') ? nextln(i) : prevln(i);
+ if (regexec(pattern, gettxt(i), 0, NULL, 0) == 0)
+ return i;
+ } while (i != curln);
+
+ error("invalid address");
+}
+
+static void
+skipblank(void)
+{
+ while (*cmdp == ' ' || *cmdp == '\t')
+ ++cmdp;
+}
+
+static int
+getnum(int *line)
+{
+ int c, ln, sign = 1;
+
+ skipblank();
+
+ switch (*cmdp) {
+ case '-':
+ case '+':
+ --cmdp;
+ case '.':
+ ln = curln;
+ break;
+ case '\'':
+ ++cmdp;
+ skipblank();
+ if (!isalpha(c = *cmdp))
+ error("invalid mark character");
+ if ((ln = marks[c]) == 0)
+ error("invalid address");
+ break;
+ case '$':
+ ln = lastln;
+ break;
+ case '?':
+ sign = -sign;
+ case '/':
+ c = *cmdp;
+ compile();
+ ln = match(c);
+ break;
+ default:
+ if (isdigit(*cmdp)) {
+ for (ln = 0; isdigit(*cmdp); ln += *cmdp++ - '0') {
+ if (ln > INT_MAX/10)
+ error("invalid address");
+ ln *= 10;
+ }
+ --cmdp;
+ break;
+ }
+ return 0;
+ }
+
+ ++cmdp;
+ *line = ln;
+ return 1;
+}
+
+static int
+address(int *line)
+{
+ int ln, sign, c, num;
+
+ if (!getnum(&ln))
+ return 0;
+
+ for (;;) {
+ skipblank();
+ c = *cmdp++;
+ if (c != '+' && c != '-')
+ break;
+ sign = c == '+';
+ if (!getnum(&num))
+ num = 1;
+ /* FIXME: detect integer overflow here */
+ ln += sign * num;
+ }
+ --cmdp;
+
+ if (ln < 0 || ln > lastln)
+ error("invalid address");
+ *line = ln;
+ return 1;
+}
+
+static void
+getlst()
+{
+ int ln;
+
+ if (*cmdp == ',') {
+ ++cmdp;
+ line1 = 1;
+ line2 = lastln;
+ nlines = lastln;
+ return;
+ }
+ line2 = curln;
+ for (nlines = 0; address(&ln); ) {
+ line1 = line2;
+ line2 = ln;
+ ++nlines;
+ skipblank();
+ if (*cmdp != ',' && *cmdp != ';')
+ break;
+ if (*cmdp++ == ';')
+ curln = line2;
+ }
+ if (nlines > 2)
+ nlines = 2;
+ else if (nlines <= 1)
+ line1 = line2;
+}
+
+static void
+deflines(int def1, int def2)
+{
+ if (nlines == 0) {
+ line1 = def1;
+ line2 = def2;
+ }
+ if (line1 > line2 || line1 < 0 || line2 > lastln)
+ error("invalid address");
+}
+
+static void
+setfname(char *fname)
+{
+ char *s;
+ size_t len;
+
+ if (fname == savfname)
+ return;
+ len = strlen(fname);
+ if ((s = malloc(len + 1)) == NULL)
+ error("out of memory");
+ memcpy(s, fname, len+1);
+ free(savfname);
+ savfname = s;
+}
+
+static void
+dowrite(char *fname, int trunc)
+{
+ FILE *fp;
+ char *s;
+ int i;
+
+ if ((fp = fopen(fname, (trunc) ? "w" : "a")) == NULL)
+ error("input/output error");
+
+ for (i = line1; i <= line2; ++i) {
+ s = gettxt(i);
+ fprintf(fp, "%s\n", s);
+ }
+
+ curln = line2;
+ if (fclose(fp))
+ error("input/output error");
+ setfname(fname);
+ modflag = 0;
+}
+
+static void
+doread(char *fname)
+{
+ FILE *fp;
+ size_t len = 0, cnt, n;
+ char *s = NULL, *p;
+
+ if (!savfname)
+ setfname(fname);
+ if ((fp = fopen(fname, "r")) == NULL)
+ error("input/output error");
+ curln = line2;
+ for (cnt = 0; getline(&s, &len, fp) > 0; ++cnt) {
+ if ((n = strlen(s)) == 0)
+ break;
+ if (s[n-1] != '\n') {
+ if (n == len) {
+ if ((p = realloc(s, len+1)) == NULL)
+ error("out of memory");
+ ++len;
+ s = p;
+ }
+ s[n-1] = '\n';
+ s[n] = '\0';
+ }
+ /*
+ * FIXME: if inject calls error we can have a
+ * memory leak, and a file leak
+ */
+ inject(s);
+ }
+ printf("%d\n", cnt);
+ free(s);
+ if (fclose(fp))
+ error("input/output error");
+}
+
+static void
+doprint(void)
+{
+ int i, c;
+ char *s, *str;
+
+ if (line1 <= 0 || line2 > lastln)
+ error("incorrect address");
+ for (i = line1; i <= line2; ++i) {
+ if (pflag == 'n')
+ printf("%d\t", i);
+ for (s = gettxt(i); (c = *s) != '\n'; ++s) {
+ if (pflag != 'l')
+ goto print_char;
+ switch (c) {
+ case '$':
+ str = "\\$";
+ goto print_str;
+ case '\t':
+ str = "\\t";
+ goto print_str;
+ case '\b':
+ str = "\\b";
+ goto print_str;
+ case '\\':
+ str = "\\\\";
+ goto print_str;
+ default:
+ if (!isprint(c)) {
+ printf("\\x%x", 0xFF & c);
+ break;
+ }
+ print_char:
+ putchar(c);
+ break;
+ print_str:
+ fputs(str, stdout);
+ break;
+ }
+ }
+ if (pflag == 'l')
+ fputs("$", stdout);
+ putc('\n', stdout);
+ }
+ curln = i - 1;
+}
+
+static void
+dohelp(void)
+{
+ if (nlines > 0)
+ error("invalid address");
+ if (lasterr)
+ fprintf(stderr, "%s\n", lasterr);
+}
+
+static void
+chkprint(int flag)
+{
+ char c;
+
+ if (flag) {
+ if ((c = *cmdp) == 'p' || c == 'l' || c == 'n') {
+ pflag = c;
+ ++cmdp;
+ }
+ }
+ if (*cmdp++ != '\n')
+ error("invalid command suffix");
+}
+
+static char *
+getfname(char *s)
+{
+ char c, *p;
+
+ if ((c = *s) != '\n' && c != ' ' && c != '\0')
+ error("invalid command suffix");
+ while ((c = *s) == ' ' || c == '\t' || c == '\n')
+ ++s;
+ if (*s) {
+ for (p = s; *p && *p != '\n'; ++p)
+ /* nothing */;
+ *p = '\0';
+ return s;
+ } else if (savfname) {
+ return savfname;
+ } else {
+ error("no current filename");
+ }
+}
+
+static void
+append(int num)
+{
+ char *s = NULL;
+ size_t len = 0;
+
+ curln = num;
+ while (getline(&s, &len, stdin) > 0) {
+ if (*s == '.' && s[1] == '\n')
+ break;
+ inject(s);
+ }
+ free(s);
+}
+
+static void
+delete(int from, int to)
+{
+ int lto, lfrom;
+
+ if (from == 0)
+ error("incorrect address");
+
+ lfrom = getindex(prevln(from));
+ lto = getindex(nextln(to));
+ lastln -= to - from + 1;
+ curln = (from > lastln) ? lastln : from;;
+ relink(lto, lfrom, lto, lfrom);
+}
+
+static void
+move(int where)
+{
+ int before, after, lto, lfrom;
+
+ if (line1 == 0 || where >= line1 && where <= line2)
+ error("incorrect address");
+
+ before = getindex(prevln(line1));
+ after = getindex(nextln(line2));
+ lfrom = getindex(line1);
+ lto = getindex(line2);
+ relink(after, before, after, before);
+
+ if (where < line1) {
+ curln = where + line1 - line2 + 1;
+ } else {
+ curln = where;
+ where -= line1 - line2 + 1;
+ }
+ before = getindex(where);
+ after = getindex(nextln(where));
+ relink(lfrom, before, lfrom, before);
+ relink(after, lto, after, lto);
+}
+
+static void
+join(void)
+{
+ int first, last;
+
+ first = getindex(line1);
+ last = getindex(line2);
+ delete(line1, line2);
+
+ /*
+ * TODO: we have to join all the lines,
+ * but we cannot use addchar because it is
+ * used in gettxt().
+ */
+}
+
+static void
+scroll(int num)
+{
+ int i, last;
+ char *s;
+
+ if (line1 == 0 || line1 == lastln)
+ error("incorrect address");
+
+ last = (line1 + num > lastln) ? lastln - line1 : num;
+ for (i = line1; i <= line1 + num && i <= lastln; ++i)
+ fputs(gettxt(i), stdout);
+ curln = i;
+}
+
+static void
+copy(int where)
+{
+ int i, ind;
+ char *s;
+
+ if (line1 == 0 || where >= line1 && where <= line2)
+ error("incorrect address");
+ curln = where;
+
+ for (i = line1; i <= line2; ++i)
+ inject(gettxt(i));
+}
+
+static void
+quit(void)
+{
+ clearbuf();
+ exit(exstatus);
+}
+
+static void
+execsh(void)
+{
+ static char *cmd;
+
+ skipblank();
+ if (*cmdp != '!') {
+ free(cmd);
+ if ((cmd = strdup(cmdp)) == NULL)
+ error("out of memory");
+ }
+ system(cmd);
+ puts("!");
+}
+
+static void
+docmd()
+{
+ char c, *s;
+ int num, trunc, line3;
+
+ while (*cmdp) {
+ trunc = pflag = 0;
+ skipblank();
+
+ switch (*cmdp++) {
+ case '!':
+ execsh();
+ break;
+ case '\n':
+ deflines(curln+1, curln+1);
+ pflag = 'p';
+ goto print;
+ case 'l':
+ case 'n':
+ case 'p':
+ --cmdp;
+ chkprint(1);
+ deflines(curln, curln);
+ goto print;
+ case 'H':
+ optverbose ^= 1;
+ case 'h':
+ if (nlines > 0)
+ goto unexpected;
+ chkprint(0);
+ dohelp();
+ break;
+ case 'w':
+ trunc = 1;
+ case 'W':
+ chkprint(0);
+ deflines(1, lastln);
+ dowrite(getfname(cmdp), trunc);
+ break;
+ case 'r':
+ if (nlines > 1)
+ goto bad_address;
+ deflines(lastln, lastln);
+ doread(getfname(cmdp));
+ break;
+ case 'd':
+ chkprint(1);
+ deflines(curln, curln);
+ delete(line1, line2);
+ break;
+ case '=':
+ if (nlines > 1)
+ goto bad_address;
+ chkprint(1);
+ deflines(lastln, lastln);
+ printf("%d\n", line1);
+ break;
+ case 'i':
+ if (nlines > 1)
+ goto bad_address;
+ chkprint(1);
+ deflines(curln, curln);
+ if (line1 == 0)
+ goto bad_address;
+ append(prevln(line1));
+ break;
+ case 'a':
+ if (nlines > 1)
+ goto bad_address;
+ chkprint(1);
+ deflines(curln, curln);
+ append(line1);
+ break;
+ case 'm':
+ deflines(curln, curln);
+ if (!address(&line3))
+ line3 = curln;
+ chkprint(1);
+ move(line3);
+ break;
+ case 't':
+ deflines(curln, curln);
+ if (!address(&line3))
+ line3 = curln;
+ chkprint(1);
+ copy(line3);
+ break;
+ case 'c':
+ chkprint(1);
+ deflines(curln, curln);
+ delete(line1, line2);
+ append(prevln(line1));
+ break;
+ case 'j':
+ chkprint(1);
+ deflines(curln, curln+1);
+ if (line1 == 0)
+ goto bad_address;
+ join();
+ break;
+ case 'z':
+ if (nlines > 1)
+ goto bad_address;
+ if (isdigit(*cmdp))
+ getnum(&num);
+ else
+ num = 24;
+ chkprint(1);
+ scroll(num);
+ break;
+ case 'k':
+ if (nlines > 1)
+ goto bad_address;
+ if (!islower(c = *cmdp++))
+ error("invalid mark character");
+ chkprint(1);
+ deflines(curln, curln);
+ marks[c] = line1;
+ break;
+ case 'P':
+ if (nlines > 0)
+ goto unexpected;
+ chkprint(1);
+ optprompt ^= 1;
+ break;
+ case 'Q':
+ modflag = 0;
+ case 'q':
+ if (nlines > 0)
+ goto unexpected;
+ if (modflag)
+ goto modified;
+ quit();
+ break;
+ case 'f':
+ if (nlines > 0)
+ goto unexpected;
+ if ((s = getfname(cmdp)) == savfname)
+ puts(savfname);
+ else
+ setfname(s);
+ break;
+ case 'E':
+ modflag = 0;
+ case 'e':
+ chkprint(0);
+ if (nlines > 0)
+ goto unexpected;
+ if (modflag)
+ goto modified;
+ setscratch();
+ setfname(getfname(cmdp));
+ deflines(curln, curln);
+ doread(savfname);
+ modflag = 0;
+ break;
+ default:
+ error("unknown command");
+ bad_address:
+ error("invalid address");
+ modified:
+ modflag = 0;
+ error("warning: file modified");
+ unexpected:
+ error("unexpected addres");
+ }
+
+ if (!pflag)
+ continue;
+
+ line1 = line2 = curln;
+ print:
+ doprint();
+ }
+}
+
+static int
+chkglobal(void)
+{
+ int nmatch, i;
+
+ uflag = 1;
+ skipblank();
+ deflines(1, lastln);
+
+ switch (*cmdp++) {
+ case 'g':
+ uflag = 0;
+ case 'G':
+ nmatch = 1;
+ break;
+ case 'v':
+ uflag = 0;
+ case 'V':
+ nmatch = 0;
+ break;
+ default:
+ --cmdp;
+ return 0;
+ }
+ compile();
+ ++cmdp; /* skip trailing '/' */
+
+ if (uflag)
+ chkprint(0);
+
+ for (i = line1; i <= line2; i = i+1)
+ setglobal(i, nmatch);
+
+ return 1;
+}
+
+static void
+readcmd(int isglobal)
+{
+ static char *buf;
+ static size_t size;
+ char *s, *p;
+ int c;
+
+ if (isglobal) {
+ if ((c = getchar()) == '&') {
+ if (getchar() != '\n')
+ error("");
+ cmdp = buf;
+ }
+ }
+
+ for (s = buf;; *s++ = c) {
+ if (optprompt)
+ fputs(prompt, stdout);
+ if (!buf || s == &buf[size-2]) {
+ if ((p = realloc(buf, size+CMDSIZE)) == NULL)
+ error("out of memory");
+ buf = p;
+ s = (size == 0) ? p : &p[size-2];
+ size = size+CMDSIZE;
+ }
+ if ((c = getchar()) == EOF || c == '\n')
+ break;
+ if (c == '\\' && (c = getchar()) == EOF)
+ break;
+ }
+ *s++ = '\n';
+ *s = '\0';
+ cmdp = buf;
+}
+
+static void
+doglobal(void)
+{
+ int i, k;
+ char *s;
+
+ s = cmdp;
+ for (i = 1; i <= lastln; i = i+1) {
+ k = getindex(i);
+ if (!zero[k].global)
+ continue;
+ cmdp = s;
+ curln = i;
+ nlines = 0;
+ if (uflag) {
+ line1 = line2 = i;
+ pflag = 0;
+ doprint();
+ readcmd(1);
+ }
+ docmd();
+ }
+}
+
+static void
+usage(void)
+{
+ fputs("ed [-s][-p][file]\n", stderr);
+ exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+ char *p;
+
+ while (*++argv) {
+ if (argv[0][0] != '-')
+ break;
+ for (p = &argv[0][1]; *p; ++p) {
+ switch (*p) {
+ case 's':
+ optdiag = 0;
+ break;
+ case 'p':
+ if (*++argv == NULL)
+ usage();
+ prompt = *argv;
+ break;
+ default:
+ usage();
+ }
+ }
+ }
+
+
+ if (!setjmp(savesp)) {
+ setscratch();
+ if (*argv) {
+ setfname(*argv);
+ doread(savfname);
+ modflag = 0;
+ }
+ }
+
+ for (;;) {
+ readcmd(0);
+ getlst();
+ if (chkglobal())
+ doglobal();
+ else
+ docmd();
+ }
+
+ /* NOTREACHED */
+ return 0;
+}