OSDN Git Service

Move genericish function to lib.
[android-x86/external-toybox.git] / toys / posix / sed.c
index 3def9d4..c62d2d1 100644 (file)
@@ -6,6 +6,9 @@
  *
  * TODO: lines > 2G could wrap signed int length counters. Not just getline()
  * but N and s///
+ * TODO: make y// handle unicode
+ * TODO: handle error return from emit(), error_msg/exit consistently
+ *       What's the right thing to do for -i when write fails? Skip to next?
 
 USE_SED(NEWTOY(sed, "(version)e*f*inEr[+Er]", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE))
 
@@ -179,13 +182,20 @@ GLOBALS(
   unsigned xx;
 )
 
-struct step {
-  struct step *next, *prev;
+// Linked list of parsed sed commands. Offset fields indicate location where
+// regex or string starts, ala offset+(char *)struct, because we remalloc()
+// these to expand them for multiline inputs, and pointers would have to be
+// individually adjusted.
+
+struct sedcmd {
+  struct sedcmd *next, *prev;
 
   // Begin and end of each match
-  long lmatch[2];
-  int rmatch[2], arg1, arg2, w; // offsets because remalloc()
-  unsigned not, hit, sflags;
+  long lmatch[2]; // line number of match
+  int rmatch[2];  // offset of regex struct for prefix matches (/abc/,/def/p)
+  int arg1, arg2, w; // offset of two arguments per command, plus s//w filename
+  unsigned not, hit;
+  unsigned sflags; // s///flag bits: i=1, g=2, p=4
   char c; // action
 };
 
@@ -195,9 +205,9 @@ static int emit(char *line, long len, int eol)
   int l, old = line[len];
 
   if (TT.noeol && !writeall(TT.fdout, "\n", 1)) return 1;
+  TT.noeol = !eol;
   if (eol) line[len++] = '\n';
   if (!len) return 0;
-  TT.noeol = len && !eol;
   l = writeall(TT.fdout, line, len);
   if (eol) line[len-1] = old;
   if (l != len) {
@@ -209,40 +219,6 @@ static int emit(char *line, long len, int eol)
   return 0;
 }
 
-// Do regex matching handling embedded NUL bytes in string. Note that
-// neither the pattern nor the match can currently include NUL bytes
-// (even with wildcards) and string must be null terminated.
-static int ghostwheel(regex_t *preg, char *string, long len, int nmatch,
-  regmatch_t pmatch[], int eflags)
-{
-  char *s = string;
-
-  for (;;) {
-    long ll = 0;
-    int rc;
-
-    while (len && !*s) {
-      s++;
-      len--;
-    }
-    while (s[ll] && ll<len) ll++;
-
-    rc = regexec(preg, s, nmatch, pmatch, eflags);
-    if (!rc) {
-      for (rc = 0; rc<nmatch && pmatch[rc].rm_so!=-1; rc++) {
-        pmatch[rc].rm_so += s-string;
-        pmatch[rc].rm_eo += s-string;
-      }
-          
-      return 0;
-    }
-    if (ll==len) return rc;
-
-    s += ll;
-    len -= ll;
-  }
-}
-
 // Extend allocation to include new string, with newline between if newlen<0
 
 static char *extend_string(char **old, char *new, int oldlen, int newlen)
@@ -271,7 +247,7 @@ static void *get_regex(void *trump, int offset)
 }
 
 // Apply pattern to line from input file
-static void walk_pattern(char **pline, long plen)
+static void process_line(char **pline, long plen)
 {
   struct append {
     struct append *next, *prev;
@@ -280,7 +256,7 @@ static void walk_pattern(char **pline, long plen)
   } *append = 0;
   char *line = TT.nextline;
   long len = TT.nextlen;
-  struct step *logrus;
+  struct sedcmd *command;
   int eol = 0, tea = 0;
 
   // Grab next line for deferred processing (EOF detection: we get a NULL
@@ -300,45 +276,45 @@ static void walk_pattern(char **pline, long plen)
 
   // The restart-1 is because we added one to make sure it wasn't NULL,
   // otherwise N as last command would restart script
-  logrus = TT.restart ? ((struct step *)TT.restart)-1 : (void *)TT.pattern;
+  command = TT.restart ? ((struct sedcmd *)TT.restart)-1 : (void *)TT.pattern;
   TT.restart = 0;
 
-  while (logrus) {
-    char *str, c = logrus->c;
+  while (command) {
+    char *str, c = command->c;
 
     // Have we got a line or regex matching range for this rule?
-    if (*logrus->lmatch || *logrus->rmatch) {
+    if (*command->lmatch || *command->rmatch) {
       int miss = 0;
       long lm;
 
       // In a match that might end?
-      if (logrus->hit) {
-        if (!(lm = logrus->lmatch[1])) {
-          if (!logrus->rmatch[1]) logrus->hit = 0;
+      if (command->hit) {
+        if (!(lm = command->lmatch[1])) {
+          if (!command->rmatch[1]) command->hit = 0;
           else {
-            void *rm = get_regex(logrus, logrus->rmatch[1]);
+            void *rm = get_regex(command, command->rmatch[1]);
 
             // regex match end includes matching line, so defer deactivation
-            if (line && !ghostwheel(rm, line, len, 0, 0, 0)) miss = 1;
+            if (line && !regexec0(rm, line, len, 0, 0, 0)) miss = 1;
           }
-        } else if (lm > 0 && lm < TT.count) logrus->hit = 0;
+        } else if (lm > 0 && lm < TT.count) command->hit = 0;
 
       // Start a new match?
       } else {
-        if (!(lm = *logrus->lmatch)) {
-          void *rm = get_regex(logrus, *logrus->rmatch);
+        if (!(lm = *command->lmatch)) {
+          void *rm = get_regex(command, *command->rmatch);
 
-          if (line && !ghostwheel(rm, line, len, 0, 0, 0)) logrus->hit++;
-        } else if (lm == TT.count || (lm == -1 && !pline)) logrus->hit++;
+          if (line && !regexec0(rm, line, len, 0, 0, 0)) command->hit++;
+        } else if (lm == TT.count || (lm == -1 && !pline)) command->hit++;
 
-        if (!logrus->lmatch[1] && !logrus->rmatch[1]) miss = 1;
+        if (!command->lmatch[1] && !command->rmatch[1]) miss = 1;
       } 
 
       // Didn't match?
-      lm = !(logrus->hit ^ logrus->not);
+      lm = !(command->hit ^ command->not);
 
       // Deferred disable from regex end match
-      if (miss || logrus->lmatch[1] == TT.count) logrus->hit = 0;
+      if (miss || command->lmatch[1] == TT.count) command->hit = 0;
 
       if (lm) {
         // Handle skipping curly bracket command group
@@ -346,19 +322,19 @@ static void walk_pattern(char **pline, long plen)
           int curly = 1;
 
           while (curly) {
-            logrus = logrus->next;
-            if (logrus->c == '{') curly++;
-            if (logrus->c == '}') curly--;
+            command = command->next;
+            if (command->c == '{') curly++;
+            if (command->c == '}') curly--;
           }
         }
-        logrus = logrus->next;
+        command = command->next;
         continue;
       }
     }
 
     // A deleted line can still update line match state for later commands
     if (!line) {
-      logrus = logrus->next;
+      command = command->next;
       continue;
     }
 
@@ -366,7 +342,7 @@ static void walk_pattern(char **pline, long plen)
 
     if (c=='a' || c=='r') {
       struct append *a = xzalloc(sizeof(struct append));
-      a->str = logrus->arg1+(char *)logrus;
+      if (command->arg1) a->str = command->arg1+(char *)command;
       a->file = c=='r';
       dlist_add_nomalloc((void *)&append, (void *)a);
     } else if (c=='b' || c=='t' || c=='T') {
@@ -374,16 +350,16 @@ static void walk_pattern(char **pline, long plen)
 
       if (c != 'b') tea = 0;
       if (c=='b' || t^(c=='T')) {
-        if (!logrus->arg1) break;
-        str = logrus->arg1+(char *)logrus;
-        for (logrus = (void *)TT.pattern; logrus; logrus = logrus->next)
-          if (logrus->c == ':' && !strcmp(logrus->arg1+(char *)logrus, str))
+        if (!command->arg1) break;
+        str = command->arg1+(char *)command;
+        for (command = (void *)TT.pattern; command; command = command->next)
+          if (command->c == ':' && !strcmp(command->arg1+(char *)command, str))
             break;
-        if (!logrus) error_exit("no :%s", str);
+        if (!command) error_exit("no :%s", str);
       }
     } else if (c=='c') {
-      str = logrus->arg1+(char *)logrus;
-      if (!logrus->hit) emit(str, strlen(str), 1);
+      str = command->arg1+(char *)command;
+      if (!command->hit) emit(str, strlen(str), 1);
       free(line);
       line = 0;
       continue;
@@ -405,7 +381,7 @@ static void walk_pattern(char **pline, long plen)
         line = 0;
       } else {
         line[len] = 0;
-        logrus = (void *)TT.pattern;
+        command = (void *)TT.pattern;
       }
       continue;
     } else if (c=='g') {
@@ -427,7 +403,7 @@ static void walk_pattern(char **pline, long plen)
       memcpy(TT.remember+TT.rememberlen, line, len);
       TT.remember[TT.rememberlen += len] = 0;
     } else if (c=='i') {
-      str = logrus->arg1+(char *)logrus;
+      str = command->arg1+(char *)command;
       emit(str, strlen(str), 1);
     } else if (c=='l') {
       int i, x, off;
@@ -455,14 +431,14 @@ static void walk_pattern(char **pline, long plen)
       toybuf[off++] = '$';
       emit(toybuf, off, 1);
     } else if (c=='n') {
-      TT.restart = logrus->next+1;
+      TT.restart = command->next+1;
 
       break;
     } else if (c=='N') {
       // Can't just grab next line because we could have multiple N and
       // we need to actually read ahead to get N;$p EOF detection right.
       if (pline) {
-        TT.restart = logrus->next+1;
+        TT.restart = command->next+1;
         extend_string(&line, TT.nextline, len, -TT.nextlen);
         free(TT.nextline);
         TT.nextline = line;
@@ -484,13 +460,13 @@ static void walk_pattern(char **pline, long plen)
 
       break;
     } else if (c=='s') {
-      char *rline = line, *new = logrus->arg2 + (char *)logrus, *swap, *rswap;
+      char *rline = line, *new = command->arg2 + (char *)command, *swap, *rswap;
       regmatch_t *match = (void *)toybuf;
-      regex_t *reg = get_regex(logrus, logrus->arg1);
+      regex_t *reg = get_regex(command, command->arg1);
       int mflags = 0, count = 0, zmatch = 1, rlen = len, mlen, off, newlen;
 
       // Find match in remaining line (up to remaining len)
-      while (!ghostwheel(reg, rline, rlen, 10, match, mflags)) {
+      while (!regexec0(reg, rline, rlen, 10, match, mflags)) {
         mflags = REG_NOTBOL;
 
         // Zero length matches don't count immediately after a previous match
@@ -503,7 +479,7 @@ static void walk_pattern(char **pline, long plen)
         } else zmatch = 0;
 
         // If we're replacing only a specific match, skip if this isn't it
-        off = logrus->sflags>>3;
+        off = command->sflags>>3;
         if (off && off != ++count) {
           rline += match[0].rm_eo;
           rlen -= match[0].rm_eo;
@@ -563,15 +539,15 @@ static void walk_pattern(char **pline, long plen)
         line = swap;
 
         // Stop after first substitution unless we have flag g
-        if (!(logrus->sflags & 2)) break;
+        if (!(command->sflags & 2)) break;
       }
 
       if (mflags) {
         // flag p
-        if (logrus->sflags & 4) emit(line, len, eol);
+        if (command->sflags & 4) emit(line, len, eol);
 
         tea = 1;
-        if (logrus->w) goto writenow;
+        if (command->w) goto writenow;
       }
     } else if (c=='w') {
       int fd, noeol;
@@ -583,14 +559,14 @@ writenow:
       noeol = TT.noeol;
 
       // We save filehandle and newline status before filename
-      name = logrus->w + (char *)logrus;
+      name = command->w + (char *)command;
       memcpy(&TT.fdout, name, 4);
       name += 4;
       TT.noeol = *(name++);
 
       // write, then save/restore context
       if (emit(line, len, eol))
-        perror_exit("w '%s'", logrus->arg1+(char *)logrus);
+        perror_exit("w '%s'", command->arg1+(char *)command);
       *(--name) = TT.noeol;
       TT.noeol = noeol;
       TT.fdout = fd;
@@ -603,11 +579,11 @@ writenow:
       TT.rememberlen = len;
       len = swap;
     } else if (c=='y') {
-      char *from, *to = (char *)logrus;
+      char *from, *to = (char *)command;
       int i, j;
 
-      from = to+logrus->arg1;
-      to += logrus->arg2;
+      from = to+command->arg1;
+      to += command->arg2;
 
       for (i = 0; i < len; i++) {
         j = stridx(from, line[i]);
@@ -618,14 +594,12 @@ writenow:
       emit(toybuf, strlen(toybuf), 1);
     }
 
-    logrus = logrus->next;
+    command = command->next;
   }
 
   if (line && !(toys.optflags & FLAG_n)) emit(line, len, eol);
 
 done:
-  free(line);
-
   if (dlist_terminate(append)) while (append) {
     struct append *a = append->next;
 
@@ -639,34 +613,12 @@ done:
         xsendfile(fd, TT.fdout);
         close(fd);
       }
-    } else emit(append->str, strlen(append->str), 1);
+    } else if (append->str) emit(append->str, strlen(append->str), 1);
+    else emit(line, 0, 0);
     free(append);
     append = a;
   }
-}
-
-// Genericish function, can probably get moved to lib.c
-
-// Iterate over lines in file, calling function. Function can write 0 to
-// the line pointer if they want to keep it, or 1 to terminate processing,
-// otherwise line is freed. Passed file descriptor is closed at the end.
-static void do_lines(int fd, char *name, void (*call)(char **pline, long len))
-{
-  FILE *fp = fd ? xfdopen(fd, "r") : stdin;
-
-  for (;;) {
-    char *line = 0;
-    ssize_t len;
-
-    len = getline(&line, (void *)&len, fp);
-    if (len > 0) {
-      call(&line, len);
-      if (line == (void *)1) break;
-      free(line);
-    } else break;
-  }
-
-  if (fd) fclose(fp);
+  free(line);
 }
 
 // Callback called on each input file
@@ -676,7 +628,7 @@ static void do_sed(int fd, char *name)
   char *tmp;
 
   if (i) {
-    struct step *primal;
+    struct sedcmd *command;
 
     if (!fd && !strcmp(name, "-")) {
       error_msg("-i on stdin");
@@ -684,12 +636,12 @@ static void do_sed(int fd, char *name)
     }
     TT.fdout = copy_tempfile(fd, name, &tmp);
     TT.count = 0;
-    for (primal = (void *)TT.pattern; primal; primal = primal->next)
-      primal->hit = 0;
+    for (command = (void *)TT.pattern; command; command = command->next)
+      command->hit = 0;
   }
-  do_lines(fd, name, walk_pattern);
+  do_lines(fd, process_line);
   if (i) {
-    walk_pattern(0, 0);
+    process_line(0, 0);
     replace_tempfile(-1, TT.fdout, &tmp);
     TT.fdout = 1;
     TT.nextline = 0;
@@ -751,38 +703,43 @@ static char *unescape_delimited_string(char **pstr, char *delim)
   return delim;
 }
 
-// Translate primal pattern into walkable form.
-static void jewel_of_judgement(char **pline, long len)
+// Translate pattern strings into command structures. Each command structure
+// is a single allocation (which requires some math and remalloc at times).
+static void parse_pattern(char **pline, long len)
 {
-  struct step *corwin = (void *)TT.pattern;
+  struct sedcmd *command = (void *)TT.pattern;
   char *line, *reg, c, *errstart;
   int i;
 
   line = errstart = pline ? *pline : "";
   if (len && line[len-1]=='\n') line[--len] = 0;
 
-  // Append additional line to pattern argument string?
-  // We temporarily repurpose "hit" to indicate line continuations
-  if (corwin && corwin->prev->hit) {
-    if (!pline || !*pline) error_exit("unfinished %c", corwin->prev->c);;
+  // Append this line to previous multiline command? (hit indicates type.)
+  // During parsing "hit" stores data about line continuations, but in
+  // process_line() it means the match range attached to this command
+  // is active, so processing the continuation must zero it again.
+  if (command && command->prev->hit) {
     // Remove half-finished entry from list so remalloc() doesn't confuse it
     TT.pattern = TT.pattern->prev;
-    corwin = dlist_pop(&TT.pattern);
-    c = corwin->c;
-    reg = (char *)corwin;
-    reg += corwin->arg1 + strlen(reg + corwin->arg1);
-
-    // Resume parsing for 'a' or 's' command
-    if (corwin->hit < 256) goto resume_s;
+    command = dlist_pop(&TT.pattern);
+    c = command->c;
+    reg = (char *)command;
+    reg += command->arg1 + strlen(reg + command->arg1);
+
+    // Resume parsing for 'a' or 's' command. (Only two that can do this.)
+    // TODO: using 256 to indicate 'a' means our s/// delimiter can't be
+    // a unicode character.
+    if (command->hit < 256) goto resume_s;
     else goto resume_a;
   }
 
-  // Loop through commands in line
+  // Loop through commands in this line.
 
-  corwin = 0;
+  command = 0;
   for (;;) {
-    if (corwin) dlist_add_nomalloc(&TT.pattern, (void *)corwin);
+    if (command) dlist_add_nomalloc(&TT.pattern, (void *)command);
 
+    // If there's no more data on this line, return.
     for (;;) {
       while (isspace(*line) || *line == ';') line++;
       if (*line == '#') while (*line && *line != '\n') line++;
@@ -790,28 +747,31 @@ static void jewel_of_judgement(char **pline, long len)
     }
     if (!*line) return;
 
+    // We start by writing data into toybuf. Later we'll allocate the
+    // ex
+
     errstart = line;
-    memset(toybuf, 0, sizeof(struct step));
-    corwin = (void *)toybuf;
-    reg = toybuf + sizeof(struct step);
+    memset(toybuf, 0, sizeof(struct sedcmd));
+    command = (void *)toybuf;
+    reg = toybuf + sizeof(struct sedcmd);
 
     // Parse address range (if any)
     for (i = 0; i < 2; i++) {
       if (*line == ',') line++;
       else if (i) break;
 
-      if (isdigit(*line)) corwin->lmatch[i] = strtol(line, &line, 0);
+      if (isdigit(*line)) command->lmatch[i] = strtol(line, &line, 0);
       else if (*line == '$') {
-        corwin->lmatch[i] = -1;
+        command->lmatch[i] = -1;
         line++;
       } else if (*line == '/' || *line == '\\') {
         char *s = line;
 
-        if (!(s = unescape_delimited_string(&line, 0))) goto brand;
-        if (!*s) corwin->rmatch[i] = 0;
+        if (!(s = unescape_delimited_string(&line, 0))) goto error;
+        if (!*s) command->rmatch[i] = 0;
         else {
           xregcomp((void *)reg, s, (toys.optflags & FLAG_r)*REG_EXTENDED);
-          corwin->rmatch[i] = reg-toybuf;
+          command->rmatch[i] = reg-toybuf;
           reg += sizeof(regex_t);
         }
         free(s);
@@ -822,58 +782,59 @@ static void jewel_of_judgement(char **pline, long len)
     if (!*line) break;
 
     while (*line == '!') {
-      corwin->not = 1;
+      command->not = 1;
       line++;
     }
     while (isspace(*line)) line++;
 
-    c = corwin->c = *(line++);
+    c = command->c = *(line++);
     if (strchr("}:", c) && i) break;
     if (strchr("aiqr=", c) && i>1) break;
 
     // Add step to pattern
-    corwin = xmemdup(toybuf, reg-toybuf);
-    reg = (reg-toybuf) + (char *)corwin;
+    command = xmemdup(toybuf, reg-toybuf);
+    reg = (reg-toybuf) + (char *)command;
 
     // Parse arguments by command type
     if (c == '{') TT.nextlen++;
     else if (c == '}') {
       if (!TT.nextlen--) break;
     } else if (c == 's') {
-      char *fiona, delim = 0;
+      char *end, delim = 0;
 
       // s/pattern/replacement/flags
 
-      // line continuations use arg1, so we fill out arg2 first (since the
-      // regex part can't be multiple lines) and swap them back later.
+      // line continuations use arg1 (back at the start of the function),
+      // so let's fill out arg2 first (since the regex part can't be multiple
+      // lines) and swap them back later.
 
       // get pattern (just record, we parse it later)
-      corwin->arg2 = reg - (char *)corwin;
+      command->arg2 = reg - (char *)command;
       if (!(TT.remember = unescape_delimited_string(&line, &delim)))
-        goto brand;
+        goto error;
 
       reg += sizeof(regex_t);
-      corwin->arg1 = reg-(char *)corwin;
-      corwin->hit = delim;
+      command->arg1 = reg-(char *)command;
+      command->hit = delim;
 resume_s:
-      // get replacement - don't replace escapes because \1 and \& need
+      // get replacement - don't replace escapes yet because \1 and \& need
       // processing later, after we replace \\ with \ we can't tell \\1 from \1
-      fiona = line;
-      while (*fiona != corwin->hit) {
-        if (!*fiona) goto brand;
-        if (*fiona++ == '\\') {
-          if (!*fiona || *fiona == '\n') {
-            fiona[-1] = '\n';
+      end = line;
+      while (*end != command->hit) {
+        if (!*end) goto error;
+        if (*end++ == '\\') {
+          if (!*end || *end == '\n') {
+            end[-1] = '\n';
             break;
           }
-          fiona++;
+          end++;
         }
       }
 
-      reg = extend_string((void *)&corwin, line, reg-(char *)corwin,fiona-line);
-      line = fiona;
+      reg = extend_string((void *)&command, line, reg-(char *)command,end-line);
+      line = end;
       // line continuation? (note: '\n' can't be a valid delim).
-      if (*line == corwin->hit) corwin->hit = 0;
+      if (*line == command->hit) command->hit = 0;
       else {
         if (!*line) continue;
         reg--;
@@ -882,9 +843,9 @@ resume_s:
       }
 
       // swap arg1/arg2 so they're back in order arguments occur.
-      i = corwin->arg1;
-      corwin->arg1 = corwin->arg2;
-      corwin->arg2 = i;
+      i = command->arg1;
+      command->arg1 = command->arg2;
+      command->arg2 = i;
 
       // get flags
       for (line++; *line; line++) {
@@ -892,18 +853,18 @@ resume_s:
 
         if (isspace(*line) && *line != '\n') continue;
 
-        if (0 <= (l = stridx("igp", *line))) corwin->sflags |= 1<<l;
-        else if (!(corwin->sflags>>3) && 0<(l = strtol(line, &line, 10))) {
-          corwin->sflags |= l << 3;
+        if (0 <= (l = stridx("igp", *line))) command->sflags |= 1<<l;
+        else if (!(command->sflags>>3) && 0<(l = strtol(line, &line, 10))) {
+          command->sflags |= l << 3;
           line--;
         } else break;
       }
 
       // We deferred actually parsing the regex until we had the s///i flag
       // allocating the space was done by extend_string() above
-      if (!*TT.remember) corwin->arg1 = 0;
-      else xregcomp((void *)(corwin->arg1 + (char *)corwin), TT.remember,
-        ((toys.optflags & FLAG_r)*REG_EXTENDED)|((corwin->sflags&1)*REG_ICASE));
+      if (!*TT.remember) command->arg1 = 0;
+      else xregcomp((void *)(command->arg1 + (char *)command), TT.remember,
+        ((toys.optflags & FLAG_r)*REG_EXTENDED)|((command->sflags&1)*REG_ICASE));
       free(TT.remember);
       TT.remember = 0;
       if (*line == 'w') {
@@ -921,16 +882,16 @@ resume_s:
 
 writenow:
       while (isspace(*line)) line++;
-      if (!*line) goto brand;
+      if (!*line) goto error;
       for (cc = line; *cc; cc++) if (*cc == '\\' && cc[1] == ';') break;
       delim = *cc;
       *cc = 0;
       fd = xcreate(line, O_WRONLY|O_CREAT|O_TRUNC, 0644);
       *cc = delim;
 
-      corwin->w = reg - (char *)corwin;
-      corwin = xrealloc(corwin, corwin->w+(cc-line)+6);
-      reg = corwin->w + (char *)corwin;
+      command->w = reg - (char *)command;
+      command = xrealloc(command, command->w+(cc-line)+6);
+      reg = command->w + (char *)command;
 
       memcpy(reg, &fd, 4);
       reg += 4;
@@ -945,38 +906,44 @@ writenow:
       char *s, delim = 0;
       int len;
 
-      if (!(s = unescape_delimited_string(&line, &delim))) goto brand;
-      corwin->arg1 = reg-(char *)corwin;
+      if (!(s = unescape_delimited_string(&line, &delim))) goto error;
+      command->arg1 = reg-(char *)command;
       len = strlen(s);
-      reg = extend_string((void *)&corwin, s, reg-(char *)corwin, len);
+      reg = extend_string((void *)&command, s, reg-(char *)command, len);
       free(s);
-      corwin->arg2 = reg-(char *)corwin;
-      if (!(s = unescape_delimited_string(&line, &delim))) goto brand;
-      if (len != strlen(s)) goto brand;
-      reg = extend_string((void *)&corwin, s, reg-(char*)corwin, len);
+      command->arg2 = reg-(char *)command;
+      if (!(s = unescape_delimited_string(&line, &delim))) goto error;
+      if (len != strlen(s)) goto error;
+      reg = extend_string((void *)&command, s, reg-(char*)command, len);
       free(s);
     } else if (strchr("abcirtTw:", c)) {
       int end;
 
+      // trim leading spaces
       while (isspace(*line) && *line != '\n') line++;
 
       // Resume logic differs from 's' case because we don't add a newline
       // unless it's after something, so we add it on return instead.
 resume_a:
-      corwin->hit = 0;
+      command->hit = 0;
 
-      // Trim whitespace from "b ;" and ": blah " but only first space in "w x "
-      if (!(end = strcspn(line, strchr("btT:", c) ? "; \t\r\n\v\f" : "\n"))) {
+      // btT: end with space or semicolon, aicrw continue to newline.
+      if (!(end = strcspn(line, strchr(":btT", c) ? "; \t\r\n\v\f" : "\n"))) {
+        // Argument's optional for btT
         if (strchr("btT", c)) continue;
-        else if (!corwin->arg1) break;
+        else if (!command->arg1) break;
       }
 
       // Extend allocation to include new string. We use offsets instead of
       // pointers so realloc() moving stuff doesn't break things. Ok to write
       // \n over NUL terminator because call to extend_string() adds it back.
-      if (!corwin->arg1) corwin->arg1 = reg - (char*)corwin;
-      else if (*(corwin->arg1+(char *)corwin)) *(reg++) = '\n';
-      reg = extend_string((void *)&corwin, line, reg - (char *)corwin, end);
+      if (!command->arg1) command->arg1 = reg - (char*)command;
+      else if (*(command->arg1+(char *)command)) *(reg++) = '\n';
+      else if (!pline) {
+        command->arg1 = 0;
+        continue;
+      }
+      reg = extend_string((void *)&command, line, reg - (char *)command, end);
 
       // Recopy data to remove escape sequences and handle line continuation.
       if (strchr("aci", c)) {
@@ -992,7 +959,7 @@ resume_a:
                 line++;
                 goto resume_a;
               }
-              corwin->hit = 256;
+              command->hit = 256;
               break;
             }
             if (!(reg[-1] = unescape(*line))) reg[-1] = *line;
@@ -1006,14 +973,13 @@ resume_a:
     } else if (!strchr("{dDgGhHlnNpPqx=", c)) break;
   }
 
-brand:
-  // Reminisce about chestnut trees.
+error:
   error_exit("bad pattern '%s'@%ld (%c)", errstart, line-errstart+1L, *line);
 }
 
 void sed_main(void)
 {
-  struct arg_list *dworkin;
+  struct arg_list *al;
   char **args = toys.optargs;
 
   // Lie to autoconf when it asks stupid questions, so configure regexes
@@ -1024,7 +990,9 @@ void sed_main(void)
     return;
   }
 
-  // Need a pattern. If no unicorns about, fight serpent and take its eye.
+  // Parse pattern into commands.
+
+  // If no -e or -f, first argument is the pattern.
   if (!TT.e && !TT.f) {
     if (!*toys.optargs) error_exit("no pattern");
     (TT.e = xzalloc(sizeof(struct arg_list)))->arg = *(args++);
@@ -1033,11 +1001,9 @@ void sed_main(void)
   // Option parsing infrastructure can't interlace "-e blah -f blah -e blah"
   // so handle all -e, then all -f. (At least the behavior's consistent.)
 
-  for (dworkin = TT.e; dworkin; dworkin = dworkin->next)
-    jewel_of_judgement(&dworkin->arg, strlen(dworkin->arg));
-  for (dworkin = TT.f; dworkin; dworkin = dworkin->next)
-    do_lines(xopen(dworkin->arg, O_RDONLY), dworkin->arg, jewel_of_judgement);
-  jewel_of_judgement(0, 0);
+  for (al = TT.e; al; al = al->next) parse_pattern(&al->arg, strlen(al->arg));
+  for (al = TT.f; al; al = al->next) do_lines(xopenro(al->arg), parse_pattern);
+  parse_pattern(0, 0);
   dlist_terminate(TT.pattern);
   if (TT.nextlen) error_exit("no }");  
 
@@ -1047,7 +1013,7 @@ void sed_main(void)
   // Inflict pattern upon input files
   loopfiles_rw(args, O_RDONLY, 0, 0, do_sed);
 
-  if (!(toys.optflags & FLAG_i)) walk_pattern(0, 0);
+  if (!(toys.optflags & FLAG_i)) process_line(0, 0);
 
   // todo: need to close fd when done for TOYBOX_FREE?
 }