URI:
       ed.c - sbase - suckless unix tools
  HTML git clone git://git.suckless.org/sbase
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
       ed.c (26133B)
       ---
            1 /* See LICENSE file for copyright and license details. */
            2 #include <sys/stat.h>
            3 #include <fcntl.h>
            4 #include <regex.h>
            5 #include <unistd.h>
            6 
            7 #include <ctype.h>
            8 #include <limits.h>
            9 #include <setjmp.h>
           10 #include <signal.h>
           11 #include <stdint.h>
           12 #include <stdio.h>
           13 #include <stdlib.h>
           14 #include <string.h>
           15 
           16 #include "util.h"
           17 
           18 #define REGEXSIZE  100
           19 #define LINESIZE    80
           20 #define NUMLINES    32
           21 #define CACHESIZ  4096
           22 #define AFTER     0
           23 #define BEFORE    1
           24 
           25 typedef struct {
           26         char *str;
           27         size_t cap;
           28         size_t siz;
           29 } String;
           30 
           31 struct hline {
           32         off_t seek;
           33         char  global;
           34         int   next, prev;
           35 };
           36 
           37 struct undo {
           38         int curln, lastln;
           39         size_t nr, cap;
           40         struct link {
           41                 int to1, from1;
           42                 int to2, from2;
           43         } *vec;
           44 };
           45 
           46 static char *prompt = "*";
           47 static regex_t *pattern;
           48 static regmatch_t matchs[10];
           49 static String lastre;
           50 
           51 static int optverbose, optprompt, exstatus, optdiag = 1;
           52 static int marks['z' - 'a' + 1];
           53 static int nlines, line1, line2;
           54 static int curln, lastln, ocurln, olastln;
           55 static jmp_buf savesp;
           56 static char *lasterr;
           57 static size_t idxsize, lastidx;
           58 static struct hline *zero;
           59 static String text;
           60 static char savfname[FILENAME_MAX];
           61 static char tmpname[FILENAME_MAX];
           62 static int scratch;
           63 static int pflag, modflag, uflag, gflag;
           64 static size_t csize;
           65 static String cmdline;
           66 static char *ocmdline;
           67 static int inputidx;
           68 static char *rhs;
           69 static char *lastmatch;
           70 static struct undo udata;
           71 static int newcmd;
           72 static int eol, bol;
           73 
           74 static sig_atomic_t intr, hup;
           75 
           76 static void undo(void);
           77 
           78 static void
           79 error(char *msg)
           80 {
           81         exstatus = 1;
           82         lasterr = msg;
           83         puts("?");
           84 
           85         if (optverbose)
           86                 puts(msg);
           87         if (!newcmd)
           88                 undo();
           89 
           90         curln = ocurln;
           91         longjmp(savesp, 1);
           92 }
           93 
           94 static int
           95 nextln(int line)
           96 {
           97         ++line;
           98         return (line > lastln) ? 0 : line;
           99 }
          100 
          101 static int
          102 prevln(int line)
          103 {
          104         --line;
          105         return (line < 0) ? lastln : line;
          106 }
          107 
          108 static String *
          109 copystring(String *s, char *from)
          110 {
          111         size_t len;
          112         char *t;
          113 
          114         if ((t = strdup(from)) == NULL)
          115                 error("out of memory");
          116         len = strlen(t);
          117 
          118         free(s->str);
          119         s->str = t;
          120         s->siz = len;
          121         s->cap = len;
          122 
          123         return s;
          124 }
          125 
          126 static String *
          127 string(String *s)
          128 {
          129         free(s->str);
          130         s->str = NULL;
          131         s->siz = 0;
          132         s->cap = 0;
          133 
          134         return s;
          135 }
          136 
          137 static char *
          138 addchar(char c, String *s)
          139 {
          140         size_t cap = s->cap, siz = s->siz;
          141         char *t = s->str;
          142 
          143         if (siz >= cap &&
          144             (cap > SIZE_MAX - LINESIZE ||
          145              (t = realloc(t, cap += LINESIZE)) == NULL))
          146                         error("out of memory");
          147         t[siz++] = c;
          148         s->siz = siz;
          149         s->cap = cap;
          150         s->str = t;
          151         return t;
          152 }
          153 
          154 static void chksignals(void);
          155 
          156 static int
          157 input(void)
          158 {
          159         int ch;
          160 
          161         chksignals();
          162 
          163         ch = cmdline.str[inputidx];
          164         if (ch != '\0')
          165                 inputidx++;
          166         return ch;
          167 }
          168 
          169 static int
          170 back(int c)
          171 {
          172         if (c == '\0')
          173                 return c;
          174         return cmdline.str[--inputidx] = c;
          175 }
          176 
          177 static int
          178 makeline(char *s, int *off)
          179 {
          180         struct hline *lp;
          181         size_t len;
          182         char *begin = s;
          183         int c;
          184 
          185         if (lastidx >= idxsize) {
          186                 lp = NULL;
          187                 if (idxsize <= SIZE_MAX - NUMLINES)
          188                         lp = reallocarray(zero, idxsize + NUMLINES, sizeof(*lp));
          189                 if (!lp)
          190                         error("out of memory");
          191                 idxsize += NUMLINES;
          192                 zero = lp;
          193         }
          194         lp = zero + lastidx;
          195         lp->global = 0;
          196 
          197         if (!s) {
          198                 lp->seek = -1;
          199                 len = 0;
          200         } else {
          201                 while ((c = *s++) && c != '\n')
          202                         ;
          203                 len = s - begin;
          204                 if ((lp->seek = lseek(scratch, 0, SEEK_END)) < 0 ||
          205                     write(scratch, begin, len) < 0) {
          206                         error("input/output error");
          207                 }
          208         }
          209         if (off)
          210                 *off = len;
          211         ++lastidx;
          212         return lp - zero;
          213 }
          214 
          215 static int
          216 getindex(int line)
          217 {
          218         struct hline *lp;
          219         int n;
          220 
          221         if (line == -1)
          222                 line = 0;
          223         for (n = 0, lp = zero; n != line; n++)
          224                 lp = zero + lp->next;
          225 
          226         return lp - zero;
          227 }
          228 
          229 static char *
          230 gettxt(int line)
          231 {
          232         static char buf[CACHESIZ];
          233         static off_t lasto;
          234         struct hline *lp;
          235         off_t off, block;
          236         ssize_t n;
          237         char *p;
          238 
          239         lp = zero + getindex(line);
          240         text.siz = 0;
          241         off = lp->seek;
          242 
          243         if (off == (off_t) -1)
          244                 return addchar('\0', &text);
          245 
          246 repeat:
          247         chksignals();
          248         if (!csize || off < lasto || off - lasto >= csize) {
          249                 block = off & ~(CACHESIZ-1);
          250                 if (lseek(scratch, block, SEEK_SET) < 0 ||
          251                     (n = read(scratch, buf, CACHESIZ)) < 0) {
          252                         error("input/output error");
          253                 }
          254                 csize = n;
          255                 lasto = block;
          256         }
          257         for (p = buf + off - lasto; p < buf + csize && *p != '\n'; ++p) {
          258                 ++off;
          259                 addchar(*p, &text);
          260         }
          261         if (csize == CACHESIZ && p == buf + csize)
          262                 goto repeat;
          263 
          264         addchar('\n', &text);
          265         addchar('\0', &text);
          266         return text.str;
          267 }
          268 
          269 static void
          270 setglobal(int i, int v)
          271 {
          272         zero[getindex(i)].global = v;
          273 }
          274 
          275 static void
          276 clearundo(void)
          277 {
          278         free(udata.vec);
          279         udata.vec = NULL;
          280         newcmd = udata.nr = udata.cap = 0;
          281         modflag = 0;
          282 }
          283 
          284 static void
          285 newundo(int from1, int from2)
          286 {
          287         struct link *p;
          288 
          289         if (newcmd) {
          290                 clearundo();
          291                 udata.curln = ocurln;
          292                 udata.lastln = olastln;
          293         }
          294         if (udata.nr >= udata.cap) {
          295                 size_t siz = (udata.cap + 10) * sizeof(struct link);
          296                 if ((p = realloc(udata.vec, siz)) == NULL)
          297                         error("out of memory");
          298                 udata.vec = p;
          299                 udata.cap = udata.cap + 10;
          300         }
          301         p = &udata.vec[udata.nr++];
          302         p->from1 = from1;
          303         p->to1 = zero[from1].next;
          304         p->from2 = from2;
          305         p->to2 = zero[from2].prev;
          306 }
          307 
          308 /*
          309  * relink: to1   <- from1
          310  *         from2 -> to2
          311  */
          312 static void
          313 relink(int to1, int from1, int from2, int to2)
          314 {
          315         newundo(from1, from2);
          316         zero[from1].next = to1;
          317         zero[from2].prev = to2;
          318         modflag = 1;
          319 }
          320 
          321 static void
          322 undo(void)
          323 {
          324         struct link *p;
          325 
          326         if (udata.nr == 0)
          327                 return;
          328         for (p = &udata.vec[udata.nr-1]; udata.nr > 0; --p) {
          329                 --udata.nr;
          330                 zero[p->from1].next = p->to1;
          331                 zero[p->from2].prev = p->to2;
          332         }
          333         free(udata.vec);
          334         udata.vec = NULL;
          335         udata.cap = 0;
          336         curln = udata.curln;
          337         lastln = udata.lastln;
          338 }
          339 
          340 static void
          341 inject(char *s, int where)
          342 {
          343         int off, k, begin, end;
          344 
          345         if (where == BEFORE) {
          346                 begin = getindex(curln-1);
          347                 end = getindex(nextln(curln-1));
          348         } else {
          349                 begin = getindex(curln);
          350                 end = getindex(nextln(curln));
          351         }
          352         while (*s) {
          353                 k = makeline(s, &off);
          354                 s += off;
          355                 relink(k, begin, k, begin);
          356                 relink(end, k, end, k);
          357                 ++lastln;
          358                 ++curln;
          359                 begin = k;
          360         }
          361 }
          362 
          363 static void
          364 clearbuf(void)
          365 {
          366         if (scratch)
          367                 close(scratch);
          368         remove(tmpname);
          369         free(zero);
          370         zero = NULL;
          371         scratch = csize = idxsize = lastidx = curln = lastln = 0;
          372         modflag = lastln = curln = 0;
          373 }
          374 
          375 static void
          376 setscratch(void)
          377 {
          378         int r, k;
          379         char *dir;
          380 
          381         clearbuf();
          382         clearundo();
          383         if ((dir = getenv("TMPDIR")) == NULL)
          384                 dir = "/tmp";
          385         r = snprintf(tmpname, sizeof(tmpname), "%s/%s",
          386                      dir, "ed.XXXXXX");
          387         if (r < 0 || (size_t)r >= sizeof(tmpname))
          388                 error("scratch filename too long");
          389         if ((scratch = mkstemp(tmpname)) < 0)
          390                 error("failed to create scratch file");
          391         if ((k = makeline(NULL, NULL)))
          392                 error("input/output error in scratch file");
          393         relink(k, k, k, k);
          394         clearundo();
          395 }
          396 
          397 static void
          398 compile(int delim)
          399 {
          400         int n, ret, c,bracket;
          401         static char buf[BUFSIZ];
          402 
          403         if (!isgraph(delim))
          404                 error("invalid pattern delimiter");
          405 
          406         eol = bol = bracket = lastre.siz = 0;
          407         for (n = 0;; ++n) {
          408                 c = input();
          409                 if (c == delim && !bracket || c == '\0') {
          410                         break;
          411                 } else if (c == '^') {
          412                         bol = 1;
          413                 } else if (c == '$') {
          414                         eol = 1;
          415                 } else if (c == '\\') {
          416                         addchar(c, &lastre);
          417                         c = input();
          418                 } else if (c == '[') {
          419                         bracket = 1;
          420                 } else if (c == ']') {
          421                         bracket = 0;
          422                 }
          423                 addchar(c, &lastre);
          424         }
          425         if (n == 0) {
          426                 if (!pattern)
          427                         error("no previous pattern");
          428                 return;
          429         }
          430         addchar('\0', &lastre);
          431 
          432         if (pattern)
          433                 regfree(pattern);
          434         if (!pattern && (!(pattern = malloc(sizeof(*pattern)))))
          435                 error("out of memory");
          436         if ((ret = regcomp(pattern, lastre.str, REG_NEWLINE))) {
          437                 regerror(ret, pattern, buf, sizeof(buf));
          438                 error(buf);
          439         }
          440 }
          441 
          442 static int
          443 match(int num)
          444 {
          445         int r;
          446 
          447         lastmatch = gettxt(num);
          448         text.str[text.siz - 2] = '\0';
          449         r =!regexec(pattern, lastmatch, 10, matchs, 0);
          450         text.str[text.siz - 2] = '\n';
          451 
          452         return r;
          453 }
          454 
          455 static int
          456 rematch(int num)
          457 {
          458         regoff_t off = matchs[0].rm_eo;
          459 
          460         if (!regexec(pattern, lastmatch + off, 10, matchs, 0)) {
          461                 lastmatch += off;
          462                 return 1;
          463         }
          464 
          465         return 0;
          466 }
          467 
          468 static int
          469 search(int way)
          470 {
          471         int i;
          472 
          473         i = curln;
          474         do {
          475                 chksignals();
          476 
          477                 i = (way == '?') ? prevln(i) : nextln(i);
          478                 if (i > 0 && match(i))
          479                         return i;
          480         } while (i != curln);
          481 
          482         error("invalid address");
          483         return -1; /* not reached */
          484 }
          485 
          486 static void
          487 skipblank(void)
          488 {
          489         char c;
          490 
          491         while ((c = input()) == ' ' || c == '\t')
          492                 ;
          493         back(c);
          494 }
          495 
          496 static void
          497 ensureblank(void)
          498 {
          499         char c;
          500 
          501         switch ((c = input())) {
          502         case ' ':
          503         case '\t':
          504                 skipblank();
          505         case '\0':
          506                 back(c);
          507                 break;
          508         default:
          509                 error("unknown command");
          510         }
          511 }
          512 
          513 static int
          514 getnum(void)
          515 {
          516         int ln, n, c;
          517 
          518         for (ln = 0; isdigit(c = input()); ln += n) {
          519                 if (ln > INT_MAX/10)
          520                         goto invalid;
          521                 n = c - '0';
          522                 ln *= 10;
          523                 if (INT_MAX - ln < n)
          524                         goto invalid;
          525         }
          526         back(c);
          527         return ln;
          528 
          529 invalid:
          530         error("invalid address");
          531         return -1; /* not reached */
          532 }
          533 
          534 static int
          535 linenum(int *line)
          536 {
          537         int ln, c;
          538 
          539         skipblank();
          540 
          541         switch (c = input()) {
          542         case '.':
          543                 ln = curln;
          544                 break;
          545         case '\'':
          546                 skipblank();
          547                 if (!islower(c = input()))
          548                         error("invalid mark character");
          549                 if (!(ln = marks[c - 'a']))
          550                         error("invalid address");
          551                 break;
          552         case '$':
          553                 ln = lastln;
          554                 break;
          555         case '?':
          556         case '/':
          557                 compile(c);
          558                 ln = search(c);
          559                 break;
          560         case '^':
          561         case '-':
          562         case '+':
          563                 ln = curln;
          564                 back(c);
          565                 break;
          566         default:
          567                 back(c);
          568                 if (isdigit(c))
          569                         ln = getnum();
          570                 else
          571                         return 0;
          572                 break;
          573         }
          574         *line = ln;
          575         return 1;
          576 }
          577 
          578 static int
          579 address(int *line)
          580 {
          581         int ln, sign, c, num;
          582 
          583         if (!linenum(&ln))
          584                 return 0;
          585 
          586         for (;;) {
          587                 skipblank();
          588                 if ((c = input()) != '+' && c != '-' && c != '^')
          589                         break;
          590                 sign = c == '+' ? 1 : -1;
          591                 num = isdigit(back(input())) ? getnum() : 1;
          592                 num *= sign;
          593                 if (INT_MAX - ln < num)
          594                         goto invalid;
          595                 ln += num;
          596         }
          597         back(c);
          598 
          599         if (ln < 0 || ln > lastln)
          600                 error("invalid address");
          601         *line = ln;
          602         return 1;
          603 
          604 invalid:
          605         error("invalid address");
          606         return -1; /* not reached */
          607 }
          608 
          609 static void
          610 getlst(void)
          611 {
          612         int ln, c;
          613 
          614         if ((c = input()) == ',') {
          615                 line1 = 1;
          616                 line2 = lastln;
          617                 nlines = lastln;
          618                 return;
          619         } else if (c == ';') {
          620                 line1 = curln;
          621                 line2 = lastln;
          622                 nlines = lastln - curln + 1;
          623                 return;
          624         }
          625         back(c);
          626         line2 = curln;
          627         for (nlines = 0; address(&ln); ) {
          628                 line1 = line2;
          629                 line2 = ln;
          630                 ++nlines;
          631 
          632                 skipblank();
          633                 if ((c = input()) != ',' && c != ';') {
          634                         back(c);
          635                         break;
          636                 }
          637                 if (c == ';')
          638                         curln = line2;
          639         }
          640         if (nlines > 2)
          641                 nlines = 2;
          642         else if (nlines <= 1)
          643                 line1 = line2;
          644 }
          645 
          646 static void
          647 deflines(int def1, int def2)
          648 {
          649         if (!nlines) {
          650                 line1 = def1;
          651                 line2 = def2;
          652         }
          653         if (line1 > line2 || line1 < 0 || line2 > lastln)
          654                 error("invalid address");
          655 }
          656 
          657 static void
          658 quit(void)
          659 {
          660         clearbuf();
          661         exit(exstatus);
          662 }
          663 
          664 static void
          665 setinput(char *s)
          666 {
          667         copystring(&cmdline, s);
          668         inputidx = 0;
          669 }
          670 
          671 static void
          672 getinput(void)
          673 {
          674         int ch;
          675 
          676         string(&cmdline);
          677 
          678         while ((ch = getchar()) != '\n' && ch != EOF) {
          679                 if (ch == '\\') {
          680                         if ((ch = getchar()) == EOF)
          681                                 break;
          682                         if (ch != '\n')
          683                                 addchar('\\', &cmdline);
          684                 }
          685                 addchar(ch, &cmdline);
          686         }
          687 
          688         addchar('\0', &cmdline);
          689         inputidx = 0;
          690 
          691         if (ch == EOF) {
          692                 chksignals();
          693                 if (ferror(stdin)) {
          694                         exstatus = 1;
          695                         fputs("ed: error reading input\n", stderr);
          696                 }
          697                 quit();
          698         }
          699 }
          700 
          701 static int
          702 moreinput(void)
          703 {
          704         if (!uflag)
          705                 return cmdline.str[inputidx] != '\0';
          706 
          707         getinput();
          708         return 1;
          709 }
          710 
          711 static void dowrite(const char *, int);
          712 
          713 static void
          714 dump(void)
          715 {
          716         char *home;
          717 
          718         if (modflag)
          719                 return;
          720 
          721         line1 = nextln(0);
          722         line2 = lastln;
          723 
          724         if (!setjmp(savesp)) {
          725                 dowrite("ed.hup", 1);
          726                 return;
          727         }
          728 
          729         home = getenv("HOME");
          730         if (!home || chdir(home) < 0)
          731                 return;
          732 
          733         if (!setjmp(savesp))
          734                 dowrite("ed.hup", 1);
          735 }
          736 
          737 static void
          738 chksignals(void)
          739 {
          740         if (hup) {
          741                 exstatus = 1;
          742                 dump();
          743                 quit();
          744         }
          745 
          746         if (intr) {
          747                 intr = 0;
          748                 newcmd = 1;
          749                 clearerr(stdin);
          750                 error("Interrupt");
          751         }
          752 }
          753 
          754 static void
          755 dowrite(const char *fname, int trunc)
          756 {
          757         size_t bytecount = 0;
          758         int i, r, line;
          759         FILE *aux;
          760         static int sh;
          761         static FILE *fp;
          762         char *mode;
          763 
          764         if (fp) {
          765                 sh ? pclose(fp) : fclose(fp);
          766                 fp = NULL;
          767         }
          768 
          769         if(fname[0] == '!') {
          770                 sh = 1;
          771                 fname++;
          772                 if((fp = popen(fname, "w")) == NULL)
          773                         error("bad exec");
          774         } else {
          775                 sh = 0;
          776                 mode = (trunc) ? "w" : "a";
          777                 if ((fp = fopen(fname, mode)) == NULL)
          778                         error("cannot open input file");
          779         }
          780 
          781         line = curln;
          782         for (i = line1; i <= line2; ++i) {
          783                 chksignals();
          784 
          785                 gettxt(i);
          786                 bytecount += text.siz - 1;
          787                 fwrite(text.str, 1, text.siz - 1, fp);
          788         }
          789 
          790         curln = line2;
          791 
          792         aux = fp;
          793         fp = NULL;
          794         r = sh ? pclose(aux) : fclose(aux);
          795         if (r)
          796                 error("input/output error");
          797         strcpy(savfname, fname);
          798         if (!sh)
          799                 modflag = 0;
          800         curln = line;
          801         if (optdiag)
          802                 printf("%zu\n", bytecount);
          803 }
          804 
          805 static void
          806 doread(const char *fname)
          807 {
          808         int r;
          809         size_t cnt;
          810         ssize_t len;
          811         char *p;
          812         FILE *aux;
          813         static size_t n;
          814         static int sh;
          815         static char *s;
          816         static FILE *fp;
          817 
          818         if (fp) {
          819                 sh ? pclose(fp) : fclose(fp);
          820                 fp = NULL;
          821         }
          822 
          823         if(fname[0] == '!') {
          824                 sh = 1;
          825                 fname++;
          826                 if((fp = popen(fname, "r")) == NULL)
          827                         error("bad exec");
          828         } else if ((fp = fopen(fname, "r")) == NULL) {
          829                 error("cannot open input file");
          830         }
          831 
          832         curln = line2;
          833         for (cnt = 0; (len = getline(&s, &n, fp)) > 0; cnt += (size_t)len) {
          834                 chksignals();
          835                 if (s[len-1] != '\n') {
          836                         if (len+1 >= n) {
          837                                 if (n == SIZE_MAX || !(p = realloc(s, ++n)))
          838                                         error("out of memory");
          839                                 s = p;
          840                         }
          841                         s[len] = '\n';
          842                         s[len+1] = '\0';
          843                 }
          844                 inject(s, AFTER);
          845         }
          846         if (optdiag)
          847                 printf("%zu\n", cnt);
          848 
          849         aux = fp;
          850         fp = NULL;
          851         r = sh ? pclose(aux) : fclose(aux);
          852         if (r)
          853                 error("input/output error");
          854 }
          855 
          856 static void
          857 doprint(void)
          858 {
          859         int i, c;
          860         char *s, *str;
          861 
          862         if (line1 <= 0 || line2 > lastln)
          863                 error("incorrect address");
          864         for (i = line1; i <= line2; ++i) {
          865                 chksignals();
          866                 if (pflag == 'n')
          867                         printf("%d\t", i);
          868                 for (s = gettxt(i); (c = *s) != '\n'; ++s) {
          869                         if (pflag != 'l')
          870                                 goto print_char;
          871                         switch (c) {
          872                         case '$':
          873                                 str = "\\$";
          874                                 goto print_str;
          875                         case '\t':
          876                                 str = "\\t";
          877                                 goto print_str;
          878                         case '\b':
          879                                 str = "\\b";
          880                                 goto print_str;
          881                         case '\\':
          882                                 str = "\\\\";
          883                                 goto print_str;
          884                         default:
          885                                 if (!isprint(c)) {
          886                                         printf("\\x%x", 0xFF & c);
          887                                         break;
          888                                 }
          889                         print_char:
          890                                 putchar(c);
          891                                 break;
          892                         print_str:
          893                                 fputs(str, stdout);
          894                                 break;
          895                         }
          896                 }
          897                 if (pflag == 'l')
          898                         fputs("$", stdout);
          899                 putc('\n', stdout);
          900         }
          901         curln = i - 1;
          902 }
          903 
          904 static void
          905 dohelp(void)
          906 {
          907         if (lasterr)
          908                 puts(lasterr);
          909 }
          910 
          911 static void
          912 chkprint(int flag)
          913 {
          914         int c;
          915 
          916         if (flag) {
          917                 if ((c = input()) == 'p' || c == 'l' || c == 'n')
          918                         pflag = c;
          919                 else
          920                         back(c);
          921         }
          922         if ((c = input()) != '\0' && c != '\n')
          923                 error("invalid command suffix");
          924 }
          925 
          926 static char *
          927 getfname(int comm)
          928 {
          929         int c;
          930         char *bp;
          931         static char fname[FILENAME_MAX];
          932 
          933         skipblank();
          934         for (bp = fname; bp < &fname[FILENAME_MAX]; *bp++ = c) {
          935                 if ((c = input()) == '\0')
          936                         break;
          937         }
          938         if (bp == fname) {
          939                 if (savfname[0] == '\0')
          940                         error("no current filename");
          941                 return savfname;
          942         }
          943         if (bp == &fname[FILENAME_MAX])
          944                 error("file name too long");
          945         *bp = '\0';
          946 
          947         if (fname[0] == '!')
          948                 return fname;
          949         if (savfname[0] == '\0' || comm == 'e' || comm == 'f')
          950                 strcpy(savfname, fname);
          951         return fname;
          952 }
          953 
          954 static void
          955 append(int num)
          956 {
          957         int ch;
          958         static String line;
          959 
          960         curln = num;
          961         while (moreinput()) {
          962                 string(&line);
          963                 while ((ch = input()) != '\n' && ch != '\0')
          964                         addchar(ch, &line);
          965                 addchar('\n', &line);
          966                 addchar('\0', &line);
          967 
          968                 if (!strcmp(line.str, ".\n") || !strcmp(line.str, "."))
          969                         break;
          970                 inject(line.str, AFTER);
          971         }
          972 }
          973 
          974 static void
          975 delete(int from, int to)
          976 {
          977         int lto, lfrom;
          978 
          979         if (!from)
          980                 error("incorrect address");
          981 
          982         lfrom = getindex(prevln(from));
          983         lto = getindex(nextln(to));
          984         lastln -= to - from + 1;
          985         curln = (from > lastln) ? lastln : from;;
          986         relink(lto, lfrom, lto, lfrom);
          987 }
          988 
          989 static void
          990 move(int where)
          991 {
          992         int before, after, lto, lfrom;
          993 
          994         if (!line1 || (where >= line1 && where <= line2))
          995                 error("incorrect address");
          996 
          997         before = getindex(prevln(line1));
          998         after = getindex(nextln(line2));
          999         lfrom = getindex(line1);
         1000         lto = getindex(line2);
         1001         relink(after, before, after, before);
         1002 
         1003         if (where < line1) {
         1004                 curln = where + line1 - line2 + 1;
         1005         } else {
         1006                 curln = where;
         1007                 where -= line1 - line2 + 1;
         1008         }
         1009         before = getindex(where);
         1010         after = getindex(nextln(where));
         1011         relink(lfrom, before, lfrom, before);
         1012         relink(after, lto, after, lto);
         1013 }
         1014 
         1015 static void
         1016 join(void)
         1017 {
         1018         int i;
         1019         char *t, c;
         1020         static String s;
         1021 
         1022         string(&s);
         1023         for (i = line1;; i = nextln(i)) {
         1024                 chksignals();
         1025                 for (t = gettxt(i); (c = *t) != '\n'; ++t)
         1026                         addchar(*t, &s);
         1027                 if (i == line2)
         1028                         break;
         1029         }
         1030 
         1031         addchar('\n', &s);
         1032         addchar('\0', &s);
         1033         delete(line1, line2);
         1034         inject(s.str, BEFORE);
         1035 }
         1036 
         1037 static void
         1038 scroll(int num)
         1039 {
         1040         int max, ln, cnt;
         1041 
         1042         if (!line1 || line1 == lastln)
         1043                 error("incorrect address");
         1044 
         1045         ln = line1;
         1046         max = line1 + num;
         1047         if (max > lastln)
         1048                 max = lastln;
         1049         for (cnt = line1; cnt < max; cnt++) {
         1050                 chksignals();
         1051                 fputs(gettxt(ln), stdout);
         1052                 ln = nextln(ln);
         1053         }
         1054         curln = ln;
         1055 }
         1056 
         1057 static void
         1058 copy(int where)
         1059 {
         1060 
         1061         if (!line1)
         1062                 error("incorrect address");
         1063         curln = where;
         1064 
         1065         while (line1 <= line2) {
         1066                 chksignals();
         1067                 inject(gettxt(line1), AFTER);
         1068                 if (line2 >= curln)
         1069                         line2 = nextln(line2);
         1070                 line1 = nextln(line1);
         1071                 if (line1 >= curln)
         1072                         line1 = nextln(line1);
         1073         }
         1074 }
         1075 
         1076 static void
         1077 execsh(void)
         1078 {
         1079         static String cmd;
         1080         char *p;
         1081         int c, repl = 0;
         1082 
         1083         skipblank();
         1084         if ((c = input()) != '!') {
         1085                 back(c);
         1086                 string(&cmd);
         1087         } else if (cmd.siz) {
         1088                 --cmd.siz;
         1089                 repl = 1;
         1090         } else {
         1091                 error("no previous command");
         1092         }
         1093 
         1094         while ((c = input()) != '\0') {
         1095                 switch (c) {
         1096                 case '%':
         1097                         if (savfname[0] == '\0')
         1098                                 error("no current filename");
         1099                         repl = 1;
         1100                         for (p = savfname; *p; ++p)
         1101                                 addchar(*p, &cmd);
         1102                         break;
         1103                 case '\\':
         1104                         c = input();
         1105                         if (c != '%') {
         1106                                 back(c);
         1107                                 c = '\\';
         1108                         }
         1109                 default:
         1110                         addchar(c, &cmd);
         1111                 }
         1112         }
         1113         addchar('\0', &cmd);
         1114 
         1115         if (repl)
         1116                 puts(cmd.str);
         1117         system(cmd.str);
         1118         if (optdiag)
         1119                 puts("!");
         1120 }
         1121 
         1122 static void
         1123 getrhs(int delim)
         1124 {
         1125         int c;
         1126         static String s;
         1127 
         1128         string(&s);
         1129         while ((c = input()) != '\0' && c != delim)
         1130                 addchar(c, &s);
         1131         addchar('\0', &s);
         1132         if (c == '\0') {
         1133                 pflag = 'p';
         1134                 back(c);
         1135         }
         1136 
         1137         if (!strcmp("%", s.str)) {
         1138                 if (!rhs)
         1139                         error("no previous substitution");
         1140                 free(s.str);
         1141         } else {
         1142                 free(rhs);
         1143                 rhs = s.str;
         1144         }
         1145         s.str = NULL;
         1146 }
         1147 
         1148 static int
         1149 getnth(void)
         1150 {
         1151         int c;
         1152 
         1153         if ((c = input()) == 'g') {
         1154                 return -1;
         1155         } else if (isdigit(c)) {
         1156                 if (c == '0')
         1157                         return -1;
         1158                 return c - '0';
         1159         } else {
         1160                 back(c);
         1161                 return 1;
         1162         }
         1163 }
         1164 
         1165 static void
         1166 addpre(String *s)
         1167 {
         1168         char *p;
         1169 
         1170         for (p = lastmatch; p < lastmatch + matchs[0].rm_so; ++p)
         1171                 addchar(*p, s);
         1172 }
         1173 
         1174 static void
         1175 addpost(String *s)
         1176 {
         1177         char c, *p;
         1178 
         1179         for (p = lastmatch + matchs[0].rm_eo; (c = *p); ++p)
         1180                 addchar(c, s);
         1181         addchar('\0', s);
         1182 }
         1183 
         1184 static int
         1185 addsub(String *s, int nth, int nmatch)
         1186 {
         1187         char *end, *q, *p, c;
         1188         int sub;
         1189 
         1190         if (nth != nmatch && nth != -1) {
         1191                 q   = lastmatch + matchs[0].rm_so;
         1192                 end = lastmatch + matchs[0].rm_eo;
         1193                 while (q < end)
         1194                         addchar(*q++, s);
         1195                 return 0;
         1196         }
         1197 
         1198         for (p = rhs; (c = *p); ++p) {
         1199                 switch (c) {
         1200                 case '&':
         1201                         sub = 0;
         1202                         goto copy_match;
         1203                 case '\\':
         1204                         if ((c = *++p) == '\0')
         1205                                 return 1;
         1206                         if (!isdigit(c))
         1207                                 goto copy_char;
         1208                         sub = c - '0';
         1209                 copy_match:
         1210                         q   = lastmatch + matchs[sub].rm_so;
         1211                         end = lastmatch + matchs[sub].rm_eo;
         1212                         while (q < end)
         1213                                 addchar(*q++, s);
         1214                         break;
         1215                 default:
         1216                 copy_char:
         1217                         addchar(c, s);
         1218                         break;
         1219                 }
         1220         }
         1221         return 1;
         1222 }
         1223 
         1224 static void
         1225 subline(int num, int nth)
         1226 {
         1227         int i, m, changed;
         1228         static String s;
         1229 
         1230         string(&s);
         1231         i = changed = 0;
         1232         for (m = match(num); m; m = rematch(num)) {
         1233                 chksignals();
         1234                 addpre(&s);
         1235                 changed |= addsub(&s, nth, ++i);
         1236                 if (eol || bol)
         1237                         break;
         1238         }
         1239         if (!changed)
         1240                 return;
         1241         addpost(&s);
         1242         delete(num, num);
         1243         curln = prevln(num);
         1244         inject(s.str, AFTER);
         1245 }
         1246 
         1247 static void
         1248 subst(int nth)
         1249 {
         1250         int i, line, next;
         1251 
         1252         line = line1;
         1253         for (i = 0; i < line2 - line1 + 1; i++) {
         1254                 chksignals();
         1255 
         1256                 next = getindex(nextln(line));
         1257                 subline(line, nth);
         1258 
         1259                 /*
         1260                  * The substitution command can add lines, so
         1261                  * we have to skip lines until we find the
         1262                  * index that we saved before the substitution
         1263                  */
         1264                 do
         1265                         line = nextln(line);
         1266                 while (getindex(line) != next);
         1267         }
         1268 }
         1269 
         1270 static void
         1271 docmd(void)
         1272 {
         1273         char *var;
         1274         int cmd, c, line3, num, trunc;
         1275 
         1276 repeat:
         1277         skipblank();
         1278         cmd = input();
         1279         trunc = pflag = 0;
         1280         switch (cmd) {
         1281         case '&':
         1282                 skipblank();
         1283                 chkprint(0);
         1284                 if (!ocmdline)
         1285                         error("no previous command");
         1286                 setinput(ocmdline);
         1287                 getlst();
         1288                 goto repeat;
         1289         case '!':
         1290                 execsh();
         1291                 break;
         1292         case '\0':
         1293                 num = gflag ? curln : curln+1;
         1294                 deflines(num, num);
         1295                 line1 = line2;
         1296                 pflag = 'p';
         1297                 goto print;
         1298         case 'l':
         1299         case 'n':
         1300         case 'p':
         1301                 back(cmd);
         1302                 chkprint(1);
         1303                 deflines(curln, curln);
         1304                 goto print;
         1305         case 'g':
         1306         case 'G':
         1307         case 'v':
         1308         case 'V':
         1309                 error("cannot nest global commands");
         1310         case 'H':
         1311                 if (nlines > 0)
         1312                         goto unexpected;
         1313                 chkprint(0);
         1314                 optverbose ^= 1;
         1315                 break;
         1316         case 'h':
         1317                 if (nlines > 0)
         1318                         goto unexpected;
         1319                 chkprint(0);
         1320                 dohelp();
         1321                 break;
         1322         case 'w':
         1323                 trunc = 1;
         1324         case 'W':
         1325                 ensureblank();
         1326                 deflines(nextln(0), lastln);
         1327                 dowrite(getfname(cmd), trunc);
         1328                 break;
         1329         case 'r':
         1330                 ensureblank();
         1331                 if (nlines > 1)
         1332                         goto bad_address;
         1333                 deflines(lastln, lastln);
         1334                 doread(getfname(cmd));
         1335                 break;
         1336         case 'd':
         1337                 chkprint(1);
         1338                 deflines(curln, curln);
         1339                 delete(line1, line2);
         1340                 break;
         1341         case '=':
         1342                 if (nlines > 1)
         1343                         goto bad_address;
         1344                 chkprint(1);
         1345                 deflines(lastln, lastln);
         1346                 printf("%d\n", line1);
         1347                 break;
         1348         case 'u':
         1349                 if (nlines > 0)
         1350                         goto bad_address;
         1351                 chkprint(1);
         1352                 if (udata.nr == 0)
         1353                         error("nothing to undo");
         1354                 undo();
         1355                 break;
         1356         case 's':
         1357                 deflines(curln, curln);
         1358                 c = input();
         1359                 compile(c);
         1360                 getrhs(c);
         1361                 num = getnth();
         1362                 chkprint(1);
         1363                 subst(num);
         1364                 break;
         1365         case 'i':
         1366                 if (nlines > 1)
         1367                         goto bad_address;
         1368                 chkprint(1);
         1369                 deflines(curln, curln);
         1370                 if (!line1)
         1371                         line1++;
         1372                 append(prevln(line1));
         1373                 break;
         1374         case 'a':
         1375                 if (nlines > 1)
         1376                         goto bad_address;
         1377                 chkprint(1);
         1378                 deflines(curln, curln);
         1379                 append(line1);
         1380                 break;
         1381         case 'm':
         1382                 deflines(curln, curln);
         1383                 if (!address(&line3))
         1384                         line3 = curln;
         1385                 chkprint(1);
         1386                 move(line3);
         1387                 break;
         1388         case 't':
         1389                 deflines(curln, curln);
         1390                 if (!address(&line3))
         1391                         line3 = curln;
         1392                 chkprint(1);
         1393                 copy(line3);
         1394                 break;
         1395         case 'c':
         1396                 chkprint(1);
         1397                 deflines(curln, curln);
         1398                 delete(line1, line2);
         1399                 append(prevln(line1));
         1400                 break;
         1401         case 'j':
         1402                 chkprint(1);
         1403                 deflines(curln, curln+1);
         1404                 if (line1 != line2 && curln != 0)
         1405                               join();
         1406                 break;
         1407         case 'z':
         1408                 if (nlines > 1)
         1409                         goto bad_address;
         1410 
         1411                 num = 0;
         1412                 if (isdigit(back(input())))
         1413                         num = getnum();
         1414                 else if ((var = getenv("LINES")) != NULL)
         1415                         num = atoi(var) - 1;
         1416                 if (num <= 0)
         1417                         num = 23;
         1418                 chkprint(1);
         1419                 deflines(curln, curln);
         1420                 scroll(num);
         1421                 break;
         1422         case 'k':
         1423                 if (nlines > 1)
         1424                         goto bad_address;
         1425                 if (!islower(c = input()))
         1426                         error("invalid mark character");
         1427                 chkprint(1);
         1428                 deflines(curln, curln);
         1429                 marks[c - 'a'] = line1;
         1430                 break;
         1431         case 'P':
         1432                 if (nlines > 0)
         1433                         goto unexpected;
         1434                 chkprint(1);
         1435                 optprompt ^= 1;
         1436                 break;
         1437         case 'x':
         1438                 trunc = 1;
         1439         case 'X':
         1440                 ensureblank();
         1441                 if (nlines > 0)
         1442                         goto unexpected;
         1443                 exstatus = 0;
         1444                 deflines(nextln(0), lastln);
         1445                 dowrite(getfname(cmd), trunc);
         1446         case 'Q':
         1447         case 'q':
         1448                 if (nlines > 0)
         1449                         goto unexpected;
         1450                 if (cmd != 'Q' && modflag)
         1451                         goto modified;
         1452                 modflag = 0;
         1453                 quit();
         1454                 break;
         1455         case 'f':
         1456                 ensureblank();
         1457                 if (nlines > 0)
         1458                         goto unexpected;
         1459                 if (back(input()) != '\0')
         1460                         getfname(cmd);
         1461                 else
         1462                         puts(savfname);
         1463                 chkprint(0);
         1464                 break;
         1465         case 'E':
         1466         case 'e':
         1467                 ensureblank();
         1468                 if (nlines > 0)
         1469                         goto unexpected;
         1470                 if (cmd == 'e' && modflag)
         1471                         goto modified;
         1472                 setscratch();
         1473                 deflines(curln, curln);
         1474                 doread(getfname(cmd));
         1475                 clearundo();
         1476                 modflag = 0;
         1477                 break;
         1478         default:
         1479                 error("unknown command");
         1480         bad_address:
         1481                 error("invalid address");
         1482         modified:
         1483                 modflag = 0;
         1484                 error("warning: file modified");
         1485         unexpected:
         1486                 error("unexpected address");
         1487         }
         1488 
         1489         if (!pflag)
         1490                 return;
         1491         line1 = line2 = curln;
         1492 
         1493 print:
         1494         doprint();
         1495 }
         1496 
         1497 static int
         1498 chkglobal(void)
         1499 {
         1500         int delim, c, dir, i, v;
         1501 
         1502         uflag = 1;
         1503         gflag = 0;
         1504         skipblank();
         1505 
         1506         switch (c = input()) {
         1507         case 'g':
         1508                 uflag = 0;
         1509         case 'G':
         1510                 dir = 1;
         1511                 break;
         1512         case 'v':
         1513                 uflag = 0;
         1514         case 'V':
         1515                 dir = 0;
         1516                 break;
         1517         default:
         1518                 back(c);
         1519                 return 0;
         1520         }
         1521         gflag = 1;
         1522         deflines(nextln(0), lastln);
         1523         delim = input();
         1524         compile(delim);
         1525 
         1526         for (i = 1; i <= lastln; ++i) {
         1527                 chksignals();
         1528                 if (i >= line1 && i <= line2)
         1529                         v = match(i) == dir;
         1530                 else
         1531                         v = 0;
         1532                 setglobal(i, v);
         1533         }
         1534 
         1535         return 1;
         1536 }
         1537 
         1538 static void
         1539 savecmd(void)
         1540 {
         1541         int ch;
         1542 
         1543         skipblank();
         1544         ch = input();
         1545         if (ch != '&') {
         1546                 ocmdline = strdup(cmdline.str);
         1547                 if (ocmdline == NULL)
         1548                         error("out of memory");
         1549         }
         1550         back(ch);
         1551 }
         1552 
         1553 static void
         1554 doglobal(void)
         1555 {
         1556         int cnt, ln, k, idx, c;
         1557 
         1558         skipblank();
         1559         gflag = 1;
         1560         if (uflag)
         1561                 chkprint(0);
         1562 
         1563         ln = line1;
         1564         for (cnt = 0; cnt < lastln; ) {
         1565                 chksignals();
         1566                 k = getindex(ln);
         1567                 if (zero[k].global) {
         1568                         zero[k].global = 0;
         1569                         curln = ln;
         1570                         nlines = 0;
         1571 
         1572                         if (!uflag) {
         1573                                 idx = inputidx;
         1574                                 getlst();
         1575                                 for (;;) {
         1576                                         docmd();
         1577                                         if (!(c = input()))
         1578                                                 break;
         1579                                         back(c);
         1580                                 }
         1581                                 inputidx = idx;
         1582                                 continue;
         1583                         }
         1584 
         1585                         line1 = line2 = ln;
         1586                         pflag = 0;
         1587                         doprint();
         1588 
         1589                         for (;;) {
         1590                                 getinput();
         1591                                 if (strcmp(cmdline.str, "") == 0)
         1592                                         break;
         1593                                 savecmd();
         1594                                 getlst();
         1595                                 docmd();
         1596                         }
         1597 
         1598                 } else {
         1599                         cnt++;
         1600                         ln = nextln(ln);
         1601                 }
         1602         }
         1603 }
         1604 
         1605 static void
         1606 usage(void)
         1607 {
         1608         eprintf("usage: %s [-s] [-p] [file]\n", argv0);
         1609 }
         1610 
         1611 static void
         1612 sigintr(int n)
         1613 {
         1614         intr = 1;
         1615 }
         1616 
         1617 static void
         1618 sighup(int dummy)
         1619 {
         1620         hup = 1;
         1621 }
         1622 
         1623 static void
         1624 edit(void)
         1625 {
         1626         for (;;) {
         1627                 newcmd = 1;
         1628                 ocurln = curln;
         1629                 olastln = lastln;
         1630                 if (optprompt) {
         1631                         fputs(prompt, stdout);
         1632                         fflush(stdout);
         1633                 }
         1634 
         1635                 getinput();
         1636                 getlst();
         1637                 chkglobal() ? doglobal() : docmd();
         1638         }
         1639 }
         1640 
         1641 static void
         1642 init(char *fname)
         1643 {
         1644         size_t len;
         1645 
         1646         setscratch();
         1647         if (!fname)
         1648                 return;
         1649         if ((len = strlen(fname)) >= FILENAME_MAX || len == 0)
         1650                 error("incorrect filename");
         1651         memcpy(savfname, fname, len);
         1652         doread(fname);
         1653         clearundo();
         1654 }
         1655 
         1656 int
         1657 main(int argc, char *argv[])
         1658 {
         1659         ARGBEGIN {
         1660         case 'p':
         1661                 prompt = EARGF(usage());
         1662                 optprompt = 1;
         1663                 break;
         1664         case 's':
         1665                 optdiag = 0;
         1666                 break;
         1667         default:
         1668                 usage();
         1669         } ARGEND
         1670 
         1671         if (argc > 1)
         1672                 usage();
         1673 
         1674         if (!setjmp(savesp)) {
         1675                 sigaction(SIGINT,
         1676                           &(struct sigaction) {.sa_handler = sigintr},
         1677                           NULL);
         1678                 sigaction(SIGHUP,
         1679                           &(struct sigaction) {.sa_handler = sighup},
         1680                           NULL);
         1681                 sigaction(SIGQUIT,
         1682                           &(struct sigaction) {.sa_handler = SIG_IGN},
         1683                           NULL);
         1684                 init(*argv);
         1685         }
         1686         edit();
         1687 
         1688         /* not reached */
         1689         return 0;
         1690 }