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