ed.c - sbase - suckless unix tools
 (HTM) git clone git://git.suckless.org/sbase
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
       ed.c (25971B)
       ---
            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                                 ungetc(ch, stdin);
          684                                 ch = '\\';
          685                         }
          686                 }
          687                 addchar(ch, &cmdline);
          688         }
          689 
          690         addchar('\0', &cmdline);
          691         inputidx = 0;
          692 
          693         if (ch == EOF) {
          694                 chksignals();
          695                 if (ferror(stdin)) {
          696                         exstatus = 1;
          697                         fputs("ed: error reading input\n", stderr);
          698                 }
          699                 quit();
          700         }
          701 }
          702 
          703 static int
          704 moreinput(void)
          705 {
          706         if (!uflag)
          707                 return cmdline.str[inputidx] != '\0';
          708 
          709         getinput();
          710         return 1;
          711 }
          712 
          713 static void dowrite(const char *, int);
          714 
          715 static void
          716 dump(void)
          717 {
          718         char *home;
          719 
          720         if (modflag)
          721                 return;
          722 
          723         line1 = nextln(0);
          724         line2 = lastln;
          725 
          726         if (!setjmp(savesp)) {
          727                 dowrite("ed.hup", 1);
          728                 return;
          729         }
          730 
          731         home = getenv("HOME");
          732         if (!home || chdir(home) < 0)
          733                 return;
          734 
          735         if (!setjmp(savesp))
          736                 dowrite("ed.hup", 1);
          737 }
          738 
          739 static void
          740 chksignals(void)
          741 {
          742         if (hup) {
          743                 exstatus = 1;
          744                 dump();
          745                 quit();
          746         }
          747 
          748         if (intr) {
          749                 intr = 0;
          750                 newcmd = 1;
          751                 clearerr(stdin);
          752                 error("Interrupt");
          753         }
          754 }
          755 
          756 static void
          757 dowrite(const char *fname, int trunc)
          758 {
          759         size_t bytecount = 0;
          760         int i, r, line;
          761         FILE *aux;
          762         static int sh;
          763         static FILE *fp;
          764         char *mode;
          765 
          766         if (fp) {
          767                 sh ? pclose(fp) : fclose(fp);
          768                 fp = NULL;
          769         }
          770 
          771         if(fname[0] == '!') {
          772                 sh = 1;
          773                 fname++;
          774                 if((fp = popen(fname, "w")) == NULL)
          775                         error("bad exec");
          776         } else {
          777                 sh = 0;
          778                 mode = (trunc) ? "w" : "a";
          779                 if ((fp = fopen(fname, mode)) == NULL)
          780                         error("cannot open input file");
          781         }
          782 
          783         line = curln;
          784         for (i = line1; i <= line2; ++i) {
          785                 chksignals();
          786 
          787                 gettxt(i);
          788                 bytecount += text.siz - 1;
          789                 fwrite(text.str, 1, text.siz - 1, fp);
          790         }
          791 
          792         curln = line2;
          793 
          794         aux = fp;
          795         fp = NULL;
          796         r = sh ? pclose(aux) : fclose(aux);
          797         if (r)
          798                 error("input/output error");
          799         strcpy(savfname, fname);
          800         if (!sh)
          801                 modflag = 0;
          802         curln = line;
          803         if (optdiag)
          804                 printf("%zu\n", bytecount);
          805 }
          806 
          807 static void
          808 doread(const char *fname)
          809 {
          810         int r;
          811         size_t cnt;
          812         ssize_t len;
          813         char *p;
          814         FILE *aux;
          815         static size_t n;
          816         static int sh;
          817         static char *s;
          818         static FILE *fp;
          819 
          820         if (fp) {
          821                 sh ? pclose(fp) : fclose(fp);
          822                 fp = NULL;
          823         }
          824 
          825         if(fname[0] == '!') {
          826                 sh = 1;
          827                 fname++;
          828                 if((fp = popen(fname, "r")) == NULL)
          829                         error("bad exec");
          830         } else if ((fp = fopen(fname, "r")) == NULL) {
          831                 error("cannot open input file");
          832         }
          833 
          834         curln = line2;
          835         for (cnt = 0; (len = getline(&s, &n, fp)) > 0; cnt += (size_t)len) {
          836                 chksignals();
          837                 if (s[len-1] != '\n') {
          838                         if (len+1 >= n) {
          839                                 if (n == SIZE_MAX || !(p = realloc(s, ++n)))
          840                                         error("out of memory");
          841                                 s = p;
          842                         }
          843                         s[len] = '\n';
          844                         s[len+1] = '\0';
          845                 }
          846                 inject(s, AFTER);
          847         }
          848         if (optdiag)
          849                 printf("%zu\n", cnt);
          850 
          851         aux = fp;
          852         fp = NULL;
          853         r = sh ? pclose(aux) : fclose(aux);
          854         if (r)
          855                 error("input/output error");
          856 }
          857 
          858 static void
          859 doprint(void)
          860 {
          861         int i, c;
          862         char *s, *str;
          863 
          864         if (line1 <= 0 || line2 > lastln)
          865                 error("incorrect address");
          866         for (i = line1; i <= line2; ++i) {
          867                 chksignals();
          868                 if (pflag == 'n')
          869                         printf("%d\t", i);
          870                 for (s = gettxt(i); (c = *s) != '\n'; ++s) {
          871                         if (pflag != 'l')
          872                                 goto print_char;
          873                         switch (c) {
          874                         case '$':
          875                                 str = "\\$";
          876                                 goto print_str;
          877                         case '\t':
          878                                 str = "\\t";
          879                                 goto print_str;
          880                         case '\b':
          881                                 str = "\\b";
          882                                 goto print_str;
          883                         case '\\':
          884                                 str = "\\\\";
          885                                 goto print_str;
          886                         default:
          887                                 if (!isprint(c)) {
          888                                         printf("\\x%x", 0xFF & c);
          889                                         break;
          890                                 }
          891                         print_char:
          892                                 putchar(c);
          893                                 break;
          894                         print_str:
          895                                 fputs(str, stdout);
          896                                 break;
          897                         }
          898                 }
          899                 if (pflag == 'l')
          900                         fputs("$", stdout);
          901                 putc('\n', stdout);
          902         }
          903         curln = i - 1;
          904 }
          905 
          906 static void
          907 dohelp(void)
          908 {
          909         if (lasterr)
          910                 puts(lasterr);
          911 }
          912 
          913 static void
          914 chkprint(int flag)
          915 {
          916         int c;
          917 
          918         if (flag) {
          919                 if ((c = input()) == 'p' || c == 'l' || c == 'n')
          920                         pflag = c;
          921                 else
          922                         back(c);
          923         }
          924         if ((c = input()) != '\0' && c != '\n')
          925                 error("invalid command suffix");
          926 }
          927 
          928 static char *
          929 getfname(int comm)
          930 {
          931         int c;
          932         char *bp;
          933         static char fname[FILENAME_MAX];
          934 
          935         skipblank();
          936         for (bp = fname; bp < &fname[FILENAME_MAX]; *bp++ = c) {
          937                 if ((c = input()) == '\0')
          938                         break;
          939         }
          940         if (bp == fname) {
          941                 if (savfname[0] == '\0')
          942                         error("no current filename");
          943                 return savfname;
          944         }
          945         if (bp == &fname[FILENAME_MAX])
          946                 error("file name too long");
          947         *bp = '\0';
          948 
          949         if (fname[0] == '!')
          950                 return fname;
          951         if (savfname[0] == '\0' || comm == 'e' || comm == 'f')
          952                 strcpy(savfname, fname);
          953         return fname;
          954 }
          955 
          956 static void
          957 append(int num)
          958 {
          959         int ch;
          960         static String line;
          961 
          962         curln = num;
          963         while (moreinput()) {
          964                 string(&line);
          965                 while ((ch = input()) != '\n' && ch != '\0')
          966                         addchar(ch, &line);
          967                 addchar('\n', &line);
          968                 addchar('\0', &line);
          969 
          970                 if (!strcmp(line.str, ".\n") || !strcmp(line.str, "."))
          971                         break;
          972                 inject(line.str, AFTER);
          973         }
          974 }
          975 
          976 static void
          977 delete(int from, int to)
          978 {
          979         int lto, lfrom;
          980 
          981         if (!from)
          982                 error("incorrect address");
          983 
          984         lfrom = getindex(prevln(from));
          985         lto = getindex(nextln(to));
          986         lastln -= to - from + 1;
          987         curln = (from > lastln) ? lastln : from;;
          988         relink(lto, lfrom, lto, lfrom);
          989 }
          990 
          991 static void
          992 move(int where)
          993 {
          994         int before, after, lto, lfrom;
          995 
          996         if (!line1 || (where >= line1 && where <= line2))
          997                 error("incorrect address");
          998 
          999         before = getindex(prevln(line1));
         1000         after = getindex(nextln(line2));
         1001         lfrom = getindex(line1);
         1002         lto = getindex(line2);
         1003         relink(after, before, after, before);
         1004 
         1005         if (where < line1) {
         1006                 curln = where + line1 - line2 + 1;
         1007         } else {
         1008                 curln = where;
         1009                 where -= line1 - line2 + 1;
         1010         }
         1011         before = getindex(where);
         1012         after = getindex(nextln(where));
         1013         relink(lfrom, before, lfrom, before);
         1014         relink(after, lto, after, lto);
         1015 }
         1016 
         1017 static void
         1018 join(void)
         1019 {
         1020         int i;
         1021         char *t, c;
         1022         static String s;
         1023 
         1024         string(&s);
         1025         for (i = line1;; i = nextln(i)) {
         1026                 chksignals();
         1027                 for (t = gettxt(i); (c = *t) != '\n'; ++t)
         1028                         addchar(*t, &s);
         1029                 if (i == line2)
         1030                         break;
         1031         }
         1032 
         1033         addchar('\n', &s);
         1034         addchar('\0', &s);
         1035         delete(line1, line2);
         1036         inject(s.str, BEFORE);
         1037 }
         1038 
         1039 static void
         1040 scroll(int num)
         1041 {
         1042         int max, ln, cnt;
         1043 
         1044         if (!line1 || line1 == lastln)
         1045                 error("incorrect address");
         1046 
         1047         ln = line1;
         1048         max = line1 + num;
         1049         if (max > lastln)
         1050                 max = lastln;
         1051         for (cnt = line1; cnt < max; cnt++) {
         1052                 chksignals();
         1053                 fputs(gettxt(ln), stdout);
         1054                 ln = nextln(ln);
         1055         }
         1056         curln = ln;
         1057 }
         1058 
         1059 static void
         1060 copy(int where)
         1061 {
         1062 
         1063         if (!line1)
         1064                 error("incorrect address");
         1065         curln = where;
         1066 
         1067         while (line1 <= line2) {
         1068                 chksignals();
         1069                 inject(gettxt(line1), AFTER);
         1070                 if (line2 >= curln)
         1071                         line2 = nextln(line2);
         1072                 line1 = nextln(line1);
         1073                 if (line1 >= curln)
         1074                         line1 = nextln(line1);
         1075         }
         1076 }
         1077 
         1078 static void
         1079 execsh(void)
         1080 {
         1081         static String cmd;
         1082         char *p;
         1083         int c, repl = 0;
         1084 
         1085         skipblank();
         1086         if ((c = input()) != '!') {
         1087                 back(c);
         1088                 string(&cmd);
         1089         } else if (cmd.siz) {
         1090                 --cmd.siz;
         1091                 repl = 1;
         1092         } else {
         1093                 error("no previous command");
         1094         }
         1095 
         1096         while ((c = input()) != '\0') {
         1097                 switch (c) {
         1098                 case '%':
         1099                         if (savfname[0] == '\0')
         1100                                 error("no current filename");
         1101                         repl = 1;
         1102                         for (p = savfname; *p; ++p)
         1103                                 addchar(*p, &cmd);
         1104                         break;
         1105                 case '\\':
         1106                         c = input();
         1107                         if (c != '%') {
         1108                                 back(c);
         1109                                 c = '\\';
         1110                         }
         1111                 default:
         1112                         addchar(c, &cmd);
         1113                 }
         1114         }
         1115         addchar('\0', &cmd);
         1116 
         1117         if (repl)
         1118                 puts(cmd.str);
         1119         system(cmd.str);
         1120         if (optdiag)
         1121                 puts("!");
         1122 }
         1123 
         1124 static void
         1125 getrhs(int delim)
         1126 {
         1127         int c;
         1128         static String s;
         1129 
         1130         string(&s);
         1131         while ((c = input()) != '\0' && c != delim)
         1132                 addchar(c, &s);
         1133         addchar('\0', &s);
         1134         if (c == '\0') {
         1135                 pflag = 'p';
         1136                 back(c);
         1137         }
         1138 
         1139         if (!strcmp("%", s.str)) {
         1140                 if (!rhs)
         1141                         error("no previous substitution");
         1142                 free(s.str);
         1143         } else {
         1144                 free(rhs);
         1145                 rhs = s.str;
         1146         }
         1147         s.str = NULL;
         1148 }
         1149 
         1150 static int
         1151 getnth(void)
         1152 {
         1153         int c;
         1154 
         1155         if ((c = input()) == 'g') {
         1156                 return -1;
         1157         } else if (isdigit(c)) {
         1158                 if (c == '0')
         1159                         return -1;
         1160                 return c - '0';
         1161         } else {
         1162                 back(c);
         1163                 return 1;
         1164         }
         1165 }
         1166 
         1167 static void
         1168 addpre(String *s)
         1169 {
         1170         char *p;
         1171 
         1172         for (p = lastmatch; p < lastmatch + matchs[0].rm_so; ++p)
         1173                 addchar(*p, s);
         1174 }
         1175 
         1176 static void
         1177 addpost(String *s)
         1178 {
         1179         char c, *p;
         1180 
         1181         for (p = lastmatch + matchs[0].rm_eo; (c = *p); ++p)
         1182                 addchar(c, s);
         1183         addchar('\0', s);
         1184 }
         1185 
         1186 static int
         1187 addsub(String *s, int nth, int nmatch)
         1188 {
         1189         char *end, *q, *p, c;
         1190         int sub;
         1191 
         1192         if (nth != nmatch && nth != -1) {
         1193                 q   = lastmatch + matchs[0].rm_so;
         1194                 end = lastmatch + matchs[0].rm_eo;
         1195                 while (q < end)
         1196                         addchar(*q++, s);
         1197                 return 0;
         1198         }
         1199 
         1200         for (p = rhs; (c = *p); ++p) {
         1201                 switch (c) {
         1202                 case '&':
         1203                         sub = 0;
         1204                         goto copy_match;
         1205                 case '\\':
         1206                         if ((c = *++p) == '\0')
         1207                                 return 1;
         1208                         if (!isdigit(c))
         1209                                 goto copy_char;
         1210                         sub = c - '0';
         1211                 copy_match:
         1212                         q   = lastmatch + matchs[sub].rm_so;
         1213                         end = lastmatch + matchs[sub].rm_eo;
         1214                         while (q < end)
         1215                                 addchar(*q++, s);
         1216                         break;
         1217                 default:
         1218                 copy_char:
         1219                         addchar(c, s);
         1220                         break;
         1221                 }
         1222         }
         1223         return 1;
         1224 }
         1225 
         1226 static void
         1227 subline(int num, int nth)
         1228 {
         1229         int i, m, changed;
         1230         static String s;
         1231 
         1232         string(&s);
         1233         i = changed = 0;
         1234         for (m = match(num); m; m = rematch(num)) {
         1235                 chksignals();
         1236                 addpre(&s);
         1237                 changed |= addsub(&s, nth, ++i);
         1238                 if (eol || bol)
         1239                         break;
         1240         }
         1241         if (!changed)
         1242                 return;
         1243         addpost(&s);
         1244         delete(num, num);
         1245         curln = prevln(num);
         1246         inject(s.str, AFTER);
         1247 }
         1248 
         1249 static void
         1250 subst(int nth)
         1251 {
         1252         int i, line, next;
         1253 
         1254         line = line1;
         1255         for (i = 0; i < line2 - line1 + 1; i++) {
         1256                 chksignals();
         1257 
         1258                 next = getindex(nextln(line));
         1259                 subline(line, nth);
         1260 
         1261                 /*
         1262                  * The substitution command can add lines, so
         1263                  * we have to skip lines until we find the
         1264                  * index that we saved before the substitution
         1265                  */
         1266                 do
         1267                         line = nextln(line);
         1268                 while (getindex(line) != next);
         1269         }
         1270 }
         1271 
         1272 static void
         1273 docmd(void)
         1274 {
         1275         int cmd, c, line3, num, trunc;
         1276 
         1277 repeat:
         1278         skipblank();
         1279         cmd = input();
         1280         trunc = pflag = 0;
         1281         switch (cmd) {
         1282         case '&':
         1283                 skipblank();
         1284                 chkprint(0);
         1285                 if (!ocmdline)
         1286                         error("no previous command");
         1287                 setinput(ocmdline);
         1288                 getlst();
         1289                 goto repeat;
         1290         case '!':
         1291                 execsh();
         1292                 break;
         1293         case '\0':
         1294                 num = gflag ? curln : curln+1;
         1295                 deflines(num, num);
         1296                 line1 = line2;
         1297                 pflag = 'p';
         1298                 goto print;
         1299         case 'l':
         1300         case 'n':
         1301         case 'p':
         1302                 back(cmd);
         1303                 chkprint(1);
         1304                 deflines(curln, curln);
         1305                 goto print;
         1306         case 'g':
         1307         case 'G':
         1308         case 'v':
         1309         case 'V':
         1310                 error("cannot nest global commands");
         1311         case 'H':
         1312                 if (nlines > 0)
         1313                         goto unexpected;
         1314                 chkprint(0);
         1315                 optverbose ^= 1;
         1316                 break;
         1317         case 'h':
         1318                 if (nlines > 0)
         1319                         goto unexpected;
         1320                 chkprint(0);
         1321                 dohelp();
         1322                 break;
         1323         case 'w':
         1324                 trunc = 1;
         1325         case 'W':
         1326                 ensureblank();
         1327                 deflines(nextln(0), lastln);
         1328                 dowrite(getfname(cmd), trunc);
         1329                 break;
         1330         case 'r':
         1331                 ensureblank();
         1332                 if (nlines > 1)
         1333                         goto bad_address;
         1334                 deflines(lastln, lastln);
         1335                 doread(getfname(cmd));
         1336                 break;
         1337         case 'd':
         1338                 chkprint(1);
         1339                 deflines(curln, curln);
         1340                 delete(line1, line2);
         1341                 break;
         1342         case '=':
         1343                 if (nlines > 1)
         1344                         goto bad_address;
         1345                 chkprint(1);
         1346                 deflines(lastln, lastln);
         1347                 printf("%d\n", line1);
         1348                 break;
         1349         case 'u':
         1350                 if (nlines > 0)
         1351                         goto bad_address;
         1352                 chkprint(1);
         1353                 if (udata.nr == 0)
         1354                         error("nothing to undo");
         1355                 undo();
         1356                 break;
         1357         case 's':
         1358                 deflines(curln, curln);
         1359                 c = input();
         1360                 compile(c);
         1361                 getrhs(c);
         1362                 num = getnth();
         1363                 chkprint(1);
         1364                 subst(num);
         1365                 break;
         1366         case 'i':
         1367                 if (nlines > 1)
         1368                         goto bad_address;
         1369                 chkprint(1);
         1370                 deflines(curln, curln);
         1371                 if (!line1)
         1372                         line1++;
         1373                 append(prevln(line1));
         1374                 break;
         1375         case 'a':
         1376                 if (nlines > 1)
         1377                         goto bad_address;
         1378                 chkprint(1);
         1379                 deflines(curln, curln);
         1380                 append(line1);
         1381                 break;
         1382         case 'm':
         1383                 deflines(curln, curln);
         1384                 if (!address(&line3))
         1385                         line3 = curln;
         1386                 chkprint(1);
         1387                 move(line3);
         1388                 break;
         1389         case 't':
         1390                 deflines(curln, curln);
         1391                 if (!address(&line3))
         1392                         line3 = curln;
         1393                 chkprint(1);
         1394                 copy(line3);
         1395                 break;
         1396         case 'c':
         1397                 chkprint(1);
         1398                 deflines(curln, curln);
         1399                 delete(line1, line2);
         1400                 append(prevln(line1));
         1401                 break;
         1402         case 'j':
         1403                 chkprint(1);
         1404                 deflines(curln, curln+1);
         1405                 if (line1 != line2 && curln != 0)
         1406                               join();
         1407                 break;
         1408         case 'z':
         1409                 if (nlines > 1)
         1410                         goto bad_address;
         1411                 if (isdigit(back(input())))
         1412                         num = getnum();
         1413                 else
         1414                         num = 24;
         1415                 chkprint(1);
         1416                 deflines(curln, curln);
         1417                 scroll(num);
         1418                 break;
         1419         case 'k':
         1420                 if (nlines > 1)
         1421                         goto bad_address;
         1422                 if (!islower(c = input()))
         1423                         error("invalid mark character");
         1424                 chkprint(1);
         1425                 deflines(curln, curln);
         1426                 marks[c - 'a'] = line1;
         1427                 break;
         1428         case 'P':
         1429                 if (nlines > 0)
         1430                         goto unexpected;
         1431                 chkprint(1);
         1432                 optprompt ^= 1;
         1433                 break;
         1434         case 'x':
         1435                 trunc = 1;
         1436         case 'X':
         1437                 ensureblank();
         1438                 if (nlines > 0)
         1439                         goto unexpected;
         1440                 exstatus = 0;
         1441                 deflines(nextln(0), lastln);
         1442                 dowrite(getfname(cmd), trunc);
         1443         case 'Q':
         1444         case 'q':
         1445                 if (nlines > 0)
         1446                         goto unexpected;
         1447                 if (cmd != 'Q' && modflag)
         1448                         goto modified;
         1449                 modflag = 0;
         1450                 quit();
         1451                 break;
         1452         case 'f':
         1453                 ensureblank();
         1454                 if (nlines > 0)
         1455                         goto unexpected;
         1456                 if (back(input()) != '\0')
         1457                         getfname(cmd);
         1458                 else
         1459                         puts(savfname);
         1460                 chkprint(0);
         1461                 break;
         1462         case 'E':
         1463         case 'e':
         1464                 ensureblank();
         1465                 if (nlines > 0)
         1466                         goto unexpected;
         1467                 if (cmd == 'e' && modflag)
         1468                         goto modified;
         1469                 setscratch();
         1470                 deflines(curln, curln);
         1471                 doread(getfname(cmd));
         1472                 clearundo();
         1473                 modflag = 0;
         1474                 break;
         1475         default:
         1476                 error("unknown command");
         1477         bad_address:
         1478                 error("invalid address");
         1479         modified:
         1480                 modflag = 0;
         1481                 error("warning: file modified");
         1482         unexpected:
         1483                 error("unexpected address");
         1484         }
         1485 
         1486         if (!pflag)
         1487                 return;
         1488         line1 = line2 = curln;
         1489 
         1490 print:
         1491         doprint();
         1492 }
         1493 
         1494 static int
         1495 chkglobal(void)
         1496 {
         1497         int delim, c, dir, i, v;
         1498 
         1499         uflag = 1;
         1500         gflag = 0;
         1501         skipblank();
         1502 
         1503         switch (c = input()) {
         1504         case 'g':
         1505                 uflag = 0;
         1506         case 'G':
         1507                 dir = 1;
         1508                 break;
         1509         case 'v':
         1510                 uflag = 0;
         1511         case 'V':
         1512                 dir = 0;
         1513                 break;
         1514         default:
         1515                 back(c);
         1516                 return 0;
         1517         }
         1518         gflag = 1;
         1519         deflines(nextln(0), lastln);
         1520         delim = input();
         1521         compile(delim);
         1522 
         1523         for (i = 1; i <= lastln; ++i) {
         1524                 chksignals();
         1525                 if (i >= line1 && i <= line2)
         1526                         v = match(i) == dir;
         1527                 else
         1528                         v = 0;
         1529                 setglobal(i, v);
         1530         }
         1531 
         1532         return 1;
         1533 }
         1534 
         1535 static void
         1536 savecmd(void)
         1537 {
         1538         int ch;
         1539 
         1540         skipblank();
         1541         ch = input();
         1542         if (ch != '&') {
         1543                 ocmdline = strdup(cmdline.str);
         1544                 if (ocmdline == NULL)
         1545                         error("out of memory");
         1546         }
         1547         back(ch);
         1548 }
         1549 
         1550 static void
         1551 doglobal(void)
         1552 {
         1553         int cnt, ln, k, idx;
         1554 
         1555         skipblank();
         1556         gflag = 1;
         1557         if (uflag)
         1558                 chkprint(0);
         1559 
         1560         ln = line1;
         1561         for (cnt = 0; cnt < lastln; ) {
         1562                 chksignals();
         1563                 k = getindex(ln);
         1564                 if (zero[k].global) {
         1565                         zero[k].global = 0;
         1566                         curln = ln;
         1567                         nlines = 0;
         1568 
         1569                         if (!uflag) {
         1570                                 idx = inputidx;
         1571                                 getlst();
         1572                                 docmd();
         1573                                 inputidx = idx;
         1574                                 continue;
         1575                         }
         1576 
         1577                         line1 = line2 = ln;
         1578                         pflag = 0;
         1579                         doprint();
         1580 
         1581                         for (;;) {
         1582                                 getinput();
         1583                                 if (strcmp(cmdline.str, "") == 0)
         1584                                         break;
         1585                                 savecmd();
         1586                                 getlst();
         1587                                 docmd();
         1588                         }
         1589 
         1590                 } else {
         1591                         cnt++;
         1592                         ln = nextln(ln);
         1593                 }
         1594         }
         1595 }
         1596 
         1597 static void
         1598 usage(void)
         1599 {
         1600         eprintf("usage: %s [-s] [-p] [file]\n", argv0);
         1601 }
         1602 
         1603 static void
         1604 sigintr(int n)
         1605 {
         1606         intr = 1;
         1607 }
         1608 
         1609 static void
         1610 sighup(int dummy)
         1611 {
         1612         hup = 1;
         1613 }
         1614 
         1615 static void
         1616 edit(void)
         1617 {
         1618         for (;;) {
         1619                 newcmd = 1;
         1620                 ocurln = curln;
         1621                 olastln = lastln;
         1622                 if (optprompt) {
         1623                         fputs(prompt, stdout);
         1624                         fflush(stdout);
         1625                 }
         1626 
         1627                 getinput();
         1628                 getlst();
         1629                 chkglobal() ? doglobal() : docmd();
         1630         }
         1631 }
         1632 
         1633 static void
         1634 init(char *fname)
         1635 {
         1636         size_t len;
         1637 
         1638         setscratch();
         1639         if (!fname)
         1640                 return;
         1641         if ((len = strlen(fname)) >= FILENAME_MAX || len == 0)
         1642                 error("incorrect filename");
         1643         memcpy(savfname, fname, len);
         1644         doread(fname);
         1645         clearundo();
         1646 }
         1647 
         1648 int
         1649 main(int argc, char *argv[])
         1650 {
         1651         ARGBEGIN {
         1652         case 'p':
         1653                 prompt = EARGF(usage());
         1654                 optprompt = 1;
         1655                 break;
         1656         case 's':
         1657                 optdiag = 0;
         1658                 break;
         1659         default:
         1660                 usage();
         1661         } ARGEND
         1662 
         1663         if (argc > 1)
         1664                 usage();
         1665 
         1666         if (!setjmp(savesp)) {
         1667                 sigaction(SIGINT,
         1668                           &(struct sigaction) {.sa_handler = sigintr},
         1669                           NULL);
         1670                 sigaction(SIGHUP,
         1671                           &(struct sigaction) {.sa_handler = sighup},
         1672                           NULL);
         1673                 sigaction(SIGQUIT,
         1674                           &(struct sigaction) {.sa_handler = SIG_IGN},
         1675                           NULL);
         1676                 init(*argv);
         1677         }
         1678         edit();
         1679 
         1680         /* not reached */
         1681         return 0;
         1682 }