ed

simple ed
git clone git://git.2f30.org/ed.git
Log | Files | Refs

commit 520a6bb3849813923d8ba7c9e7632dbafe71e6cf
Author: sin <sin@2f30.org>
Date:   Sat Nov 28 10:37:39 +0000

Initial commit

Diffstat:
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; +}