2015-12-14 07:09:34 -05:00
|
|
|
/* See LICENSE file for copyright and license details. */
|
2018-03-04 14:17:54 -05:00
|
|
|
|
|
|
|
/*
|
|
|
|
* TODO: Multi-line commands don't work in global commands:
|
|
|
|
* o g/^line/a \
|
|
|
|
* line1
|
|
|
|
* .
|
|
|
|
* o Signal handling is broken
|
|
|
|
* o cat <<EOF | ed
|
|
|
|
* 0a
|
|
|
|
* int radix = 16;
|
|
|
|
* int Pflag;
|
|
|
|
* int Aflag;
|
|
|
|
* int vflag;
|
|
|
|
* int gflag;
|
|
|
|
* int uflag;
|
|
|
|
* int arflag;
|
|
|
|
*
|
|
|
|
* .
|
|
|
|
* ?radix?;/^$/-s/^/static /
|
2018-09-20 04:49:36 -04:00
|
|
|
* o cat <<EOF | ed
|
|
|
|
* 0a
|
|
|
|
* Line
|
|
|
|
* .
|
2018-09-22 06:30:21 -04:00
|
|
|
* s# *##
|
2018-09-20 04:49:36 -04:00
|
|
|
* o cat <<EOF | ed
|
|
|
|
* 0a
|
|
|
|
* line
|
|
|
|
* .
|
|
|
|
* 1g/^$/p
|
2018-03-04 14:17:54 -05:00
|
|
|
*/
|
|
|
|
|
2015-12-14 07:09:34 -05:00
|
|
|
#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>
|
|
|
|
|
2015-12-21 12:36:28 -05:00
|
|
|
#include "util.h"
|
2015-12-15 09:45:16 -05:00
|
|
|
|
2015-12-14 07:09:34 -05:00
|
|
|
#define REGEXSIZE 100
|
|
|
|
#define LINESIZE 80
|
|
|
|
#define NUMLINES 32
|
|
|
|
#define CACHESIZ 4096
|
2018-03-04 10:08:57 -05:00
|
|
|
#define AFTER 0
|
|
|
|
#define BEFORE 1
|
2015-12-14 07:09:34 -05:00
|
|
|
|
2018-03-04 07:59:48 -05:00
|
|
|
typedef struct {
|
|
|
|
char *str;
|
|
|
|
size_t cap;
|
|
|
|
size_t siz;
|
|
|
|
} String;
|
|
|
|
|
2015-12-14 07:09:34 -05:00
|
|
|
struct hline {
|
|
|
|
off_t seek;
|
|
|
|
char global;
|
|
|
|
int next, prev;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct undo {
|
2018-03-04 14:16:06 -05:00
|
|
|
int curln, lastln;
|
2015-12-14 07:09:34 -05:00
|
|
|
size_t nr, cap;
|
|
|
|
struct link {
|
|
|
|
int to1, from1;
|
|
|
|
int to2, from2;
|
|
|
|
} *vec;
|
|
|
|
};
|
|
|
|
|
2015-12-14 07:13:23 -05:00
|
|
|
static char *prompt = "*";
|
|
|
|
static regex_t *pattern;
|
|
|
|
static regmatch_t matchs[10];
|
2018-03-04 08:06:24 -05:00
|
|
|
static String lastre;
|
2015-12-14 07:09:34 -05:00
|
|
|
|
2015-12-14 07:13:23 -05:00
|
|
|
static int optverbose, optprompt, exstatus, optdiag = 1;
|
|
|
|
static int marks['z' - 'a'];
|
|
|
|
static int nlines, line1, line2;
|
2018-03-04 14:16:06 -05:00
|
|
|
static int curln, lastln, ocurln, olastln;
|
2015-12-14 07:13:23 -05:00
|
|
|
static jmp_buf savesp;
|
|
|
|
static char *lasterr;
|
|
|
|
static size_t idxsize, lastidx;
|
|
|
|
static struct hline *zero;
|
2018-03-04 08:06:24 -05:00
|
|
|
static String text;
|
2015-12-14 07:13:23 -05: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 08:01:21 -05:00
|
|
|
static String cmdline;
|
2015-12-14 07:13:23 -05:00
|
|
|
static char *ocmdline;
|
|
|
|
static int repidx;
|
|
|
|
static char *rhs;
|
|
|
|
static char *lastmatch;
|
|
|
|
static struct undo udata;
|
|
|
|
static int newcmd;
|
2016-01-23 03:11:33 -05:00
|
|
|
int eol, bol;
|
2015-12-14 07:09:34 -05:00
|
|
|
|
|
|
|
static void
|
2015-12-16 13:39:21 -05:00
|
|
|
discard(void)
|
2015-12-14 07:09:34 -05:00
|
|
|
{
|
|
|
|
int c;
|
|
|
|
|
2018-03-04 08:01:21 -05: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)
|
|
|
|
;
|
2015-12-16 13:39:21 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
static void undo(void);
|
|
|
|
|
|
|
|
static void
|
|
|
|
error(char *msg)
|
|
|
|
{
|
2015-12-14 07:09:34 -05:00
|
|
|
exstatus = 1;
|
|
|
|
lasterr = msg;
|
2018-02-24 03:08:28 -05:00
|
|
|
puts("?");
|
2015-12-14 07:09:34 -05:00
|
|
|
|
|
|
|
if (optverbose)
|
2018-02-24 03:08:28 -05:00
|
|
|
puts(msg);
|
2015-12-14 09:38:46 -05:00
|
|
|
if (!newcmd)
|
|
|
|
undo();
|
2015-12-16 13:39:21 -05:00
|
|
|
|
|
|
|
discard();
|
2015-12-14 09:38:46 -05:00
|
|
|
curln = ocurln;
|
2015-12-14 07:09:34 -05: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;
|
|
|
|
}
|
|
|
|
|
2018-03-04 07:59:48 -05:00
|
|
|
static char *
|
2018-03-04 09:15:54 -05:00
|
|
|
addchar(char c, String *s)
|
2018-03-04 07:59:48 -05:00
|
|
|
{
|
|
|
|
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 07:09:34 -05:00
|
|
|
static int
|
|
|
|
input(void)
|
|
|
|
{
|
|
|
|
int c;
|
|
|
|
|
|
|
|
if (repidx >= 0)
|
|
|
|
return ocmdline[repidx++];
|
|
|
|
|
|
|
|
if ((c = getchar()) != EOF)
|
2018-03-04 09:15:54 -05:00
|
|
|
addchar(c, &cmdline);
|
2015-12-14 07:09:34 -05:00
|
|
|
return c;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
back(int c)
|
|
|
|
{
|
|
|
|
if (repidx > 0) {
|
|
|
|
--repidx;
|
|
|
|
} else {
|
|
|
|
ungetc(c, stdin);
|
|
|
|
if (c != EOF)
|
2018-03-04 08:01:21 -05:00
|
|
|
--cmdline.siz;
|
2015-12-14 07:09:34 -05:00
|
|
|
}
|
|
|
|
return c;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
makeline(char *s, int *off)
|
|
|
|
{
|
|
|
|
struct hline *lp;
|
|
|
|
size_t len;
|
|
|
|
char c, *begin = s;
|
|
|
|
|
|
|
|
if (lastidx >= idxsize) {
|
2018-03-06 08:38:23 -05:00
|
|
|
lp = NULL;
|
|
|
|
if (idxsize <= SIZE_MAX - NUMLINES)
|
|
|
|
lp = realloc(zero, (idxsize + NUMLINES) * sizeof(*lp));
|
|
|
|
if (!lp)
|
2015-12-14 07:09:34 -05:00
|
|
|
error("out of memory");
|
|
|
|
idxsize += NUMLINES;
|
|
|
|
zero = lp;
|
|
|
|
}
|
|
|
|
lp = zero + lastidx;
|
|
|
|
|
2016-01-06 16:04:29 -05:00
|
|
|
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 07:09:34 -05:00
|
|
|
if (off)
|
|
|
|
*off = len;
|
|
|
|
++lastidx;
|
|
|
|
return lp - zero;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
getindex(int line)
|
|
|
|
{
|
|
|
|
struct hline *lp;
|
|
|
|
int n;
|
|
|
|
|
2016-10-09 19:10:20 -04:00
|
|
|
if (line == -1)
|
|
|
|
line = 0;
|
|
|
|
for (n = 0, lp = zero; n != line; n++)
|
2015-12-14 07:09:34 -05: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 08:06:24 -05:00
|
|
|
text.siz = 0;
|
2016-01-06 16:04:29 -05:00
|
|
|
off = lp->seek;
|
|
|
|
|
|
|
|
if (off == (off_t) -1)
|
2018-03-04 09:15:54 -05:00
|
|
|
return addchar('\0', &text);
|
2015-12-14 07:09:34 -05: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;
|
2018-03-04 09:15:54 -05:00
|
|
|
addchar(*p, &text);
|
2015-12-14 07:09:34 -05:00
|
|
|
}
|
|
|
|
if (csize && p == buf + csize)
|
|
|
|
goto repeat;
|
|
|
|
|
2018-03-04 09:15:54 -05:00
|
|
|
addchar('\n', &text);
|
|
|
|
addchar('\0', &text);
|
2018-03-04 08:06:24 -05:00
|
|
|
return text.str;
|
2015-12-14 07:09:34 -05: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;
|
2016-01-01 05:17:00 -05:00
|
|
|
modflag = 0;
|
2015-12-14 07:09:34 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2018-03-04 14:16:06 -05:00
|
|
|
newundo(int from1, int from2)
|
2015-12-14 07:09:34 -05:00
|
|
|
{
|
|
|
|
struct link *p;
|
|
|
|
|
|
|
|
if (newcmd) {
|
|
|
|
clearundo();
|
|
|
|
udata.curln = ocurln;
|
2018-03-04 14:16:06 -05:00
|
|
|
udata.lastln = olastln;
|
2015-12-14 07:09:34 -05: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;
|
2018-03-04 14:16:06 -05:00
|
|
|
}
|
2015-12-14 07:09:34 -05:00
|
|
|
|
2018-03-04 14:16:06 -05:00
|
|
|
/*
|
|
|
|
* relink: to1 <- from1
|
|
|
|
* from2 -> to2
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
relink(int to1, int from1, int from2, int to2)
|
|
|
|
{
|
|
|
|
newundo(from1, from2);
|
2015-12-14 07:09:34 -05: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 14:35:26 -05:00
|
|
|
return;
|
2018-03-04 14:16:06 -05:00
|
|
|
for (p = &udata.vec[udata.nr-1]; udata.nr > 0; --p) {
|
|
|
|
--udata.nr;
|
2015-12-14 07:09:34 -05: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;
|
2018-03-04 14:16:06 -05:00
|
|
|
lastln = udata.lastln;
|
2015-12-14 07:09:34 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2018-03-04 10:08:57 -05:00
|
|
|
inject(char *s, int where)
|
2015-12-14 07:09:34 -05:00
|
|
|
{
|
|
|
|
int off, k, begin, end;
|
|
|
|
|
2018-03-04 10:08:57 -05:00
|
|
|
if (where == BEFORE) {
|
2016-10-09 19:12:46 -04:00
|
|
|
begin = getindex(curln-1);
|
|
|
|
end = getindex(nextln(curln-1));
|
|
|
|
} else {
|
|
|
|
begin = getindex(curln);
|
|
|
|
end = getindex(nextln(curln));
|
|
|
|
}
|
2015-12-14 07:09:34 -05: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
|
2016-12-27 07:07:02 -05:00
|
|
|
clearbuf(void)
|
2015-12-14 07:09:34 -05:00
|
|
|
{
|
|
|
|
if (scratch)
|
|
|
|
close(scratch);
|
|
|
|
remove(tmpname);
|
|
|
|
free(zero);
|
|
|
|
zero = NULL;
|
|
|
|
scratch = csize = idxsize = lastidx = curln = lastln = 0;
|
2016-01-01 05:17:01 -05:00
|
|
|
modflag = lastln = curln = 0;
|
2015-12-14 07:09:34 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2016-12-27 07:07:02 -05:00
|
|
|
setscratch(void)
|
2015-12-14 07:09:34 -05:00
|
|
|
{
|
2016-01-02 05:32:44 -05:00
|
|
|
int r, k;
|
2016-01-01 12:25:58 -05:00
|
|
|
char *dir;
|
2015-12-14 07:09:34 -05:00
|
|
|
|
|
|
|
clearbuf();
|
|
|
|
clearundo();
|
2016-01-01 12:25:58 -05:00
|
|
|
if ((dir = getenv("TMPDIR")) == NULL)
|
2016-01-02 05:32:44 -05:00
|
|
|
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");
|
2016-01-02 04:44:06 -05:00
|
|
|
if ((scratch = mkstemp(tmpname)) < 0)
|
2016-01-01 12:25:58 -05:00
|
|
|
error("failed to create scratch file");
|
2016-01-06 16:04:29 -05:00
|
|
|
if ((k = makeline(NULL, NULL)))
|
2015-12-14 07:09:34 -05: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 08:06:24 -05:00
|
|
|
eol = bol = bracket = lastre.siz = 0;
|
2015-12-14 07:09:34 -05:00
|
|
|
for (n = 0;; ++n) {
|
|
|
|
if ((c = input()) == delim && !bracket)
|
|
|
|
break;
|
2016-01-23 03:11:33 -05:00
|
|
|
if (c == '^') {
|
|
|
|
bol = 1;
|
|
|
|
} else if (c == '$') {
|
|
|
|
eol = 1;
|
|
|
|
} else if (c == '\n' || c == EOF) {
|
2015-12-14 07:09:34 -05:00
|
|
|
back(c);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (c == '\\') {
|
2018-03-04 09:15:54 -05:00
|
|
|
addchar(c, &lastre);
|
2015-12-14 07:09:34 -05:00
|
|
|
c = input();
|
|
|
|
} else if (c == '[') {
|
|
|
|
bracket = 1;
|
|
|
|
} else if (c == ']') {
|
|
|
|
bracket = 0;
|
|
|
|
}
|
2018-03-04 09:15:54 -05:00
|
|
|
addchar(c, &lastre);
|
2015-12-14 07:09:34 -05:00
|
|
|
}
|
|
|
|
if (n == 0) {
|
|
|
|
if (!pattern)
|
|
|
|
error("no previous pattern");
|
|
|
|
return;
|
|
|
|
}
|
2018-03-04 09:15:54 -05:00
|
|
|
addchar('\0', &lastre);
|
2015-12-14 07:09:34 -05:00
|
|
|
|
|
|
|
if (pattern)
|
|
|
|
regfree(pattern);
|
|
|
|
if (!pattern && (!(pattern = malloc(sizeof(*pattern)))))
|
|
|
|
error("out of memory");
|
2018-03-04 08:06:24 -05:00
|
|
|
if ((ret = regcomp(pattern, lastre.str, REG_NEWLINE))) {
|
2015-12-14 07:09:34 -05: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)
|
|
|
|
{
|
2016-01-07 05:37:56 -05:00
|
|
|
regoff_t off = matchs[0].rm_eo;
|
|
|
|
|
|
|
|
if (!regexec(pattern, lastmatch + off, 10, matchs, 0)) {
|
|
|
|
lastmatch += off;
|
|
|
|
return 1;
|
|
|
|
}
|
2016-12-27 07:07:02 -05:00
|
|
|
|
2016-01-07 05:37:56 -05:00
|
|
|
return 0;
|
2015-12-14 07:09:34 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
search(int way)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
i = curln;
|
|
|
|
do {
|
|
|
|
i = (way == '?') ? prevln(i) : nextln(i);
|
2016-07-24 21:04:27 -04:00
|
|
|
if (i > 0 && match(i))
|
2015-12-14 07:09:34 -05:00
|
|
|
return i;
|
|
|
|
} while (i != curln);
|
|
|
|
|
|
|
|
error("invalid address");
|
2015-12-15 04:04:00 -05:00
|
|
|
return -1; /* not reached */
|
2015-12-14 07:09:34 -05: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");
|
2015-12-15 04:04:00 -05:00
|
|
|
return -1; /* not reached */
|
2015-12-14 07:09:34 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
linenum(int *line)
|
|
|
|
{
|
|
|
|
int ln, c;
|
|
|
|
|
|
|
|
skipblank();
|
|
|
|
|
|
|
|
switch (c = input()) {
|
|
|
|
case '.':
|
|
|
|
ln = curln;
|
|
|
|
break;
|
|
|
|
case '\'':
|
|
|
|
skipblank();
|
2016-07-15 12:57:50 -04:00
|
|
|
if (!islower(c = input()))
|
2015-12-14 07:09:34 -05:00
|
|
|
error("invalid mark character");
|
2016-07-15 12:57:50 -04:00
|
|
|
if (!(ln = marks[c - 'a']))
|
2015-12-14 07:09:34 -05: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");
|
2015-12-15 04:04:00 -05:00
|
|
|
return -1; /* not reached */
|
2015-12-14 07:09:34 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2016-12-27 07:07:02 -05:00
|
|
|
getlst(void)
|
2015-12-14 07:09:34 -05: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
|
2016-12-27 07:07:02 -05:00
|
|
|
dowrite(const char *fname, int trunc)
|
2015-12-14 07:09:34 -05:00
|
|
|
{
|
|
|
|
FILE *fp;
|
2015-12-14 09:18:33 -05:00
|
|
|
int i, line;
|
2015-12-14 07:09:34 -05:00
|
|
|
|
|
|
|
if (!(fp = fopen(fname, (trunc) ? "w" : "a")))
|
|
|
|
error("input/output error");
|
|
|
|
|
2015-12-14 09:18:33 -05:00
|
|
|
line = curln;
|
2015-12-14 07:09:34 -05:00
|
|
|
for (i = line1; i <= line2; ++i)
|
|
|
|
fputs(gettxt(i), fp);
|
|
|
|
|
|
|
|
curln = line2;
|
|
|
|
if (fclose(fp))
|
|
|
|
error("input/output error");
|
2017-01-10 05:28:58 -05:00
|
|
|
strcpy(savfname, fname);
|
2015-12-14 07:09:34 -05:00
|
|
|
modflag = 0;
|
2015-12-14 09:18:33 -05:00
|
|
|
curln = line;
|
2015-12-14 07:09:34 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2016-12-27 07:07:02 -05:00
|
|
|
doread(const char *fname)
|
2015-12-14 07:09:34 -05: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);
|
2016-01-01 11:40:32 -05:00
|
|
|
if ((fp = fopen(fname, "r")) == NULL)
|
|
|
|
error("cannot open input file");
|
2015-12-14 07:09:34 -05: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 10:08:57 -05:00
|
|
|
inject(s, AFTER);
|
2015-12-14 07:09:34 -05:00
|
|
|
}
|
2016-01-01 05:17:03 -05:00
|
|
|
if (optdiag)
|
|
|
|
printf("%zu\n", cnt);
|
2015-12-14 07:09:34 -05: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)
|
2018-02-24 03:08:28 -05:00
|
|
|
puts(lasterr);
|
2015-12-14 07:09:34 -05: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 *
|
2016-01-01 11:40:34 -05:00
|
|
|
getfname(char comm)
|
2015-12-14 07:09:34 -05: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';
|
2016-01-01 11:40:34 -05:00
|
|
|
if (savfname[0] == '\0' || comm == 'e' || comm == 'f')
|
2017-01-10 02:46:48 -05:00
|
|
|
strcpy(savfname, fname);
|
2015-12-14 07:09:34 -05:00
|
|
|
return fname;
|
|
|
|
}
|
2016-12-27 07:07:02 -05:00
|
|
|
|
2015-12-15 04:04:00 -05:00
|
|
|
return NULL; /* not reached */
|
2015-12-14 07:09:34 -05: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 10:08:57 -05:00
|
|
|
inject(s, AFTER);
|
2015-12-14 07:09:34 -05: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;
|
|
|
|
|
2015-12-15 04:04:00 -05:00
|
|
|
if (!line1 || (where >= line1 && where <= line2))
|
2015-12-14 07:09:34 -05: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;
|
2018-03-04 08:06:24 -05:00
|
|
|
static String s;
|
2015-12-14 07:09:34 -05:00
|
|
|
|
2018-03-04 08:06:24 -05:00
|
|
|
free(s.str);
|
|
|
|
s.siz = s.cap = 0;
|
|
|
|
for (i = line1;; i = nextln(i)) {
|
2015-12-14 07:09:34 -05:00
|
|
|
for (t = gettxt(i); (c = *t) != '\n'; ++t)
|
2018-03-04 09:15:54 -05:00
|
|
|
addchar(*t, &s);
|
2016-10-09 19:10:20 -04:00
|
|
|
if (i == line2)
|
|
|
|
break;
|
2015-12-14 07:09:34 -05:00
|
|
|
}
|
|
|
|
|
2018-03-04 09:15:54 -05:00
|
|
|
addchar('\n', &s);
|
|
|
|
addchar('\0', &s);
|
2015-12-14 07:09:34 -05:00
|
|
|
delete(line1, line2);
|
2018-03-04 10:08:57 -05:00
|
|
|
inject(s.str, BEFORE);
|
2018-03-04 08:06:24 -05:00
|
|
|
free(s.str);
|
2015-12-14 07:09:34 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
scroll(int num)
|
|
|
|
{
|
2018-03-04 11:10:17 -05:00
|
|
|
int max, ln, cnt;
|
2015-12-14 07:09:34 -05:00
|
|
|
|
|
|
|
if (!line1 || line1 == lastln)
|
|
|
|
error("incorrect address");
|
|
|
|
|
2018-03-04 11:10:17 -05: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 07:09:34 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
copy(int where)
|
|
|
|
{
|
|
|
|
|
2018-03-04 10:08:57 -05:00
|
|
|
if (!line1)
|
2015-12-14 07:09:34 -05:00
|
|
|
error("incorrect address");
|
|
|
|
curln = where;
|
|
|
|
|
2018-03-04 11:08:14 -05:00
|
|
|
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 07:09:34 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
quit(void)
|
|
|
|
{
|
|
|
|
clearbuf();
|
|
|
|
exit(exstatus);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
execsh(void)
|
|
|
|
{
|
2018-03-04 08:06:24 -05:00
|
|
|
static String cmd;
|
2018-03-04 08:56:44 -05:00
|
|
|
char *p;
|
|
|
|
int c, repl = 0;
|
2015-12-14 07:09:34 -05:00
|
|
|
|
|
|
|
skipblank();
|
|
|
|
if ((c = input()) != '!') {
|
|
|
|
back(c);
|
2018-03-04 08:06:24 -05:00
|
|
|
cmd.siz = 0;
|
|
|
|
} else if (cmd.siz) {
|
|
|
|
--cmd.siz;
|
2015-12-14 07:09:34 -05:00
|
|
|
repl = 1;
|
|
|
|
} else {
|
|
|
|
error("no previous command");
|
|
|
|
}
|
|
|
|
|
|
|
|
while ((c = input()) != EOF && c != '\n') {
|
2018-03-04 08:06:24 -05:00
|
|
|
if (c == '%' && (cmd.siz == 0 || cmd.str[cmd.siz - 1] != '\\')) {
|
2015-12-14 07:09:34 -05:00
|
|
|
if (savfname[0] == '\0')
|
|
|
|
error("no current filename");
|
|
|
|
repl = 1;
|
|
|
|
for (p = savfname; *p; ++p)
|
2018-03-04 09:15:54 -05:00
|
|
|
addchar(*p, &cmd);
|
2015-12-14 07:09:34 -05:00
|
|
|
} else {
|
2018-03-04 09:15:54 -05:00
|
|
|
addchar(c, &cmd);
|
2015-12-14 07:09:34 -05:00
|
|
|
}
|
|
|
|
}
|
2018-03-04 09:15:54 -05:00
|
|
|
addchar('\0', &cmd);
|
2015-12-14 07:09:34 -05:00
|
|
|
|
|
|
|
if (repl)
|
2018-03-04 08:06:24 -05:00
|
|
|
puts(cmd.str);
|
|
|
|
system(cmd.str);
|
2016-01-01 11:40:33 -05:00
|
|
|
if (optdiag)
|
|
|
|
puts("!");
|
2015-12-14 07:09:34 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
getrhs(int delim)
|
|
|
|
{
|
|
|
|
int c;
|
2018-03-04 08:56:44 -05:00
|
|
|
static String s;
|
2015-12-14 07:09:34 -05:00
|
|
|
|
2018-03-04 08:56:44 -05:00
|
|
|
free(s.str);
|
|
|
|
s.str = NULL;
|
|
|
|
s.siz = s.cap = 0;
|
2016-07-24 21:04:28 -04:00
|
|
|
while ((c = input()) != '\n' && c != EOF && c != delim)
|
2018-03-04 09:15:54 -05:00
|
|
|
addchar(c, &s);
|
|
|
|
addchar('\0', &s);
|
2015-12-14 07:09:34 -05:00
|
|
|
if (c == EOF)
|
|
|
|
error("invalid pattern delimiter");
|
|
|
|
if (c == '\n') {
|
|
|
|
pflag = 'p';
|
|
|
|
back(c);
|
|
|
|
}
|
|
|
|
|
2018-03-04 08:56:44 -05:00
|
|
|
if (!strcmp("%", s.str)) {
|
|
|
|
free(s.str);
|
2015-12-14 07:09:34 -05:00
|
|
|
if (!rhs)
|
|
|
|
error("no previous substitution");
|
|
|
|
} else {
|
|
|
|
free(rhs);
|
2018-03-04 08:56:44 -05:00
|
|
|
rhs = s.str;
|
2015-12-14 07:09:34 -05:00
|
|
|
}
|
2018-03-04 08:56:44 -05:00
|
|
|
s.str = NULL;
|
2015-12-14 07:09:34 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
getnth(void)
|
|
|
|
{
|
|
|
|
int c;
|
|
|
|
|
|
|
|
if ((c = input()) == 'g') {
|
|
|
|
return -1;
|
|
|
|
} else if (isdigit(c)) {
|
|
|
|
if (c == '0')
|
|
|
|
return -1;
|
|
|
|
return c - '0';
|
|
|
|
} else {
|
|
|
|
back(c);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2018-03-04 08:06:24 -05:00
|
|
|
addpre(String *s)
|
2015-12-14 07:09:34 -05:00
|
|
|
{
|
|
|
|
char *p;
|
|
|
|
|
|
|
|
for (p = lastmatch; p < lastmatch + matchs[0].rm_so; ++p)
|
2018-03-04 09:15:54 -05:00
|
|
|
addchar(*p, s);
|
2015-12-14 07:09:34 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2018-03-04 08:06:24 -05:00
|
|
|
addpost(String *s)
|
2015-12-14 07:09:34 -05:00
|
|
|
{
|
|
|
|
char c, *p;
|
|
|
|
|
2015-12-15 04:04:00 -05:00
|
|
|
for (p = lastmatch + matchs[0].rm_eo; (c = *p); ++p)
|
2018-03-04 09:15:54 -05:00
|
|
|
addchar(c, s);
|
|
|
|
addchar('\0', s);
|
2015-12-14 07:09:34 -05:00
|
|
|
}
|
|
|
|
|
2016-01-07 05:44:49 -05:00
|
|
|
static int
|
2018-03-04 08:06:24 -05:00
|
|
|
addsub(String *s, int nth, int nmatch)
|
2015-12-14 07:09:34 -05:00
|
|
|
{
|
|
|
|
char *end, *q, *p, c;
|
|
|
|
int sub;
|
|
|
|
|
2016-01-07 05:44:49 -05:00
|
|
|
if (nth != nmatch && nth != -1) {
|
|
|
|
q = lastmatch + matchs[0].rm_so;
|
|
|
|
end = lastmatch + matchs[0].rm_eo;
|
|
|
|
while (q < end)
|
2018-03-04 09:15:54 -05:00
|
|
|
addchar(*q++, s);
|
2016-01-07 05:44:49 -05:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-12-15 04:04:00 -05:00
|
|
|
for (p = rhs; (c = *p); ++p) {
|
2015-12-14 07:09:34 -05:00
|
|
|
switch (c) {
|
|
|
|
case '&':
|
|
|
|
sub = 0;
|
|
|
|
goto copy_match;
|
|
|
|
case '\\':
|
|
|
|
if ((c = *++p) == '\0')
|
2016-01-07 05:44:49 -05:00
|
|
|
return 1;
|
2015-12-14 07:09:34 -05:00
|
|
|
if (!isdigit(c))
|
|
|
|
goto copy_char;
|
|
|
|
sub = c - '0';
|
|
|
|
copy_match:
|
|
|
|
q = lastmatch + matchs[sub].rm_so;
|
|
|
|
end = lastmatch + matchs[sub].rm_eo;
|
|
|
|
while (q < end)
|
2018-03-04 09:15:54 -05:00
|
|
|
addchar(*q++, s);
|
2015-12-14 07:09:34 -05:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
copy_char:
|
2018-03-04 09:15:54 -05:00
|
|
|
addchar(c, s);
|
2015-12-14 07:09:34 -05:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2016-01-07 05:44:49 -05:00
|
|
|
return 1;
|
2015-12-14 07:09:34 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
subline(int num, int nth)
|
|
|
|
{
|
2016-01-07 05:44:49 -05:00
|
|
|
int i, m, changed;
|
2018-03-04 08:06:24 -05:00
|
|
|
static String s;
|
2015-12-14 07:09:34 -05:00
|
|
|
|
2018-03-04 08:06:24 -05:00
|
|
|
i = changed = s.siz = 0;
|
2016-01-23 03:11:33 -05:00
|
|
|
for (m = match(num); m; m = rematch(num)) {
|
2018-03-04 08:06:24 -05:00
|
|
|
addpre(&s);
|
|
|
|
changed |= addsub(&s, nth, ++i);
|
2016-01-23 03:11:33 -05:00
|
|
|
if (eol || bol)
|
|
|
|
break;
|
2015-12-14 07:09:34 -05:00
|
|
|
}
|
|
|
|
if (!changed)
|
|
|
|
return;
|
2018-03-04 08:06:24 -05:00
|
|
|
addpost(&s);
|
2015-12-14 07:09:34 -05:00
|
|
|
delete(num, num);
|
|
|
|
curln = prevln(num);
|
2018-03-04 10:08:57 -05:00
|
|
|
inject(s.str, AFTER);
|
2015-12-14 07:09:34 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
subst(int nth)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = line1; i <= line2; ++i)
|
|
|
|
subline(i, nth);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
docmd(void)
|
|
|
|
{
|
2016-01-01 11:40:34 -05:00
|
|
|
char cmd;
|
2015-12-14 07:09:34 -05:00
|
|
|
int rep = 0, c, line3, num, trunc;
|
|
|
|
|
|
|
|
repeat:
|
|
|
|
skipblank();
|
|
|
|
cmd = input();
|
|
|
|
trunc = pflag = 0;
|
|
|
|
switch (cmd) {
|
|
|
|
case '&':
|
|
|
|
skipblank();
|
|
|
|
chkprint(0);
|
|
|
|
if (!ocmdline)
|
|
|
|
error("no previous command");
|
|
|
|
rep = 1;
|
|
|
|
repidx = 0;
|
|
|
|
getlst();
|
|
|
|
goto repeat;
|
|
|
|
case '!':
|
|
|
|
execsh();
|
|
|
|
break;
|
|
|
|
case EOF:
|
2018-03-04 08:01:21 -05:00
|
|
|
if (cmdline.siz == 0)
|
2015-12-14 07:09:34 -05:00
|
|
|
quit();
|
|
|
|
case '\n':
|
|
|
|
if (gflag && uflag)
|
|
|
|
return;
|
|
|
|
num = gflag ? curln : curln+1;
|
|
|
|
deflines(num, num);
|
|
|
|
pflag = 'p';
|
|
|
|
goto print;
|
|
|
|
case 'l':
|
|
|
|
case 'n':
|
|
|
|
case 'p':
|
|
|
|
back(cmd);
|
|
|
|
chkprint(1);
|
|
|
|
deflines(curln, curln);
|
|
|
|
goto print;
|
|
|
|
case 'g':
|
|
|
|
case 'G':
|
|
|
|
case 'v':
|
|
|
|
case 'V':
|
|
|
|
error("cannot nest global commands");
|
|
|
|
case 'H':
|
|
|
|
if (nlines > 0)
|
|
|
|
goto unexpected;
|
|
|
|
chkprint(0);
|
|
|
|
optverbose ^= 1;
|
|
|
|
break;
|
|
|
|
case 'h':
|
|
|
|
if (nlines > 0)
|
|
|
|
goto unexpected;
|
|
|
|
chkprint(0);
|
|
|
|
dohelp();
|
|
|
|
break;
|
|
|
|
case 'w':
|
|
|
|
trunc = 1;
|
|
|
|
case 'W':
|
|
|
|
deflines(nextln(0), lastln);
|
2016-01-01 11:40:34 -05:00
|
|
|
dowrite(getfname(cmd), trunc);
|
2015-12-14 07:09:34 -05:00
|
|
|
break;
|
|
|
|
case 'r':
|
|
|
|
if (nlines > 1)
|
|
|
|
goto bad_address;
|
|
|
|
deflines(lastln, lastln);
|
2016-01-01 11:40:34 -05:00
|
|
|
doread(getfname(cmd));
|
2015-12-14 07:09:34 -05:00
|
|
|
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 'u':
|
|
|
|
if (nlines > 0)
|
|
|
|
goto bad_address;
|
|
|
|
chkprint(1);
|
2015-12-31 14:35:26 -05:00
|
|
|
if (udata.nr == 0)
|
|
|
|
error("nothing to undo");
|
2015-12-14 07:09:34 -05:00
|
|
|
undo();
|
|
|
|
break;
|
|
|
|
case 's':
|
|
|
|
deflines(curln, curln);
|
|
|
|
c = input();
|
|
|
|
compile(c);
|
|
|
|
getrhs(c);
|
|
|
|
num = getnth();
|
|
|
|
chkprint(1);
|
|
|
|
subst(num);
|
|
|
|
break;
|
|
|
|
case 'i':
|
|
|
|
if (nlines > 1)
|
|
|
|
goto bad_address;
|
|
|
|
chkprint(1);
|
|
|
|
deflines(curln, curln);
|
|
|
|
if (!line1)
|
2016-11-03 11:19:35 -04:00
|
|
|
line1++;
|
2015-12-14 07:09:34 -05:00
|
|
|
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);
|
2016-10-09 19:11:01 -04:00
|
|
|
if (line1 != line2 && curln != 0)
|
|
|
|
join();
|
2015-12-14 07:09:34 -05:00
|
|
|
break;
|
|
|
|
case 'z':
|
|
|
|
if (nlines > 1)
|
|
|
|
goto bad_address;
|
|
|
|
if (isdigit(back(input())))
|
|
|
|
num = getnum();
|
|
|
|
else
|
|
|
|
num = 24;
|
|
|
|
chkprint(1);
|
|
|
|
scroll(num);
|
|
|
|
break;
|
|
|
|
case 'k':
|
|
|
|
if (nlines > 1)
|
|
|
|
goto bad_address;
|
|
|
|
if (!islower(c = input()))
|
|
|
|
error("invalid mark character");
|
|
|
|
chkprint(1);
|
|
|
|
deflines(curln, curln);
|
2016-07-15 12:57:50 -04:00
|
|
|
marks[c - 'a'] = line1;
|
2015-12-14 07:09:34 -05:00
|
|
|
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;
|
2016-01-01 11:40:34 -05:00
|
|
|
if (back(input()) != '\n')
|
|
|
|
getfname(cmd);
|
2015-12-14 07:09:34 -05:00
|
|
|
else
|
2016-01-01 11:40:34 -05:00
|
|
|
puts(savfname);
|
|
|
|
chkprint(0);
|
2015-12-14 07:09:34 -05:00
|
|
|
break;
|
|
|
|
case 'E':
|
|
|
|
modflag = 0;
|
|
|
|
case 'e':
|
|
|
|
if (nlines > 0)
|
|
|
|
goto unexpected;
|
|
|
|
if (modflag)
|
|
|
|
goto modified;
|
2016-01-01 11:40:34 -05:00
|
|
|
getfname(cmd);
|
2015-12-15 09:13:21 -05:00
|
|
|
setscratch();
|
2015-12-14 07:09:34 -05:00
|
|
|
deflines(curln, curln);
|
|
|
|
doread(savfname);
|
|
|
|
clearundo();
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
error("unknown command");
|
|
|
|
bad_address:
|
|
|
|
error("invalid address");
|
|
|
|
modified:
|
|
|
|
modflag = 0;
|
|
|
|
error("warning: file modified");
|
|
|
|
unexpected:
|
|
|
|
error("unexpected address");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!pflag)
|
|
|
|
goto save_last_cmd;
|
|
|
|
|
|
|
|
line1 = line2 = curln;
|
|
|
|
print:
|
|
|
|
doprint();
|
|
|
|
|
|
|
|
save_last_cmd:
|
|
|
|
if (!uflag)
|
|
|
|
repidx = 0;
|
|
|
|
if (rep)
|
|
|
|
return;
|
|
|
|
free(ocmdline);
|
2018-03-04 09:15:54 -05:00
|
|
|
addchar('\0', &cmdline);
|
2018-03-04 08:01:21 -05:00
|
|
|
if ((ocmdline = strdup(cmdline.str)) == NULL)
|
2015-12-14 07:09:34 -05:00
|
|
|
error("out of memory");
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
chkglobal(void)
|
|
|
|
{
|
|
|
|
int delim, c, dir, i, v;
|
|
|
|
|
|
|
|
uflag = 1;
|
|
|
|
gflag = 0;
|
|
|
|
skipblank();
|
|
|
|
|
|
|
|
switch (c = input()) {
|
|
|
|
case 'g':
|
|
|
|
uflag = 0;
|
|
|
|
case 'G':
|
|
|
|
dir = 1;
|
|
|
|
break;
|
|
|
|
case 'v':
|
|
|
|
uflag = 0;
|
|
|
|
case 'V':
|
|
|
|
dir = 0;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
back(c);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
gflag = 1;
|
|
|
|
deflines(nextln(0), lastln);
|
|
|
|
delim = input();
|
|
|
|
compile(delim);
|
|
|
|
|
|
|
|
for (i = 1; i <= lastln; ++i) {
|
|
|
|
if (i >= line1 && i <= line2)
|
|
|
|
v = match(i) == dir;
|
|
|
|
else
|
|
|
|
v = 0;
|
|
|
|
setglobal(i, v);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
doglobal(void)
|
|
|
|
{
|
2018-03-04 11:10:17 -05:00
|
|
|
int cnt, ln, k;
|
2015-12-14 07:09:34 -05:00
|
|
|
|
|
|
|
skipblank();
|
2018-03-04 08:01:21 -05:00
|
|
|
cmdline.siz = 0;
|
2015-12-14 07:09:34 -05:00
|
|
|
gflag = 1;
|
|
|
|
if (uflag)
|
|
|
|
chkprint(0);
|
|
|
|
|
2018-03-04 11:10:17 -05:00
|
|
|
ln = line1;
|
|
|
|
for (cnt = 0; cnt < lastln; ) {
|
|
|
|
k = getindex(ln);
|
|
|
|
if (zero[k].global) {
|
|
|
|
zero[k].global = 0;
|
|
|
|
curln = ln;
|
|
|
|
nlines = 0;
|
|
|
|
if (uflag) {
|
|
|
|
line1 = line2 = ln;
|
|
|
|
pflag = 0;
|
|
|
|
doprint();
|
|
|
|
}
|
|
|
|
getlst();
|
|
|
|
docmd();
|
|
|
|
} else {
|
|
|
|
cnt++;
|
|
|
|
ln = nextln(ln);
|
2015-12-14 07:09:34 -05:00
|
|
|
}
|
|
|
|
}
|
2015-12-16 15:17:52 -05:00
|
|
|
discard(); /* cover the case of not matching anything */
|
2015-12-14 07:09:34 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
usage(void)
|
|
|
|
{
|
2015-12-21 12:36:28 -05:00
|
|
|
eprintf("usage: %s [-s] [-p] [file]\n", argv0);
|
2015-12-14 07:09:34 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
sigintr(int n)
|
|
|
|
{
|
|
|
|
signal(SIGINT, sigintr);
|
|
|
|
error("interrupt");
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
sighup(int dummy)
|
|
|
|
{
|
|
|
|
int n;
|
|
|
|
char *home = getenv("HOME"), fname[FILENAME_MAX];
|
|
|
|
|
|
|
|
if (modflag) {
|
|
|
|
line1 = nextln(0);
|
|
|
|
line2 = lastln;
|
|
|
|
if (!setjmp(savesp)) {
|
|
|
|
dowrite("ed.hup", 1);
|
|
|
|
} else if (home && !setjmp(savesp)) {
|
|
|
|
n = snprintf(fname,
|
|
|
|
sizeof(fname), "%s/%s", home, "ed.hup");
|
|
|
|
if (n < sizeof(fname) && n > 0)
|
|
|
|
dowrite(fname, 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
exstatus = 1;
|
|
|
|
quit();
|
|
|
|
}
|
|
|
|
|
2015-12-16 15:21:55 -05:00
|
|
|
static void
|
|
|
|
edit(void)
|
|
|
|
{
|
|
|
|
for (;;) {
|
|
|
|
newcmd = 1;
|
|
|
|
ocurln = curln;
|
2018-03-04 14:16:06 -05:00
|
|
|
olastln = lastln;
|
2018-03-04 08:01:21 -05:00
|
|
|
cmdline.siz = 0;
|
2015-12-16 15:21:55 -05:00
|
|
|
repidx = -1;
|
2018-01-28 09:51:38 -05:00
|
|
|
if (optprompt) {
|
2015-12-16 15:21:55 -05:00
|
|
|
fputs(prompt, stdout);
|
2018-01-28 09:51:38 -05:00
|
|
|
fflush(stdout);
|
|
|
|
}
|
2015-12-16 15:21:55 -05:00
|
|
|
getlst();
|
|
|
|
chkglobal() ? doglobal() : docmd();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-01-01 05:17:02 -05:00
|
|
|
static void
|
|
|
|
init(char *fname)
|
|
|
|
{
|
|
|
|
size_t len;
|
|
|
|
|
|
|
|
setscratch();
|
|
|
|
if (!fname)
|
|
|
|
return;
|
|
|
|
if ((len = strlen(fname)) >= FILENAME_MAX || len == 0)
|
|
|
|
error("incorrect filename");
|
|
|
|
memcpy(savfname, fname, len);
|
|
|
|
doread(fname);
|
|
|
|
clearundo();
|
|
|
|
}
|
|
|
|
|
2015-12-14 07:09:34 -05:00
|
|
|
int
|
|
|
|
main(int argc, char *argv[])
|
|
|
|
{
|
2015-12-15 09:45:16 -05:00
|
|
|
ARGBEGIN {
|
|
|
|
case 'p':
|
|
|
|
prompt = EARGF(usage());
|
|
|
|
optprompt = 1;
|
|
|
|
break;
|
|
|
|
case 's':
|
|
|
|
optdiag = 0;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
usage();
|
|
|
|
} ARGEND
|
2015-12-14 07:09:34 -05:00
|
|
|
|
2015-12-15 09:45:16 -05:00
|
|
|
if (argc > 1)
|
|
|
|
usage();
|
2015-12-14 07:09:34 -05:00
|
|
|
|
2018-03-04 05:58:07 -05:00
|
|
|
if (!setjmp(savesp)) {
|
|
|
|
signal(SIGINT, sigintr);
|
|
|
|
signal(SIGHUP, sighup);
|
|
|
|
signal(SIGQUIT, SIG_IGN);
|
|
|
|
init(*argv);
|
|
|
|
}
|
2015-12-16 15:21:55 -05:00
|
|
|
edit();
|
2015-12-14 07:09:34 -05:00
|
|
|
|
|
|
|
/* not reached */
|
|
|
|
return 0;
|
|
|
|
}
|