master xplshn/aruu / cmd / posix / ed.c
   1/* See LICENSE file for copyright and license details. */
   2
   3#include <fcntl.h>
   4#include <regex.h>
   5#include <sys/stat.h>
   6#include <unistd.h>
   7
   8#include <ctype.h>
   9#include <limits.h>
  10#include <setjmp.h>
  11#include <signal.h>
  12#include <stdint.h>
  13#include <stdio.h>
  14#include <stdlib.h>
  15#include <string.h>
  16
  17#include "util.h"
  18#include "wexec.h"
  19
  20#define REGEXSIZE 100
  21#define LINESIZE  80
  22#define NUMLINES  32
  23#define CACHESIZ  4096
  24#define AFTER     0
  25#define BEFORE    1
  26
  27typedef struct {
  28  char  *str;
  29  size_t cap;
  30  size_t siz;
  31} String;
  32
  33struct hline {
  34  off_t seek;
  35  char  global;
  36  int   next, prev;
  37};
  38
  39struct undo {
  40  int    curln, lastln;
  41  size_t nr, cap;
  42  struct link {
  43    int to1, from1;
  44    int to2, from2;
  45  } *vec;
  46};
  47
  48static char      *prompt = "*";
  49static regex_t   *pattern;
  50static regmatch_t matchs[10];
  51static String     lastre;
  52
  53static int           optverbose, optprompt, exstatus, optdiag = 1;
  54static int           marks['z' - 'a' + 1];
  55static int           nlines, line1, line2;
  56static int           curln, lastln, ocurln, olastln;
  57static jmp_buf       savesp;
  58static char         *lasterr;
  59static size_t        idxsize, lastidx;
  60static struct hline *zero;
  61static String        text;
  62static char          savfname[FILENAME_MAX];
  63static char          tmpname[FILENAME_MAX];
  64static int           scratch;
  65static int           pflag, modflag, uflag, gflag;
  66static size_t        csize;
  67static String        cmdline;
  68static char         *ocmdline;
  69static int           inputidx;
  70static char         *rhs;
  71static char         *lastmatch;
  72static struct undo   udata;
  73static int           newcmd;
  74
  75static sig_atomic_t intr, hup;
  76
  77static void undo(void);
  78
  79static void
  80error(char *msg)
  81{
  82  exstatus = 1;
  83  lasterr  = msg;
  84  puts("?");
  85
  86  if (optverbose)
  87    puts(msg);
  88  if (!newcmd)
  89    undo();
  90
  91  curln = ocurln;
  92  longjmp(savesp, 1);
  93}
  94
  95static int
  96nextln(int line)
  97{
  98  ++line;
  99  return (line > lastln) ? 0 : line;
 100}
 101
 102static int
 103prevln(int line)
 104{
 105  --line;
 106  return (line < 0) ? lastln : line;
 107}
 108
 109static String *
 110copystring(String *s, char *from)
 111{
 112  size_t len;
 113  char  *t;
 114
 115  if ((t = strdup(from)) == NULL)
 116    error("out of memory");
 117  len = strlen(t);
 118
 119  free(s->str);
 120  s->str = t;
 121  s->siz = len;
 122  s->cap = len;
 123
 124  return s;
 125}
 126
 127static String *
 128string(String *s)
 129{
 130  free(s->str);
 131  s->str = NULL;
 132  s->siz = 0;
 133  s->cap = 0;
 134
 135  return s;
 136}
 137
 138static char *
 139addchar(char c, String *s)
 140{
 141  size_t cap = s->cap, siz = s->siz;
 142  char  *t = s->str;
 143
 144  if (siz >= cap && (cap > SIZE_MAX - LINESIZE || (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
 153static void chksignals(void);
 154
 155static int
 156input(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
 168static int
 169back(int c)
 170{
 171  if (c == '\0')
 172    return c;
 173  return cmdline.str[--inputidx] = c;
 174}
 175
 176static int
 177makeline(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 || write(scratch, begin, len) < 0) {
 204      error("input/output error");
 205    }
 206  }
 207  if (off)
 208    *off = len;
 209  ++lastidx;
 210  return lp - zero;
 211}
 212
 213static int
 214getindex(int line)
 215{
 216  struct hline *lp;
 217  int           n;
 218
 219  if (line == -1)
 220    line = 0;
 221  for (n = 0, lp = zero; n != line; n++)
 222    lp = zero + lp->next;
 223
 224  return lp - zero;
 225}
 226
 227static char *
 228gettxt(int line)
 229{
 230  static char   buf[CACHESIZ];
 231  static off_t  lasto;
 232  struct hline *lp;
 233  off_t         off, block;
 234  ssize_t       n;
 235  char         *p;
 236
 237  lp       = zero + getindex(line);
 238  text.siz = 0;
 239  off      = lp->seek;
 240
 241  if (off == (off_t)-1)
 242    return addchar('\0', &text);
 243
 244repeat:
 245  chksignals();
 246  if (!csize || off < lasto || (size_t)(off - lasto) >= csize) {
 247    block = off & ~(CACHESIZ - 1);
 248    if (lseek(scratch, block, SEEK_SET) < 0 || (n = read(scratch, buf, CACHESIZ)) < 0) {
 249      error("input/output error");
 250    }
 251    csize = n;
 252    lasto = block;
 253  }
 254  for (p = buf + off - lasto; p < buf + csize && *p != '\n'; ++p) {
 255    ++off;
 256    addchar(*p, &text);
 257  }
 258  if (csize == CACHESIZ && p == buf + csize)
 259    goto repeat;
 260
 261  addchar('\n', &text);
 262  addchar('\0', &text);
 263  return text.str;
 264}
 265
 266static void
 267setglobal(int i, int v)
 268{
 269  zero[getindex(i)].global = v;
 270}
 271
 272static void
 273clearundo(void)
 274{
 275  free(udata.vec);
 276  udata.vec = NULL;
 277  newcmd = udata.nr = udata.cap = 0;
 278  modflag                       = 0;
 279}
 280
 281static void
 282newundo(int from1, int from2)
 283{
 284  struct link *p;
 285
 286  if (newcmd) {
 287    clearundo();
 288    udata.curln  = ocurln;
 289    udata.lastln = olastln;
 290  }
 291  if (udata.nr >= udata.cap) {
 292    size_t siz = (udata.cap + 10) * sizeof(struct link);
 293    if ((p = realloc(udata.vec, siz)) == NULL)
 294      error("out of memory");
 295    udata.vec = p;
 296    udata.cap = udata.cap + 10;
 297  }
 298  p        = &udata.vec[udata.nr++];
 299  p->from1 = from1;
 300  p->to1   = zero[from1].next;
 301  p->from2 = from2;
 302  p->to2   = zero[from2].prev;
 303}
 304
 305/*
 306 * relink: to1   <- from1
 307 *         from2 -> to2
 308 */
 309static void
 310relink(int to1, int from1, int from2, int to2)
 311{
 312  newundo(from1, from2);
 313  zero[from1].next = to1;
 314  zero[from2].prev = to2;
 315  modflag          = 1;
 316}
 317
 318static void
 319undo(void)
 320{
 321  struct link *p;
 322
 323  if (udata.nr == 0)
 324    return;
 325  for (p = &udata.vec[udata.nr - 1]; udata.nr > 0; --p) {
 326    --udata.nr;
 327    zero[p->from1].next = p->to1;
 328    zero[p->from2].prev = p->to2;
 329  }
 330  free(udata.vec);
 331  udata.vec = NULL;
 332  udata.cap = 0;
 333  curln     = udata.curln;
 334  lastln    = udata.lastln;
 335}
 336
 337static void
 338inject(char *s, int where)
 339{
 340  int off, k, begin, end;
 341
 342  if (where == BEFORE) {
 343    begin = getindex(curln - 1);
 344    end   = getindex(nextln(curln - 1));
 345  } else {
 346    begin = getindex(curln);
 347    end   = getindex(nextln(curln));
 348  }
 349  while (*s) {
 350    k = makeline(s, &off);
 351    s += off;
 352    relink(k, begin, k, begin);
 353    relink(end, k, end, k);
 354    ++lastln;
 355    ++curln;
 356    begin = k;
 357  }
 358}
 359
 360static void
 361clearbuf(void)
 362{
 363  if (scratch)
 364    close(scratch);
 365  remove(tmpname);
 366  free(zero);
 367  zero    = NULL;
 368  scratch = csize = idxsize = lastidx = curln = lastln = 0;
 369  modflag = lastln = curln = 0;
 370}
 371
 372static void
 373setscratch(void)
 374{
 375  int   r, k;
 376  char *dir;
 377
 378  clearbuf();
 379  clearundo();
 380  if ((dir = getenv("TMPDIR")) == NULL)
 381    dir = "/tmp";
 382  r = snprintf(tmpname, sizeof(tmpname), "%s/%s", dir, "ed.XXXXXX");
 383  if (r < 0 || (size_t)r >= sizeof(tmpname))
 384    error("scratch filename too long");
 385  if ((scratch = mkstemp(tmpname)) < 0)
 386    error("failed to create scratch file");
 387  if ((k = makeline(NULL, NULL)))
 388    error("input/output error in scratch file");
 389  relink(k, k, k, k);
 390  clearundo();
 391}
 392
 393static void
 394compile(int delim)
 395{
 396  int         n, ret, c, bracket;
 397  static char buf[BUFSIZ];
 398
 399  if (!isgraph(delim))
 400    error("invalid pattern delimiter");
 401
 402  bracket = lastre.siz = 0;
 403  for (n = 0;; ++n) {
 404    c = input();
 405    if ((c == delim && !bracket) || c == '\0') {
 406      break;
 407    } else if (c == '\\') {
 408      addchar(c, &lastre);
 409      c = input();
 410    } else if (c == '[') {
 411      bracket = 1;
 412    } else if (c == ']') {
 413      bracket = 0;
 414    }
 415    addchar(c, &lastre);
 416  }
 417  if (n == 0) {
 418    if (!pattern)
 419      error("no previous pattern");
 420    return;
 421  }
 422  addchar('\0', &lastre);
 423
 424  if (pattern)
 425    regfree(pattern);
 426  if (!pattern && (!(pattern = malloc(sizeof(*pattern)))))
 427    error("out of memory");
 428  if ((ret = regcomp(pattern, lastre.str, 0))) {
 429    regerror(ret, pattern, buf, sizeof(buf));
 430    error(buf);
 431  }
 432}
 433
 434static int
 435match(int num)
 436{
 437  int r;
 438
 439  lastmatch              = gettxt(num);
 440  text.str[text.siz - 2] = '\0';
 441  r                      = !regexec(pattern, lastmatch, 10, matchs, 0);
 442  text.str[text.siz - 2] = '\n';
 443
 444  return r;
 445}
 446
 447static int
 448rematch(int num)
 449{
 450  (void)num;
 451  regoff_t    off = matchs[0].rm_eo;
 452  regmatch_t *m;
 453  int         r;
 454
 455  text.str[text.siz - 2] = '\0';
 456  r                      = !regexec(pattern, lastmatch + off, 10, matchs, REG_NOTBOL);
 457  text.str[text.siz - 2] = '\n';
 458
 459  if (!r)
 460    return 0;
 461
 462  if (matchs[0].rm_eo > 0) {
 463    lastmatch += off;
 464    return 1;
 465  }
 466
 467  /* Zero width match was found at the end of the input, done */
 468  if (lastmatch[off] == '\n') {
 469    lastmatch += off;
 470    return 0;
 471  }
 472
 473  /* Zero width match at the current posiion, find the next one */
 474  text.str[text.siz - 2] = '\0';
 475  r                      = !regexec(pattern, lastmatch + off + 1, 10, matchs, REG_NOTBOL);
 476  text.str[text.siz - 2] = '\n';
 477
 478  if (!r)
 479    return 0;
 480
 481  /* Re-adjust matches to account for +1 in regexec */
 482  for (m = matchs; m < &matchs[10]; m++) {
 483    m->rm_so += 1;
 484    m->rm_eo += 1;
 485  }
 486  lastmatch += off;
 487
 488  return 1;
 489}
 490
 491static int
 492search(int way)
 493{
 494  int i;
 495
 496  i = curln;
 497  do {
 498    chksignals();
 499
 500    i = (way == '?') ? prevln(i) : nextln(i);
 501    if (i > 0 && match(i))
 502      return i;
 503  } while (i != curln);
 504
 505  error("invalid address");
 506  return -1; /* not reached */
 507}
 508
 509static void
 510skipblank(void)
 511{
 512  char c;
 513
 514  while ((c = input()) == ' ' || c == '\t')
 515    ;
 516  back(c);
 517}
 518
 519static void
 520ensureblank(void)
 521{
 522  char c;
 523
 524  switch ((c = input())) {
 525    case ' ':
 526    case '\t':
 527      skipblank();
 528      /* fallthrough */
 529    case '\0':
 530      back(c);
 531      break;
 532    default:
 533      error("unknown command");
 534  }
 535}
 536
 537static int
 538getnum(void)
 539{
 540  int ln, n, c;
 541
 542  for (ln = 0; isdigit(c = input()); ln += n) {
 543    if (ln > INT_MAX / 10)
 544      goto invalid;
 545    n = c - '0';
 546    ln *= 10;
 547    if (INT_MAX - ln < n)
 548      goto invalid;
 549  }
 550  back(c);
 551  return ln;
 552
 553invalid:
 554  error("invalid address");
 555  return -1; /* not reached */
 556}
 557
 558static int
 559linenum(int *line)
 560{
 561  int ln, c;
 562
 563  skipblank();
 564
 565  switch (c = input()) {
 566    case '.':
 567      ln = curln;
 568      break;
 569    case '\'':
 570      skipblank();
 571      if (!islower(c = input()))
 572        error("invalid mark character");
 573      if (!(ln = marks[c - 'a']))
 574        error("invalid address");
 575      break;
 576    case '$':
 577      ln = lastln;
 578      break;
 579    case '?':
 580    case '/':
 581      compile(c);
 582      ln = search(c);
 583      break;
 584    case '^':
 585    case '-':
 586    case '+':
 587      ln = curln;
 588      back(c);
 589      break;
 590    default:
 591      back(c);
 592      if (isdigit(c))
 593        ln = getnum();
 594      else
 595        return 0;
 596      break;
 597  }
 598  *line = ln;
 599  return 1;
 600}
 601
 602static int
 603address(int *line)
 604{
 605  int ln, sign, c, num;
 606
 607  if (!linenum(&ln))
 608    return 0;
 609
 610  for (;;) {
 611    skipblank();
 612    if ((c = input()) != '+' && c != '-' && c != '^')
 613      break;
 614    sign = c == '+' ? 1 : -1;
 615    num  = isdigit(back(input())) ? getnum() : 1;
 616    num *= sign;
 617    if (INT_MAX - ln < num)
 618      goto invalid;
 619    ln += num;
 620  }
 621  back(c);
 622
 623  if (ln < 0 || ln > lastln)
 624    error("invalid address");
 625  *line = ln;
 626  return 1;
 627
 628invalid:
 629  error("invalid address");
 630  return -1; /* not reached */
 631}
 632
 633static void
 634getlst(void)
 635{
 636  int ln, c;
 637
 638  if ((c = input()) == ',') {
 639    line1  = 1;
 640    line2  = lastln;
 641    nlines = lastln;
 642    return;
 643  } else if (c == ';') {
 644    line1  = curln;
 645    line2  = lastln;
 646    nlines = lastln - curln + 1;
 647    return;
 648  }
 649  back(c);
 650  line2 = curln;
 651  for (nlines = 0; address(&ln);) {
 652    line1 = line2;
 653    line2 = ln;
 654    ++nlines;
 655
 656    skipblank();
 657    if ((c = input()) != ',' && c != ';') {
 658      back(c);
 659      break;
 660    }
 661    if (c == ';')
 662      curln = line2;
 663  }
 664  if (nlines > 2)
 665    nlines = 2;
 666  else if (nlines <= 1)
 667    line1 = line2;
 668}
 669
 670static void
 671deflines(int def1, int def2)
 672{
 673  if (!nlines) {
 674    line1 = def1;
 675    line2 = def2;
 676  }
 677  if (line1 > line2 || line1 < 0 || line2 > lastln)
 678    error("invalid address");
 679}
 680
 681static void
 682quit(void)
 683{
 684  clearbuf();
 685  exit(exstatus);
 686}
 687
 688static void
 689setinput(char *s)
 690{
 691  copystring(&cmdline, s);
 692  inputidx = 0;
 693}
 694
 695static void
 696getinput(void)
 697{
 698  int ch;
 699
 700  string(&cmdline);
 701
 702  while ((ch = getchar()) != '\n' && ch != EOF) {
 703    if (ch == '\\') {
 704      if ((ch = getchar()) == EOF)
 705        break;
 706      if (ch != '\n')
 707        addchar('\\', &cmdline);
 708    }
 709    addchar(ch, &cmdline);
 710  }
 711
 712  addchar('\0', &cmdline);
 713  inputidx = 0;
 714
 715  if (ch == EOF) {
 716    chksignals();
 717    if (ferror(stdin)) {
 718      exstatus = 1;
 719      fputs("ed: error reading input\n", stderr);
 720    }
 721    quit();
 722  }
 723}
 724
 725static int
 726moreinput(void)
 727{
 728  if (!uflag)
 729    return cmdline.str[inputidx] != '\0';
 730
 731  getinput();
 732  return 1;
 733}
 734
 735static void dowrite(const char *, int);
 736
 737static void
 738dump(void)
 739{
 740  char *home;
 741
 742  if (modflag)
 743    return;
 744
 745  line1 = nextln(0);
 746  line2 = lastln;
 747
 748  if (!setjmp(savesp)) {
 749    dowrite("ed.hup", 1);
 750    return;
 751  }
 752
 753  home = getenv("HOME");
 754  if (!home || chdir(home) < 0)
 755    return;
 756
 757  if (!setjmp(savesp))
 758    dowrite("ed.hup", 1);
 759}
 760
 761static void
 762chksignals(void)
 763{
 764  if (hup) {
 765    exstatus = 1;
 766    dump();
 767    quit();
 768  }
 769
 770  if (intr) {
 771    intr   = 0;
 772    newcmd = 1;
 773    clearerr(stdin);
 774    error("Interrupt");
 775  }
 776}
 777
 778static const char *
 779expandcmd(void)
 780{
 781  static String cmd;
 782  char         *p;
 783  int           c, repl = 0;
 784
 785  skipblank();
 786  if ((c = input()) != '!') {
 787    back(c);
 788    string(&cmd);
 789  } else if (cmd.siz) {
 790    --cmd.siz;
 791    repl = 1;
 792  } else {
 793    error("no previous command");
 794  }
 795
 796  while ((c = input()) != '\0') {
 797    switch (c) {
 798      case '%':
 799        if (savfname[0] == '\0')
 800          error("no current filename");
 801        repl = 1;
 802        for (p = savfname; *p; ++p)
 803          addchar(*p, &cmd);
 804        break;
 805      case '\\':
 806        c = input();
 807        if (c != '%') {
 808          back(c);
 809          c = '\\';
 810        }
 811        /* fallthrough */
 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
 824static void
 825dowrite(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 ? wpclose(fp) : fclose(fp);
 836    fp = NULL;
 837  }
 838
 839  if (fname[0] == '!') {
 840    sh = 1;
 841    if ((fp = wpopen(expandcmd(), NULL, "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 ? wpclose(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
 874static void
 875doread(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 ? wpclose(fp) : fclose(fp);
 889    fp = NULL;
 890  }
 891
 892  if (fname[0] == '!') {
 893    sh = 1;
 894    if ((fp = wpopen(expandcmd(), NULL, "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 ((size_t)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 ? wpclose(aux) : fclose(aux);
 920  if (r)
 921    error("input/output error");
 922}
 923
 924static void
 925doprint(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
 972static void
 973dohelp(void)
 974{
 975  if (lasterr)
 976    puts(lasterr);
 977}
 978
 979static void
 980chkprint(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
 994static char *
 995getfname(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
1026static void
1027append(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
1046static void delete (int from, int to)
1047{
1048  int lto, lfrom;
1049
1050  if (!from)
1051    error("incorrect address");
1052
1053  lfrom = getindex(prevln(from));
1054  lto   = getindex(nextln(to));
1055  lastln -= to - from + 1;
1056  curln = (from > lastln) ? lastln : from;
1057  ;
1058  relink(lto, lfrom, lto, lfrom);
1059}
1060
1061static void
1062move(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
1087static void
1088join(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
1109static void
1110scroll(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
1129static void
1130copy(int where)
1131{
1132  if (!line1)
1133    error("incorrect address");
1134  curln = where;
1135
1136  while (line1 <= line2) {
1137    chksignals();
1138    inject(gettxt(line1), AFTER);
1139    if (line2 >= curln)
1140      line2 = nextln(line2);
1141    line1 = nextln(line1);
1142    if (line1 >= curln)
1143      line1 = nextln(line1);
1144  }
1145}
1146
1147static void
1148execsh(void)
1149{
1150  wsystem(expandcmd());
1151  if (optdiag)
1152    puts("!");
1153}
1154
1155static void
1156getrhs(int delim)
1157{
1158  int           c;
1159  static String s;
1160
1161  string(&s);
1162  while ((c = input()) != '\0' && c != delim)
1163    addchar(c, &s);
1164  addchar('\0', &s);
1165  if (c == '\0') {
1166    pflag = 'p';
1167    back(c);
1168  }
1169
1170  if (!strcmp("%", s.str)) {
1171    if (!rhs)
1172      error("no previous substitution");
1173    free(s.str);
1174  } else {
1175    free(rhs);
1176    rhs = s.str;
1177  }
1178  s.str = NULL;
1179}
1180
1181static int
1182getnth(void)
1183{
1184  int c;
1185
1186  if ((c = input()) == 'g') {
1187    return -1;
1188  } else if (isdigit(c)) {
1189    if (c == '0')
1190      return -1;
1191    return c - '0';
1192  } else {
1193    back(c);
1194    return 1;
1195  }
1196}
1197
1198static void
1199addpre(String *s)
1200{
1201  char *p;
1202
1203  for (p = lastmatch; p < lastmatch + matchs[0].rm_so; ++p)
1204    addchar(*p, s);
1205}
1206
1207static void
1208addpost(String *s)
1209{
1210  char c, *p;
1211
1212  for (p = lastmatch + matchs[0].rm_eo; (c = *p); ++p)
1213    addchar(c, s);
1214  addchar('\0', s);
1215}
1216
1217static int
1218addsub(String *s, int nth, int nmatch)
1219{
1220  char *end, *q, *p, c;
1221  int   sub;
1222
1223  if (nth != nmatch && nth != -1) {
1224    q   = lastmatch + matchs[0].rm_so;
1225    end = lastmatch + matchs[0].rm_eo;
1226    while (q < end)
1227      addchar(*q++, s);
1228    return 0;
1229  }
1230
1231  for (p = rhs; (c = *p); ++p) {
1232    switch (c) {
1233      case '&':
1234        sub = 0;
1235        goto copy_match;
1236      case '\\':
1237        if ((c = *++p) == '\0')
1238          return 1;
1239        if (!isdigit(c))
1240          goto copy_char;
1241        sub = c - '0';
1242      copy_match:
1243        q   = lastmatch + matchs[sub].rm_so;
1244        end = lastmatch + matchs[sub].rm_eo;
1245        while (q < end)
1246          addchar(*q++, s);
1247        break;
1248      default:
1249      copy_char:
1250        addchar(c, s);
1251        break;
1252    }
1253  }
1254  return 1;
1255}
1256
1257static void
1258subline(int num, int nth)
1259{
1260  int           i, m, changed;
1261  static String s;
1262
1263  string(&s);
1264  i = changed = 0;
1265  for (m = match(num); m; m = (nth < 0 || i < nth) && rematch(num)) {
1266    chksignals();
1267    addpre(&s);
1268    changed |= addsub(&s, nth, ++i);
1269  }
1270  if (!changed)
1271    return;
1272  addpost(&s);
1273  delete (num, num);
1274  curln = prevln(num);
1275  inject(s.str, AFTER);
1276}
1277
1278static void
1279subst(int nth)
1280{
1281  int i, line, next;
1282
1283  line = line1;
1284  for (i = 0; i < line2 - line1 + 1; i++) {
1285    chksignals();
1286
1287    next = getindex(nextln(line));
1288    subline(line, nth);
1289
1290    /*
1291     * The substitution command can add lines, so
1292     * we have to skip lines until we find the
1293     * index that we saved before the substitution
1294     */
1295    do
1296      line = nextln(line);
1297    while (getindex(line) != next);
1298  }
1299}
1300
1301static void
1302docmd(void)
1303{
1304  char *var;
1305  int   cmd, c, line3, num, trunc;
1306
1307repeat:
1308  skipblank();
1309  cmd   = input();
1310  trunc = pflag = 0;
1311  switch (cmd) {
1312    case '&':
1313      skipblank();
1314      chkprint(0);
1315      if (!ocmdline)
1316        error("no previous command");
1317      setinput(ocmdline);
1318      getlst();
1319      goto repeat;
1320    case '!':
1321      execsh();
1322      break;
1323    case '\0':
1324      num = gflag ? curln : curln + 1;
1325      deflines(num, num);
1326      line1 = line2;
1327      pflag = 'p';
1328      goto print;
1329    case 'l':
1330    case 'n':
1331    // ?man -p: preserve file attributes
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      break;
1343    case 'H':
1344      if (nlines > 0)
1345        goto unexpected;
1346      chkprint(0);
1347      optverbose ^= 1;
1348      break;
1349    case 'h':
1350      if (nlines > 0)
1351        goto unexpected;
1352      chkprint(0);
1353      dohelp();
1354      break;
1355    case 'w':
1356      trunc = 1;
1357      /* fallthrough */
1358    case 'W':
1359      ensureblank();
1360      deflines(nextln(0), lastln);
1361      dowrite(getfname(cmd), trunc);
1362      break;
1363    case 'r':
1364      ensureblank();
1365      if (nlines > 1)
1366        goto bad_address;
1367      deflines(lastln, lastln);
1368      doread(getfname(cmd));
1369      break;
1370    case 'd':
1371      chkprint(1);
1372      deflines(curln, curln);
1373      delete (line1, line2);
1374      break;
1375    case '=':
1376      if (nlines > 1)
1377        goto bad_address;
1378      chkprint(1);
1379      deflines(lastln, lastln);
1380      printf("%d\n", line1);
1381      break;
1382    case 'u':
1383      if (nlines > 0)
1384        goto bad_address;
1385      chkprint(1);
1386      if (udata.nr == 0)
1387        error("nothing to undo");
1388      undo();
1389      break;
1390    // ?man -s: silent mode or print summary
1391    case 's':
1392      deflines(curln, curln);
1393      c = input();
1394      compile(c);
1395      getrhs(c);
1396      num = getnth();
1397      chkprint(1);
1398      subst(num);
1399      break;
1400    case 'i':
1401      if (nlines > 1)
1402        goto bad_address;
1403      chkprint(1);
1404      deflines(curln, curln);
1405      if (!line1)
1406        line1++;
1407      append(prevln(line1));
1408      break;
1409    case 'a':
1410      if (nlines > 1)
1411        goto bad_address;
1412      chkprint(1);
1413      deflines(curln, curln);
1414      append(line1);
1415      break;
1416    case 'm':
1417      deflines(curln, curln);
1418      if (!address(&line3))
1419        line3 = curln;
1420      chkprint(1);
1421      move(line3);
1422      break;
1423    case 't':
1424      deflines(curln, curln);
1425      if (!address(&line3))
1426        line3 = curln;
1427      chkprint(1);
1428      copy(line3);
1429      break;
1430    case 'c':
1431      chkprint(1);
1432      deflines(curln, curln);
1433      delete (line1, line2);
1434      append(prevln(line1));
1435      break;
1436    case 'j':
1437      chkprint(1);
1438      deflines(curln, curln + 1);
1439      if (line1 != line2 && curln != 0)
1440        join();
1441      break;
1442    case 'z':
1443      if (nlines > 1)
1444        goto bad_address;
1445
1446      num = 0;
1447      if (isdigit(back(input())))
1448        num = getnum();
1449      else if ((var = getenv("LINES")) != NULL)
1450        num = atoi(var) - 1;
1451      if (num <= 0)
1452        num = 23;
1453      chkprint(1);
1454      deflines(curln, curln);
1455      scroll(num);
1456      break;
1457    case 'k':
1458      if (nlines > 1)
1459        goto bad_address;
1460      if (!islower(c = input()))
1461        error("invalid mark character");
1462      chkprint(1);
1463      deflines(curln, curln);
1464      marks[c - 'a'] = line1;
1465      break;
1466    case 'P':
1467      if (nlines > 0)
1468        goto unexpected;
1469      chkprint(1);
1470      optprompt ^= 1;
1471      break;
1472    case 'x':
1473      trunc = 1;
1474      /* fallthrough */
1475    case 'X':
1476      ensureblank();
1477      if (nlines > 0)
1478        goto unexpected;
1479      exstatus = 0;
1480      deflines(nextln(0), lastln);
1481      dowrite(getfname(cmd), trunc);
1482      /* fallthrough */
1483    case 'Q':
1484    case 'q':
1485      if (nlines > 0)
1486        goto unexpected;
1487      if (cmd != 'Q' && modflag)
1488        goto modified;
1489      modflag = 0;
1490      quit();
1491      break;
1492    case 'f':
1493      ensureblank();
1494      if (nlines > 0)
1495        goto unexpected;
1496      if (back(input()) != '\0')
1497        getfname(cmd);
1498      else
1499        puts(savfname);
1500      chkprint(0);
1501      break;
1502    case 'E':
1503    case 'e':
1504      ensureblank();
1505      if (nlines > 0)
1506        goto unexpected;
1507      if (cmd == 'e' && modflag)
1508        goto modified;
1509      setscratch();
1510      deflines(curln, curln);
1511      doread(getfname(cmd));
1512      clearundo();
1513      modflag = 0;
1514      break;
1515    default:
1516      error("unknown command");
1517    bad_address:
1518      error("invalid address");
1519    modified:
1520      modflag = 0;
1521      error("warning: file modified");
1522    unexpected:
1523      error("unexpected address");
1524  }
1525
1526  if (!pflag)
1527    return;
1528  line1 = line2 = curln;
1529
1530print:
1531  doprint();
1532}
1533
1534static int
1535chkglobal(void)
1536{
1537  int delim, c, dir, i, v;
1538
1539  uflag = 1;
1540  gflag = 0;
1541  skipblank();
1542
1543  switch (c = input()) {
1544    case 'g':
1545      uflag = 0;
1546      /* fallthrough */
1547    case 'G':
1548      dir = 1;
1549      break;
1550    case 'v':
1551      uflag = 0;
1552      /* fallthrough */
1553    case 'V':
1554      dir = 0;
1555      break;
1556    default:
1557      back(c);
1558      return 0;
1559  }
1560  gflag = 1;
1561  deflines(nextln(0), lastln);
1562  delim = input();
1563  compile(delim);
1564
1565  for (i = 1; i <= lastln; ++i) {
1566    chksignals();
1567    if (i >= line1 && i <= line2)
1568      v = match(i) == dir;
1569    else
1570      v = 0;
1571    setglobal(i, v);
1572  }
1573
1574  return 1;
1575}
1576
1577static void
1578savecmd(void)
1579{
1580  int ch;
1581
1582  skipblank();
1583  ch = input();
1584  if (ch != '&') {
1585    ocmdline = strdup(cmdline.str);
1586    if (ocmdline == NULL)
1587      error("out of memory");
1588  }
1589  back(ch);
1590}
1591
1592static void
1593doglobal(void)
1594{
1595  int cnt, ln, k, idx, c;
1596
1597  skipblank();
1598  gflag = 1;
1599  if (uflag)
1600    chkprint(0);
1601
1602  ln = line1;
1603  for (cnt = 0; cnt < lastln;) {
1604    chksignals();
1605    k = getindex(ln);
1606    if (zero[k].global) {
1607      zero[k].global = 0;
1608      curln          = ln;
1609      nlines         = 0;
1610
1611      if (!uflag) {
1612        idx = inputidx;
1613        getlst();
1614        for (;;) {
1615          docmd();
1616          if (!(c = input()))
1617            break;
1618          back(c);
1619        }
1620        inputidx = idx;
1621        continue;
1622      }
1623
1624      line1 = line2 = ln;
1625      pflag         = 0;
1626      doprint();
1627
1628      for (;;) {
1629        getinput();
1630        if (strcmp(cmdline.str, "") == 0)
1631          break;
1632        savecmd();
1633        getlst();
1634        docmd();
1635      }
1636
1637    } else {
1638      cnt++;
1639      ln = nextln(ln);
1640    }
1641  }
1642}
1643
1644static void
1645usage(void)
1646{
1647  eprintf("usage: %s [-s] [-p] [file]\n", argv0);
1648}
1649
1650static void
1651sigintr(int n)
1652{
1653  (void)n;
1654  intr = 1;
1655}
1656
1657static void
1658sighup(int dummy)
1659{
1660  (void)dummy;
1661  hup = 1;
1662}
1663
1664static void
1665edit(void)
1666{
1667  for (;;) {
1668    newcmd  = 1;
1669    ocurln  = curln;
1670    olastln = lastln;
1671    if (optprompt) {
1672      fputs(prompt, stdout);
1673      fflush(stdout);
1674    }
1675
1676    getinput();
1677    getlst();
1678    chkglobal() ? doglobal() : docmd();
1679  }
1680}
1681
1682static void
1683init(char *fname)
1684{
1685  size_t len;
1686
1687  setscratch();
1688  if (!fname)
1689    return;
1690  if ((len = strlen(fname)) >= FILENAME_MAX || len == 0)
1691    error("incorrect filename");
1692  memcpy(savfname, fname, len);
1693  doread(fname);
1694  clearundo();
1695}
1696
1697// ?man ed: line editor
1698// ?man arguments: file
1699// ?man simple text line editor
1700int
1701main(int argc, char *argv[])
1702{
1703  ARGBEGIN
1704  {
1705    // ?man -p:str: preserve file attributes
1706    case 'p':
1707      prompt    = EARGF(usage());
1708      optprompt = 1;
1709      break;
1710    // ?man -s: silent mode or print summary
1711    case 's':
1712      optdiag = 0;
1713      break;
1714    default:
1715      usage();
1716  }
1717  ARGEND
1718
1719  if (argc > 1)
1720    usage();
1721
1722  if (!setjmp(savesp)) {
1723    sigaction(SIGINT, &(struct sigaction){.sa_handler = sigintr}, NULL);
1724    sigaction(SIGHUP, &(struct sigaction){.sa_handler = sighup}, NULL);
1725    sigaction(SIGQUIT, &(struct sigaction){.sa_handler = SIG_IGN}, NULL);
1726    init(*argv);
1727  }
1728  edit();
1729
1730  /* not reached */
1731  return 0;
1732}