sbase/ed.c

1473 lines
23 KiB
C
Raw Permalink Normal View History

2015-12-14 12:09:34 +00:00
/* See LICENSE file for copyright and license details. */
#include <sys/stat.h>
#include <fcntl.h>
#include <regex.h>
#include <unistd.h>
#include <ctype.h>
#include <limits.h>
#include <setjmp.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "util.h"
2015-12-15 14:45:16 +00:00
2015-12-14 12:09:34 +00:00
#define REGEXSIZE 100
#define LINESIZE 80
#define NUMLINES 32
#define CACHESIZ 4096
2018-03-04 15:08:57 +00:00
#define AFTER 0
#define BEFORE 1
2015-12-14 12:09:34 +00:00
typedef struct {
char *str;
size_t cap;
size_t siz;
} String;
2015-12-14 12:09:34 +00:00
struct hline {
off_t seek;
char global;
int next, prev;
};
struct undo {
int curln, lastln;
2015-12-14 12:09:34 +00:00
size_t nr, cap;
struct link {
int to1, from1;
int to2, from2;
} *vec;
};
2015-12-14 12:13:23 +00:00
static char *prompt = "*";
static regex_t *pattern;
static regmatch_t matchs[10];
2018-03-04 13:06:24 +00:00
static String lastre;
2015-12-14 12:09:34 +00:00
2015-12-14 12:13:23 +00:00
static int optverbose, optprompt, exstatus, optdiag = 1;
static int marks['z' - 'a'];
static int nlines, line1, line2;
static int curln, lastln, ocurln, olastln;
2015-12-14 12:13:23 +00:00
static jmp_buf savesp;
static char *lasterr;
static size_t idxsize, lastidx;
static struct hline *zero;
2018-03-04 13:06:24 +00:00
static String text;
2015-12-14 12:13:23 +00:00
static char savfname[FILENAME_MAX];
static char tmpname[FILENAME_MAX];
static int scratch;
static int pflag, modflag, uflag, gflag;
static size_t csize;
2018-03-04 13:01:21 +00:00
static String cmdline;
2015-12-14 12:13:23 +00:00
static char *ocmdline;
static int repidx;
static char *rhs;
static char *lastmatch;
static struct undo udata;
static int newcmd;
int eol, bol;
2015-12-14 12:09:34 +00:00
static void
discard(void)
2015-12-14 12:09:34 +00:00
{
int c;
2018-03-04 13:01:21 +00:00
if (repidx >= 0)
return;
/* discard until the end of the line */
if (cmdline.siz > 0 && cmdline.str[cmdline.siz-1] == '\n')
return;
while ((c = getchar()) != '\n' && c != EOF)
;
}
static void undo(void);
static void
error(char *msg)
{
2015-12-14 12:09:34 +00:00
exstatus = 1;
lasterr = msg;
puts("?");
2015-12-14 12:09:34 +00:00
if (optverbose)
puts(msg);
if (!newcmd)
undo();
discard();
curln = ocurln;
2015-12-14 12:09:34 +00:00
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 char *
addchar(char c, String *s)
{
size_t cap = s->cap, siz = s->siz;
char *t = s->str;
if (siz >= cap &&
(cap > SIZE_MAX - LINESIZE ||
(t = realloc(t, cap += LINESIZE)) == NULL))
error("out of memory");
t[siz++] = c;
s->siz = siz;
s->cap = cap;
s->str = t;
return t;
}
2015-12-14 12:09:34 +00:00
static int
input(void)
{
int c;
if (repidx >= 0)
return ocmdline[repidx++];
if ((c = getchar()) != EOF)
addchar(c, &cmdline);
2015-12-14 12:09:34 +00:00
return c;
}
static int
back(int c)
{
if (repidx > 0) {
--repidx;
} else {
ungetc(c, stdin);
if (c != EOF)
2018-03-04 13:01:21 +00:00
--cmdline.siz;
2015-12-14 12:09:34 +00:00
}
return c;
}
static int
makeline(char *s, int *off)
{
struct hline *lp;
size_t len;
char c, *begin = s;
if (lastidx >= idxsize) {
lp = NULL;
if (idxsize <= SIZE_MAX - NUMLINES)
2019-12-31 21:41:38 +00:00
lp = reallocarray(zero, idxsize + NUMLINES, sizeof(*lp));
if (!lp)
2015-12-14 12:09:34 +00:00
error("out of memory");
idxsize += NUMLINES;
zero = lp;
}
lp = zero + lastidx;
if (!s) {
lp->seek = -1;
len = 0;
} else {
while ((c = *s++) != '\n')
/* nothing */;
len = s - begin;
if ((lp->seek = lseek(scratch, 0, SEEK_END)) < 0 ||
write(scratch, begin, len) < 0) {
error("input/output error");
}
}
2015-12-14 12:09:34 +00:00
if (off)
*off = len;
++lastidx;
return lp - zero;
}
static int
getindex(int line)
{
struct hline *lp;
int n;
if (line == -1)
line = 0;
for (n = 0, lp = zero; n != line; n++)
2015-12-14 12:09:34 +00:00
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);
2018-03-04 13:06:24 +00:00
text.siz = 0;
off = lp->seek;
if (off == (off_t) -1)
return addchar('\0', &text);
2015-12-14 12:09:34 +00:00
repeat:
if (!csize || off < lasto || off - lasto >= csize) {
block = off & ~(CACHESIZ-1);
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 < buf + csize && *p != '\n'; ++p) {
++off;
addchar(*p, &text);
2015-12-14 12:09:34 +00:00
}
if (csize && p == buf + csize)
goto repeat;
addchar('\n', &text);
addchar('\0', &text);
2018-03-04 13:06:24 +00:00
return text.str;
2015-12-14 12:09:34 +00:00
}
static void
setglobal(int i, int v)
{
zero[getindex(i)].global = v;
}
static void
clearundo(void)
{
free(udata.vec);
udata.vec = NULL;
newcmd = udata.nr = udata.cap = 0;
modflag = 0;
2015-12-14 12:09:34 +00:00
}
static void
newundo(int from1, int from2)
2015-12-14 12:09:34 +00:00
{
struct link *p;
if (newcmd) {
clearundo();
udata.curln = ocurln;
udata.lastln = olastln;
2015-12-14 12:09:34 +00:00
}
if (udata.nr >= udata.cap) {
size_t siz = (udata.cap + 10) * sizeof(struct link);
if ((p = realloc(udata.vec, siz)) == NULL)
error("out of memory");
udata.vec = p;
udata.cap = udata.cap + 10;
}
p = &udata.vec[udata.nr++];
p->from1 = from1;
p->to1 = zero[from1].next;
p->from2 = from2;
p->to2 = zero[from2].prev;
}
2015-12-14 12:09:34 +00:00
/*
* relink: to1 <- from1
* from2 -> to2
*/
static void
relink(int to1, int from1, int from2, int to2)
{
newundo(from1, from2);
2015-12-14 12:09:34 +00:00
zero[from1].next = to1;
zero[from2].prev = to2;
modflag = 1;
}
static void
undo(void)
{
struct link *p;
if (udata.nr == 0)
2015-12-31 19:35:26 +00:00
return;
for (p = &udata.vec[udata.nr-1]; udata.nr > 0; --p) {
--udata.nr;
2015-12-14 12:09:34 +00:00
zero[p->from1].next = p->to1;
zero[p->from2].prev = p->to2;
}
free(udata.vec);
udata.vec = NULL;
udata.cap = 0;
curln = udata.curln;
lastln = udata.lastln;
2015-12-14 12:09:34 +00:00
}
static void
2018-03-04 15:08:57 +00:00
inject(char *s, int where)
2015-12-14 12:09:34 +00:00
{
int off, k, begin, end;
2018-03-04 15:08:57 +00:00
if (where == BEFORE) {
2016-10-09 23:12:46 +00:00
begin = getindex(curln-1);
end = getindex(nextln(curln-1));
} else {
begin = getindex(curln);
end = getindex(nextln(curln));
}
2015-12-14 12:09:34 +00:00
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(void)
2015-12-14 12:09:34 +00:00
{
if (scratch)
close(scratch);
remove(tmpname);
free(zero);
zero = NULL;
scratch = csize = idxsize = lastidx = curln = lastln = 0;
modflag = lastln = curln = 0;
2015-12-14 12:09:34 +00:00
}
static void
setscratch(void)
2015-12-14 12:09:34 +00:00
{
int r, k;
char *dir;
2015-12-14 12:09:34 +00:00
clearbuf();
clearundo();
if ((dir = getenv("TMPDIR")) == NULL)
dir = "/tmp";
r = snprintf(tmpname, sizeof(tmpname), "%s/%s",
dir, "ed.XXXXXX");
if (r < 0 || (size_t)r >= sizeof(tmpname))
error("scratch filename too long");
if ((scratch = mkstemp(tmpname)) < 0)
error("failed to create scratch file");
if ((k = makeline(NULL, NULL)))
2015-12-14 12:09:34 +00:00
error("input/output error in scratch file");
relink(k, k, k, k);
clearundo();
}
static void
compile(int delim)
{
int n, ret, c,bracket;
static char buf[BUFSIZ];
if (!isgraph(delim))
error("invalid pattern delimiter");
2018-03-04 13:06:24 +00:00
eol = bol = bracket = lastre.siz = 0;
2015-12-14 12:09:34 +00:00
for (n = 0;; ++n) {
if ((c = input()) == delim && !bracket)
break;
if (c == '^') {
bol = 1;
} else if (c == '$') {
eol = 1;
} else if (c == '\n' || c == EOF) {
2015-12-14 12:09:34 +00:00
back(c);
break;
}
if (c == '\\') {
addchar(c, &lastre);
2015-12-14 12:09:34 +00:00
c = input();
} else if (c == '[') {
bracket = 1;
} else if (c == ']') {
bracket = 0;
}
addchar(c, &lastre);
2015-12-14 12:09:34 +00:00
}
if (n == 0) {
if (!pattern)
error("no previous pattern");
return;
}
addchar('\0', &lastre);
2015-12-14 12:09:34 +00:00
if (pattern)
regfree(pattern);
if (!pattern && (!(pattern = malloc(sizeof(*pattern)))))
error("out of memory");
2018-03-04 13:06:24 +00:00
if ((ret = regcomp(pattern, lastre.str, REG_NEWLINE))) {
2015-12-14 12:09:34 +00:00
regerror(ret, pattern, buf, sizeof(buf));
error(buf);
}
}
static int
match(int num)
{
lastmatch = gettxt(num);
return !regexec(pattern, lastmatch, 10, matchs, 0);
}
static int
rematch(int num)
{
regoff_t off = matchs[0].rm_eo;
if (!regexec(pattern, lastmatch + off, 10, matchs, 0)) {
lastmatch += off;
return 1;
}
return 0;
2015-12-14 12:09:34 +00:00
}
static int
search(int way)
{
int i;
i = curln;
do {
i = (way == '?') ? prevln(i) : nextln(i);
if (i > 0 && match(i))
2015-12-14 12:09:34 +00:00
return i;
} while (i != curln);
error("invalid address");
return -1; /* not reached */
2015-12-14 12:09:34 +00:00
}
static void
skipblank(void)
{
char c;
while ((c = input()) == ' ' || c == '\t')
/* nothing */;
back(c);
}
static int
getnum(void)
{
int ln, n, c;
for (ln = 0; isdigit(c = input()); ln += n) {
if (ln > INT_MAX/10)
goto invalid;
n = c - '0';
ln *= 10;
if (INT_MAX - ln < n)
goto invalid;
}
back(c);
return ln;
invalid:
error("invalid address");
return -1; /* not reached */
2015-12-14 12:09:34 +00:00
}
static int
linenum(int *line)
{
int ln, c;
skipblank();
switch (c = input()) {
case '.':
ln = curln;
break;
case '\'':
skipblank();
if (!islower(c = input()))
2015-12-14 12:09:34 +00:00
error("invalid mark character");
if (!(ln = marks[c - 'a']))
2015-12-14 12:09:34 +00:00
error("invalid address");
break;
case '$':
ln = lastln;
break;
case '?':
case '/':
compile(c);
ln = search(c);
break;
case '^':
case '-':
case '+':
ln = curln;
back(c);
break;
default:
back(c);
if (isdigit(c))
ln = getnum();
else
return 0;
break;
}
*line = ln;
return 1;
}
static int
address(int *line)
{
int ln, sign, c, num;
if (!linenum(&ln))
return 0;
for (;;) {
skipblank();
if ((c = input()) != '+' && c != '-' && c != '^')
break;
sign = c == '+' ? 1 : -1;
num = isdigit(back(input())) ? getnum() : 1;
num *= sign;
if (INT_MAX - ln < num)
goto invalid;
ln += num;
}
back(c);
if (ln < 0 || ln > lastln)
error("invalid address");
*line = ln;
return 1;
invalid:
error("invalid address");
return -1; /* not reached */
2015-12-14 12:09:34 +00:00
}
static void
getlst(void)
2015-12-14 12:09:34 +00:00
{
int ln, c;
if ((c = input()) == ',') {
line1 = 1;
line2 = lastln;
nlines = lastln;
return;
} else if (c == ';') {
line1 = curln;
line2 = lastln;
nlines = lastln - curln + 1;
return;
}
back(c);
line2 = curln;
for (nlines = 0; address(&ln); ) {
line1 = line2;
line2 = ln;
++nlines;
skipblank();
if ((c = input()) != ',' && c != ';') {
back(c);
break;
}
if (c == ';')
curln = line2;
}
if (nlines > 2)
nlines = 2;
else if (nlines <= 1)
line1 = line2;
}
static void
deflines(int def1, int def2)
{
if (!nlines) {
line1 = def1;
line2 = def2;
}
if (line1 > line2 || line1 < 0 || line2 > lastln)
error("invalid address");
}
static void
dowrite(const char *fname, int trunc)
2015-12-14 12:09:34 +00:00
{
FILE *fp;
2020-09-14 22:36:15 +00:00
size_t bytecount = 0;
int i, line;
2015-12-14 12:09:34 +00:00
if (!(fp = fopen(fname, (trunc) ? "w" : "a")))
error("input/output error");
line = curln;
2020-09-14 22:36:15 +00:00
for (i = line1; i <= line2; ++i) {
gettxt(i);
bytecount += text.siz - 1;
fwrite(text.str, 1, text.siz - 1, fp);
2020-09-14 22:36:15 +00:00
}
2015-12-14 12:09:34 +00:00
curln = line2;
if (fclose(fp))
error("input/output error");
strcpy(savfname, fname);
2015-12-14 12:09:34 +00:00
modflag = 0;
curln = line;
2020-09-14 22:36:15 +00:00
printf("%zu\n", bytecount);
2015-12-14 12:09:34 +00:00
}
static void
doread(const char *fname)
2015-12-14 12:09:34 +00:00
{
size_t cnt;
ssize_t n;
char *p;
FILE *aux;
static size_t len;
static char *s;
static FILE *fp;
if (fp)
fclose(fp);
if ((fp = fopen(fname, "r")) == NULL)
error("cannot open input file");
2015-12-14 12:09:34 +00:00
curln = line2;
for (cnt = 0; (n = getline(&s, &len, fp)) > 0; cnt += (size_t)n) {
if (s[n-1] != '\n') {
if (len == SIZE_MAX || !(p = realloc(s, ++len)))
error("out of memory");
s = p;
s[n-1] = '\n';
s[n] = '\0';
}
2018-03-04 15:08:57 +00:00
inject(s, AFTER);
2015-12-14 12:09:34 +00:00
}
if (optdiag)
printf("%zu\n", cnt);
2015-12-14 12:09:34 +00:00
aux = fp;
fp = NULL;
if (fclose(aux))
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 (lasterr)
puts(lasterr);
2015-12-14 12:09:34 +00:00
}
static void
chkprint(int flag)
{
char c;
if (flag) {
if ((c = input()) == 'p' || c == 'l' || c == 'n')
pflag = c;
else
back(c);
}
if (input() != '\n')
error("invalid command suffix");
}
static char *
getfname(char comm)
2015-12-14 12:09:34 +00:00
{
int c;
char *bp;
static char fname[FILENAME_MAX];
skipblank();
for (bp = fname; bp < &fname[FILENAME_MAX]; *bp++ = c) {
if ((c = input()) == EOF || c == '\n')
break;
}
if (bp == fname) {
if (savfname[0] == '\0')
error("no current filename");
return savfname;
} else if (bp == &fname[FILENAME_MAX]) {
error("file name too long");
} else {
*bp = '\0';
if (savfname[0] == '\0' || comm == 'e' || comm == 'f')
strcpy(savfname, fname);
2015-12-14 12:09:34 +00:00
return fname;
}
return NULL; /* not reached */
2015-12-14 12:09:34 +00:00
}
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;
2018-03-04 15:08:57 +00:00
inject(s, AFTER);
2015-12-14 12:09:34 +00:00
}
free(s);
}
static void
delete(int from, int to)
{
int lto, lfrom;
if (!from)
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 || (where >= line1 && where <= line2))
2015-12-14 12:09:34 +00:00
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 i;
char *t, c;
String s;
2015-12-14 12:09:34 +00:00
s.str = NULL;
2018-03-04 13:06:24 +00:00
s.siz = s.cap = 0;
for (i = line1;; i = nextln(i)) {
2015-12-14 12:09:34 +00:00
for (t = gettxt(i); (c = *t) != '\n'; ++t)
addchar(*t, &s);
if (i == line2)
break;
2015-12-14 12:09:34 +00:00
}
addchar('\n', &s);
addchar('\0', &s);
2015-12-14 12:09:34 +00:00
delete(line1, line2);
2018-03-04 15:08:57 +00:00
inject(s.str, BEFORE);
2018-03-04 13:06:24 +00:00
free(s.str);
2015-12-14 12:09:34 +00:00
}
static void
scroll(int num)
{
2018-03-04 16:10:17 +00:00
int max, ln, cnt;
2015-12-14 12:09:34 +00:00
if (!line1 || line1 == lastln)
error("incorrect address");
2018-03-04 16:10:17 +00:00
ln = line1;
max = line1 + num;
if (max > lastln)
max = lastln;
for (cnt = line1; cnt < max; cnt++) {
fputs(gettxt(ln), stdout);
ln = nextln(ln);
}
curln = ln;
2015-12-14 12:09:34 +00:00
}
static void
copy(int where)
{
2018-03-04 15:08:57 +00:00
if (!line1)
2015-12-14 12:09:34 +00:00
error("incorrect address");
curln = where;
while (line1 <= line2) {
inject(gettxt(line1), AFTER);
if (line2 >= curln)
line2 = nextln(line2);
line1 = nextln(line1);
if (line1 >= curln)
line1 = nextln(line1);
}
2015-12-14 12:09:34 +00:00
}
static void
quit(void)
{
clearbuf();
exit(exstatus);
}
static void
execsh(void)
{
2018-03-04 13:06:24 +00:00
static String cmd;
char *p;
int c, repl = 0;
2015-12-14 12:09:34 +00:00
skipblank();
if ((c = input()) != '!') {
back(c);
2018-03-04 13:06:24 +00:00
cmd.siz = 0;
} else if (cmd.siz) {
--cmd.siz;
2015-12-14 12:09:34 +00:00
repl = 1;
} else {
error("no previous command");
}
while ((c = input()) != EOF && c != '\n') {
2018-03-04 13:06:24 +00:00
if (c == '%' && (cmd.siz == 0 || cmd.str[cmd.siz - 1] != '\\')) {
2015-12-14 12:09:34 +00:00
if (savfname[0] == '\0')
error("no current filename");
repl = 1;
for (p = savfname; *p; ++p)
addchar(*p, &cmd);
2015-12-14 12:09:34 +00:00
} else {
addchar(c, &cmd);
2015-12-14 12:09:34 +00:00
}
}
addchar('\0', &cmd);
2015-12-14 12:09:34 +00:00
if (repl)
2018-03-04 13:06:24 +00:00
puts(cmd.str);
system(cmd.str);
if (optdiag)
puts("!");
2015-12-14 12:09:34 +00:00
}