]> git.ipfire.org Git - thirdparty/tar.git/commitdiff
Sync wordsplit and paxutils with the most recent versions
authorSergey Poznyakoff <gray@gnu.org>
Tue, 31 Jul 2018 08:53:00 +0000 (11:53 +0300)
committerSergey Poznyakoff <gray@gnu.org>
Tue, 31 Jul 2018 08:56:44 +0000 (11:56 +0300)
lib/wordsplit.c
lib/wordsplit.h
paxutils

index f2ecadaf7a919f713f5b5a0e82d2cb63d8567056..034ffd900e4671210c5948c21e451cdd27cd9f19 100644 (file)
@@ -1,5 +1,5 @@
 /* wordsplit - a word splitter
-   Copyright (C) 2009-2014, 2016-2017 Free Software Foundation, Inc.
+   Copyright (C) 2009-2018 Sergey Poznyakoff
 
    This program is free software; you can redistribute it and/or modify it
    under the terms of the GNU General Public License as published by the
    GNU General Public License for more details.
 
    You should have received a copy of the GNU General Public License along
-   with this program. If not, see <http://www.gnu.org/licenses/>.
-
-   Written by Sergey Poznyakoff
-*/
+   with this program. If not, see <http://www.gnu.org/licenses/>. */
 
 #ifdef HAVE_CONFIG_H
 # include <config.h>
@@ -28,6 +25,8 @@
 #include <string.h>
 #include <stdio.h>
 #include <stdarg.h>
+#include <pwd.h>
+#include <glob.h>
 
 #if ENABLE_NLS
 # include <gettext.h>
 #define ISALNUM(c) (ISALPHA(c) || ISDIGIT(c))
 #define ISPRINT(c) (' ' <= ((unsigned) (c)) && ((unsigned) (c)) <= 127)
 
+#define ISVARBEG(c) (ISALPHA(c) || c == '_')
+#define ISVARCHR(c) (ISALNUM(c) || c == '_')
+
+#define WSP_RETURN_DELIMS(wsp) \
+ ((wsp)->ws_flags & WRDSF_RETURN_DELIMS || ((wsp)->ws_options & WRDSO_MAXWORDS))
+
 #define ALLOC_INIT 128
 #define ALLOC_INCR 128
 
 static void
 _wsplt_alloc_die (struct wordsplit *wsp)
 {
-  wsp->ws_error (_("memory exhausted"));
+  wsp->ws_error ("%s", _("memory exhausted"));
   abort ();
 }
 
@@ -74,6 +79,15 @@ _wsplt_error (const char *fmt, ...)
 
 static void wordsplit_free_nodes (struct wordsplit *);
 
+static int
+_wsplt_seterr (struct wordsplit *wsp, int ec)
+{
+  wsp->ws_errno = ec;
+  if (wsp->ws_flags & WRDSF_SHOWERR)
+    wordsplit_perror (wsp);
+  return ec;
+}
+  
 static int
 _wsplt_nomem (struct wordsplit *wsp)
 {
@@ -89,6 +103,84 @@ _wsplt_nomem (struct wordsplit *wsp)
   return wsp->ws_errno;
 }
 
+static int wordsplit_run (const char *command, size_t length,
+                         struct wordsplit *wsp,
+                         int flags, int lvl);
+
+static int wordsplit_init (struct wordsplit *wsp, const char *input, size_t len,
+                          int flags);
+static int wordsplit_process_list (struct wordsplit *wsp, size_t start);
+static int wordsplit_finish (struct wordsplit *wsp);
+
+static int
+_wsplt_subsplit (struct wordsplit *wsp, struct wordsplit *wss,
+                char const *str, int len,
+                int flags, int finalize)
+{
+  int rc;
+  
+  wss->ws_delim = wsp->ws_delim;
+  wss->ws_debug = wsp->ws_debug;
+  wss->ws_error = wsp->ws_error;
+  wss->ws_alloc_die = wsp->ws_alloc_die;
+
+  if (!(flags & WRDSF_NOVAR))
+    {
+      wss->ws_env = wsp->ws_env;
+      wss->ws_getvar = wsp->ws_getvar;
+      flags |= wsp->ws_flags & (WRDSF_ENV | WRDSF_ENV_KV | WRDSF_GETVAR);
+    }
+  if (!(flags & WRDSF_NOCMD))
+    {
+      wss->ws_command = wsp->ws_command;
+    }
+
+  if ((flags & (WRDSF_NOVAR|WRDSF_NOCMD)) != (WRDSF_NOVAR|WRDSF_NOCMD))
+    {
+      wss->ws_closure = wsp->ws_closure;
+      flags |= wsp->ws_flags & WRDSF_CLOSURE;
+    }
+
+  wss->ws_options = wsp->ws_options;
+  
+  flags |= WRDSF_DELIM
+         | WRDSF_ALLOC_DIE
+         | WRDSF_ERROR
+         | WRDSF_DEBUG
+         | (wsp->ws_flags & (WRDSF_SHOWDBG | WRDSF_SHOWERR | WRDSF_OPTIONS));
+
+  rc = wordsplit_init (wss, str, len, flags);
+  if (rc)
+    return rc;
+  wss->ws_lvl = wsp->ws_lvl + 1;
+  rc = wordsplit_process_list (wss, 0);
+  if (rc)
+    {
+      wordsplit_free_nodes (wss);
+      return rc;
+    }
+  if (finalize)
+    {
+      rc = wordsplit_finish (wss);
+      wordsplit_free_nodes (wss);
+    }
+  return rc;
+}
+
+static void
+_wsplt_seterr_sub (struct wordsplit *wsp, struct wordsplit *wss)
+{
+  if (wsp->ws_errno == WRDSE_USERERR)
+    free (wsp->ws_usererr);
+  wsp->ws_errno = wss->ws_errno;
+  if (wss->ws_errno == WRDSE_USERERR)
+    {
+      wsp->ws_usererr = wss->ws_usererr;
+      wss->ws_errno = WRDSE_EOF;
+      wss->ws_usererr = NULL;
+    }
+}
+
 static void
 wordsplit_init0 (struct wordsplit *wsp)
 {
@@ -96,6 +188,7 @@ wordsplit_init0 (struct wordsplit *wsp)
     {
       if (!(wsp->ws_flags & WRDSF_APPEND))
        wordsplit_free_words (wsp);
+      wordsplit_clearerr (wsp);
     }
   else
     {
@@ -105,9 +198,10 @@ wordsplit_init0 (struct wordsplit *wsp)
     }
 
   wsp->ws_errno = 0;
-  wsp->ws_head = wsp->ws_tail = NULL;
 }
 
+char wordsplit_c_escape_tab[] = "\\\\\"\"a\ab\bf\fn\nr\rt\tv\v";
+  
 static int
 wordsplit_init (struct wordsplit *wsp, const char *input, size_t len,
                int flags)
@@ -119,23 +213,21 @@ wordsplit_init (struct wordsplit *wsp, const char *input, size_t len,
   if (!(wsp->ws_flags & WRDSF_ERROR))
     wsp->ws_error = _wsplt_error;
 
-  if (!(wsp->ws_flags & WRDSF_NOVAR)
-      && !(wsp->ws_flags & (WRDSF_ENV | WRDSF_GETVAR)))
+  if (!(wsp->ws_flags & WRDSF_NOVAR))
     {
-      errno = EINVAL;
-      wsp->ws_errno = WRDSE_USAGE;
-      if (wsp->ws_flags & WRDSF_SHOWERR)
-       wordsplit_perror (wsp);
-      return wsp->ws_errno;
+      /* These will be initialized on first variable assignment */
+      wsp->ws_envidx = wsp->ws_envsiz = 0;
+      wsp->ws_envbuf = NULL;
     }
 
   if (!(wsp->ws_flags & WRDSF_NOCMD))
     {
-      errno = EINVAL;
-      wsp->ws_errno = WRDSE_NOSUPP;
-      if (wsp->ws_flags & WRDSF_SHOWERR)
-       wordsplit_perror (wsp);
-      return wsp->ws_errno;
+      if (!wsp->ws_command)
+       {
+         _wsplt_seterr (wsp, WRDSE_USAGE);
+         errno = EINVAL;
+         return wsp->ws_errno;
+       }
     }
 
   if (wsp->ws_flags & WRDSF_SHOWDBG)
@@ -166,10 +258,42 @@ wordsplit_init (struct wordsplit *wsp, const char *input, size_t len,
   if (!(wsp->ws_flags & WRDSF_CLOSURE))
     wsp->ws_closure = NULL;
 
+  if (!(wsp->ws_flags & WRDSF_OPTIONS))
+    wsp->ws_options = 0;
+
+  if (wsp->ws_flags & WRDSF_ESCAPE)
+    {
+      if (!wsp->ws_escape[WRDSX_WORD])
+       wsp->ws_escape[WRDSX_WORD] = "";
+      if (!wsp->ws_escape[WRDSX_QUOTE])
+       wsp->ws_escape[WRDSX_QUOTE] = "";
+    }
+  else
+    {
+      if (wsp->ws_flags & WRDSF_CESCAPES)
+       {
+         wsp->ws_escape[WRDSX_WORD] = wordsplit_c_escape_tab;
+         wsp->ws_escape[WRDSX_QUOTE] = wordsplit_c_escape_tab;
+         wsp->ws_options |= WRDSO_OESC_QUOTE | WRDSO_OESC_WORD       
+                            | WRDSO_XESC_QUOTE | WRDSO_XESC_WORD;
+       }
+      else
+       {
+         wsp->ws_escape[WRDSX_WORD] = "";
+         wsp->ws_escape[WRDSX_QUOTE] = "\\\\\"\"";
+         wsp->ws_options |= WRDSO_BSKEEP_QUOTE;
+       }
+    }
+  
   wsp->ws_endp = 0;
+  wsp->ws_wordi = 0;
 
+  if (wsp->ws_flags & WRDSF_REUSE)
+    wordsplit_free_nodes (wsp);
+  wsp->ws_head = wsp->ws_tail = NULL;
+  
   wordsplit_init0 (wsp);
-
+  
   return 0;
 }
 
@@ -212,6 +336,7 @@ alloc_space (struct wordsplit *wsp, size_t count)
 #define _WSNF_NOEXPAND 0x08    /* text is not subject to expansion */
 #define _WSNF_JOIN     0x10    /* node must be joined with the next node */
 #define _WSNF_SEXP     0x20    /* is a sed expression */
+#define _WSNF_DELIM    0x40     /* node is a delimiter */
 
 #define _WSNF_EMPTYOK  0x0100  /* special flag indicating that
                                   wordsplit_add_segm must add the
@@ -221,7 +346,7 @@ struct wordsplit_node
 {
   struct wordsplit_node *prev; /* Previous element */
   struct wordsplit_node *next; /* Next element */
-  unsigned flags;              /* Node flags */
+  int flags;                   /* Node flags */
   union
   {
     struct
@@ -236,7 +361,7 @@ struct wordsplit_node
 static const char *
 wsnode_flagstr (int flags)
 {
-  static char retbuf[6];
+  static char retbuf[7];
   char *p = retbuf;
 
   if (flags & _WSNF_WORD)
@@ -261,6 +386,10 @@ wsnode_flagstr (int flags)
     *p++ = 's';
   else
     *p++ = '-';
+  if (flags & _WSNF_DELIM)
+    *p++ = 'd';
+  else
+    *p++ = '-';
   *p = 0;
   return retbuf;
 }
@@ -341,6 +470,14 @@ wsnode_remove (struct wordsplit *wsp, struct wordsplit_node *node)
   node->next = node->prev = NULL;
 }
 
+static struct wordsplit_node *
+wsnode_tail (struct wordsplit_node *p)
+{
+  while (p && p->next)
+    p = p->next;
+  return p;
+}
+
 static void
 wsnode_insert (struct wordsplit *wsp, struct wordsplit_node *node,
               struct wordsplit_node *anchor, int before)
@@ -356,22 +493,24 @@ wsnode_insert (struct wordsplit *wsp, struct wordsplit_node *node,
        wsnode_insert (wsp, node, anchor->prev, 0);
       else
        {
+         struct wordsplit_node *tail = wsnode_tail (node);
          node->prev = NULL;
-         node->next = anchor;
-         anchor->prev = node;
+         tail->next = anchor;
+         anchor->prev = tail;
          wsp->ws_head = node;
        }
     }
   else
     {
       struct wordsplit_node *p;
+      struct wordsplit_node *tail = wsnode_tail (node);
 
       p = anchor->next;
       if (p)
-       p->prev = node;
+       p->prev = tail;
       else
-       wsp->ws_tail = node;
-      node->next = p;
+       wsp->ws_tail = tail;
+      tail->next = p;
       node->prev = anchor;
       anchor->next = node;
     }
@@ -418,10 +557,12 @@ wordsplit_dump_nodes (struct wordsplit *wsp)
   for (p = wsp->ws_head, n = 0; p; p = p->next, n++)
     {
       if (p->flags & _WSNF_WORD)
-       wsp->ws_debug ("%4d: %p: %#04x (%s):%s;",
+       wsp->ws_debug ("(%02d) %4d: %p: %#04x (%s):%s;",
+                      wsp->ws_lvl,
                       n, p, p->flags, wsnode_flagstr (p->flags), p->v.word);
       else
-       wsp->ws_debug ("%4d: %p: %#04x (%s):%.*s;",
+       wsp->ws_debug ("(%02d) %4d: %p: %#04x (%s):%.*s;",
+                      wsp->ws_lvl,
                       n, p, p->flags, wsnode_flagstr (p->flags),
                       (int) (p->v.segm.end - p->v.segm.beg),
                       wsp->ws_input + p->v.segm.beg);
@@ -436,11 +577,15 @@ coalesce_segment (struct wordsplit *wsp, struct wordsplit_node *node)
   char *buf, *cur;
   int stop;
 
+  if (!(node->flags & _WSNF_JOIN))
+    return 0;
+  
   for (p = node; p && (p->flags & _WSNF_JOIN); p = p->next)
     {
       len += wsnode_len (p);
     }
-  len += wsnode_len (p);
+  if (p)
+    len += wsnode_len (p);
   end = p;
 
   buf = malloc (len + 1);
@@ -459,6 +604,7 @@ coalesce_segment (struct wordsplit *wsp, struct wordsplit_node *node)
       cur += slen;
       if (p != node)
        {
+         node->flags |= p->flags & _WSNF_QUOTE;
          wsnode_remove (wsp, p);
          stop = p == end;
          wsnode_free (p);
@@ -478,13 +624,14 @@ coalesce_segment (struct wordsplit *wsp, struct wordsplit_node *node)
   return 0;
 }
 
+static void wordsplit_string_unquote_copy (struct wordsplit *ws, int inquote,
+                                          char *dst, const char *src,
+                                          size_t n);
+
 static int
 wsnode_quoteremoval (struct wordsplit *wsp)
 {
   struct wordsplit_node *p;
-  void (*uqfn) (char *, const char *, size_t) =
-    (wsp->ws_flags & WRDSF_CESCAPES) ?
-    wordsplit_c_unquote_copy : wordsplit_sh_unquote_copy;
 
   for (p = wsp->ws_head; p; p = p->next)
     {
@@ -493,9 +640,7 @@ wsnode_quoteremoval (struct wordsplit *wsp)
       int unquote;
 
       if (wsp->ws_flags & WRDSF_QUOTE)
-       {
-         unquote = !(p->flags & _WSNF_NOEXPAND);
-       }
+       unquote = !(p->flags & _WSNF_NOEXPAND);
       else
        unquote = 0;
 
@@ -512,11 +657,8 @@ wsnode_quoteremoval (struct wordsplit *wsp)
              p->flags |= _WSNF_WORD;
            }
 
-         if (wsp->ws_flags & WRDSF_ESCAPE)
-           wordsplit_general_unquote_copy (p->v.word, str, slen,
-                                           wsp->ws_escape);
-         else
-           uqfn (p->v.word, str, slen);
+         wordsplit_string_unquote_copy (wsp, p->flags & _WSNF_QUOTE,
+                                        p->v.word, str, slen);
        }
     }
   return 0;
@@ -536,24 +678,161 @@ wsnode_coalesce (struct wordsplit *wsp)
   return 0;
 }
 
+static int
+wsnode_tail_coalesce (struct wordsplit *wsp, struct wordsplit_node *p)
+{
+  if (p->next)
+    {
+      struct wordsplit_node *np = p;
+      while (np && np->next)
+       {
+         np->flags |= _WSNF_JOIN;
+         np = np->next;
+       }
+      if (coalesce_segment (wsp, p))
+       return 1;
+    }
+  return 0;
+}
+
+static size_t skip_delim (struct wordsplit *wsp);
+
 static int
 wordsplit_finish (struct wordsplit *wsp)
 {
   struct wordsplit_node *p;
   size_t n;
+  int delim;
 
-  n = 0;
+  /* Postprocess delimiters. It would be rather simple, if it weren't for
+     the incremental operation.
 
-  for (p = wsp->ws_head; p; p = p->next)
-    n++;
+     Nodes of type _WSNF_DELIM get inserted to the node list if either
+     WRDSF_RETURN_DELIMS flag or WRDSO_MAXWORDS option is set.
+     
+     The following cases should be distinguished:
+
+     1. If both WRDSF_SQUEEZE_DELIMS and WRDSF_RETURN_DELIMS are set, compress
+        any runs of similar delimiter nodes to a single node. The nodes are
+       'similar' if they point to the same delimiter character.
+
+       If WRDSO_MAXWORDS option is set, stop compressing when
+       ws_wordi + 1 == ws_maxwords, and coalesce the rest of nodes into
+       a single last node.
+
+     2. If WRDSO_MAXWORDS option is set, but WRDSF_RETURN_DELIMS is not,
+        remove any delimiter nodes. Stop operation when
+       ws_wordi + 1 == ws_maxwords, and coalesce the rest of nodes into
+       a single last node.
+
+     3. If incremental operation is in progress, restart the loop any time
+        a delimiter node is about to be returned, unless WRDSF_RETURN_DELIMS
+       is set.
+  */
+ again:
+  delim = 0;         /* Delimiter being processed (if any) */
+  n = 0;             /* Number of words processed so far */
+  p = wsp->ws_head;  /* Current node */
+
+  while (p)
+    {
+      struct wordsplit_node *next = p->next;
+      if (p->flags & _WSNF_DELIM)
+       {
+         if (wsp->ws_flags & WRDSF_RETURN_DELIMS)
+           {
+             if (wsp->ws_flags & WRDSF_SQUEEZE_DELIMS)
+               {
+                 char const *s = wsnode_ptr (wsp, p);
+                 if (delim)
+                   {
+                     if (delim == *s)
+                       {
+                         wsnode_remove (wsp, p);
+                         p = next;
+                         continue;
+                       }
+                     else
+                       {
+                         delim = 0;
+                         n++; /* Count this node; it will be returned */
+                       }
+                   }
+                 else
+                   {
+                     delim = *s;
+                     p = next;
+                     continue;
+                   }
+               }
+           }
+         else if (wsp->ws_options & WRDSO_MAXWORDS)
+           {
+             wsnode_remove (wsp, p);
+             p = next;
+             continue;
+           }
+       }
+      else 
+       {
+         if (delim)
+           {
+             /* Last node was a delimiter or a compressed run of delimiters;
+                Count it, and clear the delimiter marker */
+             n++;
+             delim = 0;
+           }
+         if (wsp->ws_options & WRDSO_MAXWORDS)
+           {
+             if (wsp->ws_wordi + n + 1 == wsp->ws_maxwords)
+               break;
+           }
+       }
+      n++;
+      if (wsp->ws_flags & WRDSF_INCREMENTAL)
+       p = NULL; /* Break the loop */
+      else
+       p = next;
+    }
+
+  if (p)
+    {
+      /* We're here if WRDSO_MAXWORDS is in effect and wsp->ws_maxwords
+        words have already been collected. Reconstruct a single final
+        node from the remaining nodes. */
+      if (wsnode_tail_coalesce (wsp, p))
+       return wsp->ws_errno;
+      n++;
+    }
+
+  if (n == 0 && (wsp->ws_flags & WRDSF_INCREMENTAL))
+    {
+      /* The loop above have eliminated all nodes. Restart the
+        processing, if there's any input left. */
+      if (wsp->ws_endp < wsp->ws_len)
+       {
+         int rc;
+         if (wsp->ws_flags & WRDSF_SHOWDBG)
+           wsp->ws_debug (_("Restarting"));
+         rc = wordsplit_process_list (wsp, skip_delim (wsp));
+         if (rc)
+           return rc;
+       }
+      else
+       {
+         wsp->ws_error = WRDSE_EOF;
+         return WRDSE_EOF;
+       }
+      goto again;
+    }
 
   if (alloc_space (wsp, n + 1))
-    return 1;
+    return wsp->ws_errno;
 
-  for (p = wsp->ws_head; p; p = p->next)
+  while (wsp->ws_head)
     {
-      const char *str = wsnode_ptr (wsp, p);
-      size_t slen = wsnode_len (p);
+      const char *str = wsnode_ptr (wsp, wsp->ws_head);
+      size_t slen = wsnode_len (wsp->ws_head);
       char *newstr = malloc (slen + 1);
 
       /* Assign newstr first, even if it is NULL.  This way
@@ -565,14 +844,47 @@ wordsplit_finish (struct wordsplit *wsp)
       memcpy (newstr, str, slen);
       newstr[slen] = 0;
 
+      wsnode_remove (wsp, wsp->ws_head);
+
       wsp->ws_wordc++;
+      wsp->ws_wordi++;
 
+      if (wsp->ws_flags & WRDSF_INCREMENTAL)
+       break;
     }
   wsp->ws_wordv[wsp->ws_offs + wsp->ws_wordc] = NULL;
   return 0;
 }
 \f
+int
+wordsplit_append (wordsplit_t *wsp, int argc, char **argv)
+{
+  int rc;
+  size_t i;
 
+  rc = alloc_space (wsp, wsp->ws_wordc + argc + 1);
+  if (rc)
+    return rc;
+  for (i = 0; i < argc; i++)
+    {
+      char *newstr = strdup (argv[i]);
+      if (!newstr)
+       {
+         while (i > 0)
+           {
+             free (wsp->ws_wordv[wsp->ws_offs + wsp->ws_wordc + i - 1]);
+             wsp->ws_wordv[wsp->ws_offs + wsp->ws_wordc + i - 1] = NULL;
+             i--;
+           }
+         return _wsplt_nomem (wsp);
+       }
+      wsp->ws_wordv[wsp->ws_offs + wsp->ws_wordc + i] = newstr;
+    }
+  wsp->ws_wordc += i;
+  wsp->ws_wordv[wsp->ws_offs + wsp->ws_wordc] = NULL;
+  return 0;
+}
+\f
 /* Variable expansion */
 static int
 node_split_prefix (struct wordsplit *wsp,
@@ -609,10 +921,10 @@ node_split_prefix (struct wordsplit *wsp,
 }
 
 static int
-find_closing_cbrace (const char *str, size_t i, size_t len, size_t * poff)
+find_closing_paren (const char *str, size_t i, size_t len, size_t *poff,
+                   char const *paren)
 {
-  enum
-  { st_init, st_squote, st_dquote } state = st_init;
+  enum { st_init, st_squote, st_dquote } state = st_init;
   size_t level = 1;
 
   for (; i < len; i++)
@@ -622,18 +934,23 @@ find_closing_cbrace (const char *str, size_t i, size_t len, size_t * poff)
        case st_init:
          switch (str[i])
            {
-           case '{':
-             level++;
-             break;
-
-           case '}':
-             if (--level == 0)
+           default:
+             if (str[i] == paren[0])
+               {
+                 level++;
+                 break;
+               }
+             else if (str[i] == paren[1])
                {
-                 *poff = i;
-                 return 0;
+                 if (--level == 0)
+                   {
+                     *poff = i;
+                     return 0;
+                   }
+                 break;
                }
              break;
-
+             
            case '"':
              state = st_dquote;
              break;
@@ -660,13 +977,14 @@ find_closing_cbrace (const char *str, size_t i, size_t len, size_t * poff)
   return 1;
 }
 
-static const char *
-wordsplit_find_env (struct wordsplit *wsp, const char *name, size_t len)
+static int
+wordsplit_find_env (struct wordsplit *wsp, const char *name, size_t len,
+                   char const **ret)
 {
   size_t i;
 
   if (!(wsp->ws_flags & WRDSF_ENV))
-    return NULL;
+    return WRDSE_UNDEF;
 
   if (wsp->ws_flags & WRDSF_ENV_KV)
     {
@@ -675,14 +993,17 @@ wordsplit_find_env (struct wordsplit *wsp, const char *name, size_t len)
        {
          size_t elen = strlen (wsp->ws_env[i]);
          if (elen == len && memcmp (wsp->ws_env[i], name, elen) == 0)
-           return wsp->ws_env[i + 1];
+           {
+             *ret = wsp->ws_env[i + 1];
+             return WRDSE_OK;
+           }
          /* Skip the value.  Break the loop if it is NULL. */
          i++;
          if (wsp->ws_env[i] == NULL)
            break;
        }
     }
-  else
+  else if (wsp->ws_env)
     {
       /* Usual (A=B) environment. */
       for (i = 0; wsp->ws_env[i]; i++)
@@ -694,10 +1015,117 @@ wordsplit_find_env (struct wordsplit *wsp, const char *name, size_t len)
            if (name[j] != var[j])
              break;
          if (j == len && var[j] == '=')
-           return var + j + 1;
+           {
+             *ret = var + j + 1;
+             return WRDSE_OK;
+           }
+       }
+    }
+  return WRDSE_UNDEF;
+}
+
+static int
+wsplt_assign_var (struct wordsplit *wsp, const char *name, size_t namelen,
+                 char *value)
+{
+  int n = (wsp->ws_flags & WRDSF_ENV_KV) ? 2 : 1;
+  char *v;
+  
+  if (wsp->ws_envidx + n >= wsp->ws_envsiz)
+    {
+      size_t sz;
+      char **newenv;
+
+      if (!wsp->ws_envbuf)
+       {
+         if (wsp->ws_flags & WRDSF_ENV)
+           {
+             size_t i = 0, j;
+
+             if (wsp->ws_env)
+               {
+                 for (; wsp->ws_env[i]; i++)
+                   ;
+               }
+             
+             sz = i + n + 1;
+
+             newenv = calloc (sz, sizeof(newenv[0]));
+             if (!newenv)
+               return _wsplt_nomem (wsp);
+
+             for (j = 0; j < i; j++)
+               {
+                 newenv[j] = strdup (wsp->ws_env[j]);
+                 if (!newenv[j])
+                   {
+                     for (; j > 1; j--)
+                       free (newenv[j-1]);
+                     free (newenv[j-1]);
+                     return _wsplt_nomem (wsp);
+                   }
+               }
+             newenv[j] = NULL;
+             
+             wsp->ws_envbuf = newenv;
+             wsp->ws_envidx = i;
+             wsp->ws_envsiz = sz;
+             wsp->ws_env = (const char**) wsp->ws_envbuf;
+           }
+         else
+           {
+             newenv = calloc (WORDSPLIT_ENV_INIT, sizeof(newenv[0]));
+             if (!newenv)
+               return _wsplt_nomem (wsp);
+             wsp->ws_envbuf = newenv;
+             wsp->ws_envidx = 0;
+             wsp->ws_envsiz = WORDSPLIT_ENV_INIT;
+             wsp->ws_env = (const char**) wsp->ws_envbuf;
+             wsp->ws_flags |= WRDSF_ENV;
+           }
+       }
+      else
+       {
+         wsp->ws_envsiz *= 2;
+         newenv = realloc (wsp->ws_envbuf,
+                           wsp->ws_envsiz * sizeof (wsp->ws_envbuf[0]));
+         if (!newenv)
+           return _wsplt_nomem (wsp);
+         wsp->ws_envbuf = newenv;
+         wsp->ws_env = (const char**) wsp->ws_envbuf;
+       }
+    }
+  
+  if (wsp->ws_flags & WRDSF_ENV_KV)
+    {
+      /* A key-value pair environment */
+      char *p = malloc (namelen + 1);
+      if (!p)
+       return _wsplt_nomem (wsp);
+      memcpy (p, name, namelen);
+      p[namelen] = 0;
+
+      v = strdup (value);
+      if (!v)
+       {
+         free (p);
+         return _wsplt_nomem (wsp);
        }
+      wsp->ws_env[wsp->ws_envidx++] = p;
+      wsp->ws_env[wsp->ws_envidx++] = v;
     }
-  return NULL;
+  else
+    {
+      v = malloc (namelen + strlen(value) + 2);
+      if (!v)
+       return _wsplt_nomem (wsp);
+      memcpy (v, name, namelen);
+      v[namelen++] = '=';
+      strcpy(v + namelen, value);
+      wsp->ws_env[wsp->ws_envidx++] = v;
+    }
+  wsp->ws_env[wsp->ws_envidx++] = NULL;
+  return WRDSE_OK;
 }
 
 static int
@@ -706,15 +1134,17 @@ expvar (struct wordsplit *wsp, const char *str, size_t len,
 {
   size_t i = 0;
   const char *defstr = NULL;
-  const char *value;
+  char *value;
   const char *vptr;
   struct wordsplit_node *newnode;
   const char *start = str - 1;
-
-  if (ISALPHA (str[0]) || str[0] == '_')
+  int rc;
+  struct wordsplit ws;
+  
+  if (ISVARBEG (str[0]))
     {
       for (i = 1; i < len; i++)
-       if (!(ISALNUM (str[i]) || str[i] == '_'))
+       if (!ISVARCHR (str[i]))
          break;
       *pend = str + i - 1;
     }
@@ -723,30 +1153,36 @@ expvar (struct wordsplit *wsp, const char *str, size_t len,
       str++;
       len--;
       for (i = 1; i < len; i++)
-       if (str[i] == '}' || str[i] == ':')
-         break;
-      if (str[i] == ':')
        {
-         size_t j;
-
-         defstr = str + i + 1;
-         if (find_closing_cbrace (str, i + 1, len, &j))
+         if (str[i] == ':')
            {
-             wsp->ws_errno = WRDSE_CBRACE;
-             return 1;
+             size_t j;
+             
+             defstr = str + i + 1;
+             if (find_closing_paren (str, i + 1, len, &j, "{}"))
+               return _wsplt_seterr (wsp, WRDSE_CBRACE);
+             *pend = str + j;
+             break;
+           }
+         else if (str[i] == '}')
+           {
+             defstr = NULL;
+             *pend = str + i;
+             break;
+           }
+         else if (strchr ("-+?=", str[i]))
+           {
+             size_t j;
+             
+             defstr = str + i;
+             if (find_closing_paren (str, i, len, &j, "{}"))
+               return _wsplt_seterr (wsp, WRDSE_CBRACE);
+             *pend = str + j;
+             break;
            }
-         *pend = str + j;
-       }
-      else if (str[i] == '}')
-       {
-         defstr = NULL;
-         *pend = str + i;
-       }
-      else
-       {
-         wsp->ws_errno = WRDSE_CBRACE;
-         return 1;
        }
+      if (i == len)
+       return _wsplt_seterr (wsp, WRDSE_CBRACE);
     }
   else
     {
@@ -770,53 +1206,161 @@ expvar (struct wordsplit *wsp, const char *str, size_t len,
      i   - its length
      defstr - default replacement str */
 
-  vptr = wordsplit_find_env (wsp, str, i);
-  if (vptr)
-    {
-      value = strdup (vptr);
-      if (!value)
-       return _wsplt_nomem (wsp);
-    }
-  else if (wsp->ws_flags & WRDSF_GETVAR)
-    value = wsp->ws_getvar (str, i, wsp->ws_closure);
-  else if (wsp->ws_flags & WRDSF_UNDEF)
+  if (defstr && strchr("-+?=", defstr[0]) == 0)
     {
-      wsp->ws_errno = WRDSE_UNDEF;
-      if (wsp->ws_flags & WRDSF_SHOWERR)
-       wordsplit_perror (wsp);
-      return 1;
+      rc = WRDSE_UNDEF;
+      defstr = NULL;
     }
   else
     {
-      if (wsp->ws_flags & WRDSF_WARNUNDEF)
-       wsp->ws_error (_("warning: undefined variable `%.*s'"), (int) i, str);
-      if (wsp->ws_flags & WRDSF_KEEPUNDEF)
-       value = NULL;
+      rc = wordsplit_find_env (wsp, str, i, &vptr);
+      if (rc == WRDSE_OK)
+       {
+         if (vptr)
+           {
+             value = strdup (vptr);
+             if (!value)
+               rc = WRDSE_NOSPACE;
+           }
+         else
+           rc = WRDSE_UNDEF;
+       }
+      else if (wsp->ws_flags & WRDSF_GETVAR)
+       rc = wsp->ws_getvar (&value, str, i, wsp->ws_closure);
       else
-       value = "";
-    }
-
-  /* FIXME: handle defstr */
-  (void) defstr;
+       rc = WRDSE_UNDEF;
 
-  if (value)
+      if (rc == WRDSE_OK
+         && (!value || value[0] == 0)
+         && defstr && defstr[-1] == ':')
+       {
+         free (value);
+         rc = WRDSE_UNDEF;
+       }
+    }
+  
+  switch (rc)
     {
-      if (flg & _WSNF_QUOTE)
+    case WRDSE_OK:
+      if (defstr && *defstr == '+')
        {
-         if (wsnode_new (wsp, &newnode))
-           return 1;
-         wsnode_insert (wsp, newnode, *ptail, 0);
-         *ptail = newnode;
-         newnode->flags = _WSNF_WORD | _WSNF_NOEXPAND | flg;
-         newnode->v.word = strdup (value);
-         if (!newnode->v.word)
-           return _wsplt_nomem (wsp);
+         size_t size = *pend - ++defstr;
+
+         rc = _wsplt_subsplit (wsp, &ws, defstr, size,
+                               WRDSF_NOSPLIT | WRDSF_WS | WRDSF_QUOTE |
+                               (wsp->ws_flags &
+                                (WRDSF_NOVAR | WRDSF_NOCMD)), 1);
+         if (rc)
+           return rc;
+         free (value);
+         value = ws.ws_wordv[0];
+         ws.ws_wordv[0] = NULL;
+         wordsplit_free (&ws);
        }
-      else if (*value == 0)
+      break;
+      
+    case WRDSE_UNDEF:
+      if (defstr)
        {
-         /* Empty string is a special case */
-         if (wsnode_new (wsp, &newnode))
-           return 1;
+         size_t size;
+         if (*defstr == '-' || *defstr == '=')
+           {
+             size = *pend - ++defstr;
+
+             rc = _wsplt_subsplit (wsp, &ws, defstr, size,
+                                   WRDSF_NOSPLIT | WRDSF_WS | WRDSF_QUOTE |
+                                   (wsp->ws_flags &
+                                    (WRDSF_NOVAR | WRDSF_NOCMD)),
+                                   1);
+             if (rc)
+               return rc;
+
+             value = ws.ws_wordv[0];
+             ws.ws_wordv[0] = NULL;
+             wordsplit_free (&ws);
+             
+             if (defstr[-1] == '=')
+               wsplt_assign_var (wsp, str, i, value);
+           }
+         else 
+           {
+             if (*defstr == '?')
+               {
+                 size = *pend - ++defstr;
+                 if (size == 0)
+                   wsp->ws_error (_("%.*s: variable null or not set"),
+                                  (int) i, str);
+                 else
+                   {
+                     rc = _wsplt_subsplit (wsp, &ws, defstr, size,
+                                           WRDSF_NOSPLIT | WRDSF_WS |
+                                           WRDSF_QUOTE |
+                                           (wsp->ws_flags &
+                                            (WRDSF_NOVAR | WRDSF_NOCMD)),
+                                           1);
+                     if (rc == 0)
+                       wsp->ws_error ("%.*s: %s",
+                                      (int) i, str, ws.ws_wordv[0]);
+                     else
+                       wsp->ws_error ("%.*s: %.*s",
+                                      (int) i, str, (int) size, defstr);
+                     wordsplit_free (&ws);
+                   }
+               }
+             value = NULL;
+           }
+       }
+      else if (wsp->ws_flags & WRDSF_UNDEF)
+       {
+         _wsplt_seterr (wsp, WRDSE_UNDEF);
+         return 1;
+       }
+      else
+       {
+         if (wsp->ws_flags & WRDSF_WARNUNDEF)
+           wsp->ws_error (_("warning: undefined variable `%.*s'"),
+                          (int) i, str);
+         if (wsp->ws_flags & WRDSF_KEEPUNDEF)
+           value = NULL;
+         else
+           {
+             value = strdup ("");
+             if (!value)
+               return _wsplt_nomem (wsp);
+           }
+       }
+      break;
+      
+    case WRDSE_NOSPACE:
+      return _wsplt_nomem (wsp);
+
+    case WRDSE_USERERR:
+      if (wsp->ws_errno == WRDSE_USERERR)
+       free (wsp->ws_usererr);
+      wsp->ws_usererr = value;
+      /* fall through */
+    default:
+      _wsplt_seterr (wsp, rc);
+      return 1;
+    }
+
+  if (value)
+    {
+      if (flg & _WSNF_QUOTE)
+       {
+         if (wsnode_new (wsp, &newnode))
+           return 1;
+         wsnode_insert (wsp, newnode, *ptail, 0);
+         *ptail = newnode;
+         newnode->flags = _WSNF_WORD | _WSNF_NOEXPAND | flg;
+         newnode->v.word = value;
+       }
+      else if (*value == 0)
+       {
+         free (value);
+         /* Empty string is a special case */
+         if (wsnode_new (wsp, &newnode))
+           return 1;
          wsnode_insert (wsp, newnode, *ptail, 0);
          *ptail = newnode;
          newnode->flags = _WSNF_NULL;
@@ -824,28 +1368,23 @@ expvar (struct wordsplit *wsp, const char *str, size_t len,
       else
        {
          struct wordsplit ws;
-         int i;
-
-         ws.ws_delim = wsp->ws_delim;
-         if (wordsplit (value, &ws,
-                        WRDSF_NOVAR | WRDSF_NOCMD | WRDSF_DELIM | WRDSF_WS))
+         int rc;
+         
+         rc = _wsplt_subsplit (wsp, &ws, value, strlen (value),
+                               WRDSF_NOVAR | WRDSF_NOCMD |
+                               WRDSF_QUOTE
+                               | (WSP_RETURN_DELIMS (wsp) ? WRDSF_RETURN_DELIMS : 0) ,
+                               0);
+         free (value);
+         if (rc)
            {
+             _wsplt_seterr_sub (wsp, &ws);
              wordsplit_free (&ws);
              return 1;
            }
-         for (i = 0; i < ws.ws_wordc; i++)
-           {
-             if (wsnode_new (wsp, &newnode))
-               return 1;
-             wsnode_insert (wsp, newnode, *ptail, 0);
-             *ptail = newnode;
-             newnode->flags = _WSNF_WORD |
-               _WSNF_NOEXPAND |
-               (i + 1 < ws.ws_wordc ? (flg & ~_WSNF_JOIN) : flg);
-             newnode->v.word = strdup (ws.ws_wordv[i]);
-             if (!newnode->v.word)
-               return _wsplt_nomem (wsp);
-           }
+         wsnode_insert (wsp, ws.ws_head, *ptail, 0);
+         *ptail = ws.ws_tail;
+         ws.ws_head = ws.ws_tail = NULL;
          wordsplit_free (&ws);
        }
     }
@@ -876,7 +1415,19 @@ expvar (struct wordsplit *wsp, const char *str, size_t len,
 }
 
 static int
-node_expand_vars (struct wordsplit *wsp, struct wordsplit_node *node)
+begin_var_p (int c)
+{
+  return c == '{' || ISVARBEG (c);
+}
+
+static int
+node_expand (struct wordsplit *wsp, struct wordsplit_node *node,
+            int (*beg_p) (int),
+            int (*ws_exp_fn) (struct wordsplit *wsp,
+                              const char *str, size_t len,
+                              struct wordsplit_node **ptail,
+                              const char **pend,
+                              int flg))
 {
   const char *str = wsnode_ptr (wsp, node);
   size_t slen = wsnode_len (node);
@@ -892,7 +1443,7 @@ node_expand_vars (struct wordsplit *wsp, struct wordsplit_node *node)
          p++;
          continue;
        }
-      if (*p == '$')
+      if (*p == '$' && beg_p (p[1]))
        {
          size_t n = p - str;
 
@@ -901,8 +1452,8 @@ node_expand_vars (struct wordsplit *wsp, struct wordsplit_node *node)
          if (node_split_prefix (wsp, &tail, node, off, n, _WSNF_JOIN))
            return 1;
          p++;
-         if (expvar (wsp, p, slen - n, &tail, &p,
-                     node->flags & (_WSNF_JOIN | _WSNF_QUOTE)))
+         if (ws_exp_fn (wsp, p, slen - n, &tail, &p,
+                        node->flags & (_WSNF_JOIN | _WSNF_QUOTE)))
            return 1;
          off += p - str + 1;
          str = p + 1;
@@ -913,7 +1464,7 @@ node_expand_vars (struct wordsplit *wsp, struct wordsplit_node *node)
       if (tail != node)
        tail->flags |= _WSNF_JOIN;
       if (node_split_prefix (wsp, &tail, node, off, p - str,
-                            node->flags & _WSNF_JOIN))
+                            node->flags & (_WSNF_JOIN|_WSNF_QUOTE)))
        return 1;
     }
   if (tail != node)
@@ -923,8 +1474,8 @@ node_expand_vars (struct wordsplit *wsp, struct wordsplit_node *node)
     }
   return 0;
 }
-
-/* Remove NULL lists */
+  
+/* Remove NULL nodes from the list */
 static void
 wsnode_nullelim (struct wordsplit *wsp)
 {
@@ -933,6 +1484,8 @@ wsnode_nullelim (struct wordsplit *wsp)
   for (p = wsp->ws_head; p;)
     {
       struct wordsplit_node *next = p->next;
+      if (p->flags & _WSNF_DELIM && p->prev)
+       p->prev->flags &= ~_WSNF_JOIN;
       if (p->flags & _WSNF_NULL)
        {
          wsnode_remove (wsp, p);
@@ -947,11 +1500,140 @@ wordsplit_varexp (struct wordsplit *wsp)
 {
   struct wordsplit_node *p;
 
+  for (p = wsp->ws_head; p;)
+    {
+      struct wordsplit_node *next = p->next;
+      if (!(p->flags & (_WSNF_NOEXPAND|_WSNF_DELIM)))
+       if (node_expand (wsp, p, begin_var_p, expvar))
+         return 1;
+      p = next;
+    }
+
+  wsnode_nullelim (wsp);
+  return 0;
+}
+\f
+static int
+begin_cmd_p (int c)
+{
+  return c == '(';
+}
+
+static int
+expcmd (struct wordsplit *wsp, const char *str, size_t len,
+       struct wordsplit_node **ptail, const char **pend, int flg)
+{
+  int rc;
+  size_t j;
+  char *value;
+  struct wordsplit_node *newnode;
+  
+  str++;
+  len--;
+
+  if (find_closing_paren (str, 0, len, &j, "()"))
+    {
+      _wsplt_seterr (wsp, WRDSE_PAREN);
+      return 1;
+    }
+
+  *pend = str + j;
+  if (wsp->ws_options & WRDSO_ARGV)
+    {
+      struct wordsplit ws;
+
+      rc = _wsplt_subsplit (wsp, &ws, str, j, WRDSF_WS | WRDSF_QUOTE, 1);
+      if (rc)
+       {
+         _wsplt_seterr_sub (wsp, &ws);
+         wordsplit_free (&ws);
+         return 1;
+       }
+      rc = wsp->ws_command (&value, str, j, ws.ws_wordv, wsp->ws_closure);
+      wordsplit_free (&ws);
+    }
+  else
+    rc = wsp->ws_command (&value, str, j, NULL, wsp->ws_closure);
+  
+  if (rc == WRDSE_NOSPACE)
+    return _wsplt_nomem (wsp);
+  else if (rc)
+    {
+      if (rc == WRDSE_USERERR)
+       {
+         if (wsp->ws_errno == WRDSE_USERERR)
+           free (wsp->ws_usererr);
+         wsp->ws_usererr = value;
+       }
+      _wsplt_seterr (wsp, rc);
+      return 1;
+    }
+
+  if (value)
+    {
+      if (flg & _WSNF_QUOTE)
+       {
+         if (wsnode_new (wsp, &newnode))
+           return 1;
+         wsnode_insert (wsp, newnode, *ptail, 0);
+         *ptail = newnode;
+         newnode->flags = _WSNF_WORD | _WSNF_NOEXPAND | flg;
+         newnode->v.word = value;
+       }
+      else if (*value == 0)
+       {
+         free (value);
+         /* Empty string is a special case */
+         if (wsnode_new (wsp, &newnode))
+           return 1;
+         wsnode_insert (wsp, newnode, *ptail, 0);
+         *ptail = newnode;
+         newnode->flags = _WSNF_NULL;
+       }
+      else
+       {
+         struct wordsplit ws;
+         int rc;
+
+         rc = _wsplt_subsplit (wsp, &ws, value, strlen (value),
+                               WRDSF_NOVAR | WRDSF_NOCMD
+                               | WRDSF_WS | WRDSF_QUOTE
+                               | (WSP_RETURN_DELIMS (wsp) ? WRDSF_RETURN_DELIMS : 0),
+                               0);
+         free (value);
+         if (rc)
+           {
+             _wsplt_seterr_sub (wsp, &ws);
+             wordsplit_free (&ws);
+             return 1;
+           }
+         wsnode_insert (wsp, ws.ws_head, *ptail, 0);
+         *ptail = ws.ws_tail;
+         ws.ws_head = ws.ws_tail = NULL;
+         wordsplit_free (&ws);
+       }
+    }
+  else
+    {
+      if (wsnode_new (wsp, &newnode))
+       return 1;
+      wsnode_insert (wsp, newnode, *ptail, 0);
+      *ptail = newnode;
+      newnode->flags = _WSNF_NULL;
+    }
+  return 0;
+}
+
+static int
+wordsplit_cmdexp (struct wordsplit *wsp)
+{
+  struct wordsplit_node *p;
+
   for (p = wsp->ws_head; p;)
     {
       struct wordsplit_node *next = p->next;
       if (!(p->flags & _WSNF_NOEXPAND))
-       if (node_expand_vars (wsp, p))
+       if (node_expand (wsp, p, begin_cmd_p, expcmd))
          return 1;
       p = next;
     }
@@ -963,7 +1645,7 @@ wordsplit_varexp (struct wordsplit *wsp)
 /* Strip off any leading and trailing whitespace.  This function is called
    right after the initial scanning, therefore it assumes that every
    node in the list is a text reference node. */
-static void
+static int
 wordsplit_trimws (struct wordsplit *wsp)
 {
   struct wordsplit_node *p;
@@ -972,14 +1654,21 @@ wordsplit_trimws (struct wordsplit *wsp)
     {
       size_t n;
 
+      if (!(p->flags & _WSNF_QUOTE))
+       {
+         /* Skip leading whitespace: */
+         for (n = p->v.segm.beg; n < p->v.segm.end && ISWS (wsp->ws_input[n]);
+              n++)
+           ;
+         p->v.segm.beg = n;
+       }
+      
+      while (p->next && (p->flags & _WSNF_JOIN))
+       p = p->next;
+      
       if (p->flags & _WSNF_QUOTE)
        continue;
-
-      /* Skip leading whitespace: */
-      for (n = p->v.segm.beg; n < p->v.segm.end && ISWS (wsp->ws_input[n]);
-          n++)
-       ;
-      p->v.segm.beg = n;
+      
       /* Trim trailing whitespace */
       for (n = p->v.segm.end;
           n > p->v.segm.beg && ISWS (wsp->ws_input[n - 1]); n--);
@@ -989,6 +1678,196 @@ wordsplit_trimws (struct wordsplit *wsp)
     }
 
   wsnode_nullelim (wsp);
+  return 0;
+}
+\f
+static int
+wordsplit_tildexpand (struct wordsplit *wsp)
+{
+  struct wordsplit_node *p;
+  char *uname = NULL;
+  size_t usize = 0;
+  
+  for (p = wsp->ws_head; p; p = p->next)
+    {
+      const char *str;
+
+      if (p->flags & _WSNF_QUOTE)
+       continue;
+
+      str = wsnode_ptr (wsp, p);
+      if (str[0] == '~')
+       {
+         size_t i, size, dlen;
+         size_t slen = wsnode_len (p);
+         struct passwd *pw;
+         char *newstr;
+         
+         for (i = 1; i < slen && str[i] != '/'; i++)
+           ;
+         if (i == slen)
+           continue;
+         if (i > 1)
+           {
+             if (i > usize)
+               {
+                 char *p = realloc (uname, i);
+                 if (!p)
+                   {
+                     free (uname);
+                     return _wsplt_nomem (wsp);
+                   }
+                 uname = p;
+                 usize = i;
+               }
+             --i;
+             memcpy (uname, str + 1, i);
+             uname[i] = 0;
+             pw = getpwnam (uname);
+           }
+         else
+           pw = getpwuid (getuid ());
+
+         if (!pw)
+           continue;
+
+         dlen = strlen (pw->pw_dir);
+         size = slen - i + dlen;
+         newstr = malloc (size);
+         if (!newstr)
+           {
+             free (uname);
+             return _wsplt_nomem (wsp);
+           }
+         --size;
+
+         memcpy (newstr, pw->pw_dir, dlen);
+         memcpy (newstr + dlen, str + i + 1, slen - i - 1);
+         newstr[size] = 0;
+         if (p->flags & _WSNF_WORD)
+           free (p->v.word);
+         p->v.word = newstr;
+         p->flags |= _WSNF_WORD;
+       }
+    }
+  free (uname);
+  return 0;
+}
+\f
+static int
+isglob (const char *s, int l)
+{
+  while (l--)
+    {
+      if (strchr ("*?[", *s++))
+       return 1;
+    }
+  return 0;
+}
+
+static int
+wordsplit_pathexpand (struct wordsplit *wsp)
+{
+  struct wordsplit_node *p, *next;
+  char *pattern = NULL;
+  size_t patsize = 0;
+  size_t slen;
+  int flags = 0;
+
+#ifdef GLOB_PERIOD
+  if (wsp->ws_options & WRDSO_DOTGLOB)
+    flags = GLOB_PERIOD;
+#endif
+  
+  for (p = wsp->ws_head; p; p = next)
+    {
+      const char *str;
+
+      next = p->next;
+
+      if (p->flags & _WSNF_QUOTE)
+       continue;
+
+      str = wsnode_ptr (wsp, p);
+      slen = wsnode_len (p);
+
+      if (isglob (str, slen))
+       {
+         int i;
+         glob_t g;
+         struct wordsplit_node *prev;
+         
+         if (slen + 1 > patsize)
+           {
+             char *p = realloc (pattern, slen + 1);
+             if (!p)
+               return _wsplt_nomem (wsp);
+             pattern = p;
+             patsize = slen + 1;
+           }
+         memcpy (pattern, str, slen);
+         pattern[slen] = 0;
+      
+         switch (glob (pattern, flags, NULL, &g))
+           {
+           case 0:
+             break;
+             
+           case GLOB_NOSPACE:
+             free (pattern);
+             return _wsplt_nomem (wsp);
+             
+           case GLOB_NOMATCH:
+             if (wsp->ws_options & WRDSO_NULLGLOB)
+               {
+                 wsnode_remove (wsp, p);
+                 wsnode_free (p);
+               }
+             else if (wsp->ws_options & WRDSO_FAILGLOB)
+               {
+                 char buf[128];
+                 if (wsp->ws_errno == WRDSE_USERERR)
+                   free (wsp->ws_usererr);
+                 snprintf (buf, sizeof (buf), _("no files match pattern %s"),
+                           pattern);
+                 free (pattern);
+                 wsp->ws_usererr = strdup (buf);
+                 if (!wsp->ws_usererr)
+                   return _wsplt_nomem (wsp);
+                 else
+                   return _wsplt_seterr (wsp, WRDSE_USERERR);
+               }
+             continue;
+             
+           default:
+             free (pattern);
+             return _wsplt_seterr (wsp, WRDSE_GLOBERR);
+           }
+
+         prev = p;
+         for (i = 0; i < g.gl_pathc; i++)
+           {
+             struct wordsplit_node *newnode;
+             char *newstr;
+             
+             if (wsnode_new (wsp, &newnode))
+               return 1;
+             newstr = strdup (g.gl_pathv[i]);
+             if (!newstr)
+               return _wsplt_nomem (wsp);
+             newnode->v.word = newstr;
+             newnode->flags |= _WSNF_WORD|_WSNF_QUOTE;
+             wsnode_insert (wsp, newnode, prev, 0);
+             prev = newnode;
+           }
+         globfree (&g);
+
+         wsnode_remove (wsp, p);
+         wsnode_free (p);
+       }
+    }
+  free (pattern);
+  return 0;
 }
 \f
 static int
@@ -1024,33 +1903,24 @@ skip_sed_expr (const char *command, size_t i, size_t len)
   return i;
 }
 
-static size_t
-skip_delim (struct wordsplit *wsp)
+/* wsp->ws_endp points to a delimiter character. If RETURN_DELIMS
+   is true, return its value, otherwise return the index past it. */
+static inline size_t
+skip_delim_internal (struct wordsplit *wsp, int return_delims)
 {
-  size_t start = wsp->ws_endp;
-  if (wsp->ws_flags & WRDSF_SQUEEZE_DELIMS)
-    {
-      if ((wsp->ws_flags & WRDSF_RETURN_DELIMS) &&
-         ISDELIM (wsp, wsp->ws_input[start]))
-       {
-         int delim = wsp->ws_input[start];
-         do
-           start++;
-         while (start < wsp->ws_len && delim == wsp->ws_input[start]);
-       }
-      else
-       {
-         do
-           start++;
-         while (start < wsp->ws_len && ISDELIM (wsp, wsp->ws_input[start]));
-       }
-      start--;
-    }
+  return return_delims ? wsp->ws_endp : wsp->ws_endp + 1;
+}
 
-  if (!(wsp->ws_flags & WRDSF_RETURN_DELIMS))
-    start++;
+static inline size_t
+skip_delim (struct wordsplit *wsp)
+{
+  return skip_delim_internal (wsp, WSP_RETURN_DELIMS (wsp));
+}
 
-  return start;
+static inline size_t
+skip_delim_real (struct wordsplit *wsp)
+{
+  return skip_delim_internal (wsp, wsp->ws_flags & WRDSF_RETURN_DELIMS);
 }
 
 #define _WRDS_EOF   0
@@ -1058,7 +1928,7 @@ skip_delim (struct wordsplit *wsp)
 #define _WRDS_ERR   2
 
 static int
-scan_qstring (struct wordsplit *wsp, size_t start, size_t * end)
+scan_qstring (struct wordsplit *wsp, size_t start, size_t *end)
 {
   size_t j;
   const char *command = wsp->ws_input;
@@ -1080,23 +1950,22 @@ scan_qstring (struct wordsplit *wsp, size_t start, size_t * end)
   else
     {
       wsp->ws_endp = start;
-      wsp->ws_errno = WRDSE_QUOTE;
-      if (wsp->ws_flags & WRDSF_SHOWERR)
-       wordsplit_perror (wsp);
+      _wsplt_seterr (wsp, WRDSE_QUOTE);
       return _WRDS_ERR;
     }
   return 0;
 }
 
 static int
-scan_word (struct wordsplit *wsp, size_t start)
+scan_word (struct wordsplit *wsp, size_t start, int consume_all)
 {
   size_t len = wsp->ws_len;
   const char *command = wsp->ws_input;
   const char *comment = wsp->ws_comment;
   int join = 0;
   int flags = 0;
-
+  struct wordsplit_node *np = wsp->ws_tail;
+  
   size_t i = start;
 
   if (i >= len)
@@ -1113,7 +1982,7 @@ scan_word (struct wordsplit *wsp, size_t start)
       flags = _WSNF_SEXP;
       i = skip_sed_expr (command, i, len);
     }
-  else if (!ISDELIM (wsp, command[i]))
+  else if (consume_all || !ISDELIM (wsp, command[i]))
     {
       while (i < len)
        {
@@ -1152,15 +2021,28 @@ scan_word (struct wordsplit *wsp, size_t start)
                }
            }
 
-         if (ISDELIM (wsp, command[i]))
+         if (command[i] == '$')
+           {
+             if (!(wsp->ws_flags & WRDSF_NOVAR)
+                 && command[i+1] == '{'
+                 && find_closing_paren (command, i + 2, len, &i, "{}") == 0)
+               continue;
+             if (!(wsp->ws_flags & WRDSF_NOCMD)
+                 && command[i+1] == '('
+                 && find_closing_paren (command, i + 2, len, &i, "()") == 0)
+               continue;
+           }
+
+         if (!consume_all && ISDELIM (wsp, command[i]))
            break;
          else
            i++;
        }
     }
-  else if (wsp->ws_flags & WRDSF_RETURN_DELIMS)
+  else if (WSP_RETURN_DELIMS (wsp))
     {
       i++;
+      flags |= _WSNF_DELIM;
     }
   else if (!(wsp->ws_flags & WRDSF_SQUEEZE_DELIMS))
     flags |= _WSNF_EMPTYOK;
@@ -1172,36 +2054,19 @@ scan_word (struct wordsplit *wsp, size_t start)
   wsp->ws_endp = i;
   if (wsp->ws_flags & WRDSF_INCREMENTAL)
     return _WRDS_EOF;
-  return _WRDS_OK;
-}
-
-static char quote_transtab[] = "\\\\\"\"a\ab\bf\fn\nr\rt\tv\v";
-
-int
-wordsplit_c_unquote_char (int c)
-{
-  char *p;
-
-  for (p = quote_transtab; *p; p += 2)
+  
+  if (consume_all)
     {
-      if (*p == c)
-       return p[1];
-    }
-  return c;
-}
-
-int
-wordsplit_c_quote_char (int c)
-{
-  char *p;
-
-  for (p = quote_transtab + sizeof (quote_transtab) - 2;
-       p > quote_transtab; p -= 2)
-    {
-      if (*p == c)
-       return p[-1];
+      if (!np)
+       np = wsp->ws_head;
+      while (np)
+       {
+         np->flags |= _WSNF_QUOTE;
+         np = np->next;
+       }
     }
-  return -1;
+  
+  return _WRDS_OK;
 }
 
 #define to_num(c) \
@@ -1244,7 +2109,7 @@ wordsplit_c_quoted_length (const char *str, int quote_hex, int *quote)
        len += 3;
       else
        {
-         if (wordsplit_c_quote_char (*str) != -1)
+         if (wordsplit_c_quote_char (*str))
            len += 2;
          else
            len += 4;
@@ -1253,47 +2118,56 @@ wordsplit_c_quoted_length (const char *str, int quote_hex, int *quote)
   return len;
 }
 
-void
-wordsplit_general_unquote_copy (char *dst, const char *src, size_t n,
-                                  const char *escapable)
+static int
+wsplt_unquote_char (const char *transtab, int c)
 {
-  int i;
-
-  for (i = 0; i < n;)
+  while (*transtab && transtab[1])
     {
-      if (src[i] == '\\' && i < n && strchr (escapable, src[i + 1]))
-       i++;
-      *dst++ = src[i++];
+      if (*transtab++ == c)
+       return *transtab;
+      ++transtab;
     }
-  *dst = 0;
+  return 0;
 }
 
-void
-wordsplit_sh_unquote_copy (char *dst, const char *src, size_t n)
+static int
+wsplt_quote_char (const char *transtab, int c)
 {
-  int i;
-
-  for (i = 0; i < n;)
+  for (; *transtab && transtab[1]; transtab += 2)
     {
-      if (src[i] == '\\')
-       i++;
-      *dst++ = src[i++];
+      if (transtab[1] == c)
+       return *transtab;
     }
-  *dst = 0;
+  return 0;
+}
+
+int
+wordsplit_c_unquote_char (int c)
+{
+  return wsplt_unquote_char (wordsplit_c_escape_tab, c);
+}
+
+int
+wordsplit_c_quote_char (int c)
+{
+  return wsplt_quote_char (wordsplit_c_escape_tab, c);
 }
 
 void
-wordsplit_c_unquote_copy (char *dst, const char *src, size_t n)
+wordsplit_string_unquote_copy (struct wordsplit *ws, int inquote,
+                              char *dst, const char *src, size_t n)
 {
   int i = 0;
   int c;
 
+  inquote = !!inquote;
   while (i < n)
     {
       if (src[i] == '\\')
        {
          ++i;
-         if (src[i] == 'x' || src[i] == 'X')
+         if (WRDSO_ESC_TEST (ws, inquote, WRDSO_XESC)
+             && (src[i] == 'x' || src[i] == 'X'))
            {
              if (n - i < 2)
                {
@@ -1316,7 +2190,8 @@ wordsplit_c_unquote_copy (char *dst, const char *src, size_t n)
                    }
                }
            }
-         else if ((unsigned char) src[i] < 128 && ISDIGIT (src[i]))
+         else if (WRDSO_ESC_TEST (ws, inquote, WRDSO_OESC)
+                  && (unsigned char) src[i] < 128 && ISDIGIT (src[i]))
            {
              if (n - i < 1)
                {
@@ -1338,8 +2213,17 @@ wordsplit_c_unquote_copy (char *dst, const char *src, size_t n)
                    }
                }
            }
+         else if ((c = wsplt_unquote_char (ws->ws_escape[inquote], src[i])))
+           {
+             *dst++ = c;
+             ++i;
+           }
          else
-           *dst++ = wordsplit_c_unquote_char (src[i++]);
+           {
+             if (WRDSO_ESC_TEST (ws, inquote, WRDSO_BSKEEP))
+               *dst++ = '\\';
+             *dst++ = src[i++];
+           }
        }
       else
        *dst++ = src[i++];
@@ -1373,7 +2257,7 @@ wordsplit_c_quote_copy (char *dst, const char *src, int quote_hex)
            {
              int c = wordsplit_c_quote_char (*src);
              *dst++ = '\\';
-             if (c != -1)
+             if (c)
                *dst++ = c;
              else
                {
@@ -1386,20 +2270,82 @@ wordsplit_c_quote_copy (char *dst, const char *src, int quote_hex)
     }
 }
 
+
+/* This structure describes a single expansion phase */
+struct exptab
+{
+  char const *descr; /* Textual description (for debugging) */
+  int flag;          /* WRDSF_ bit that controls this phase */
+  int opt;           /* Entry-specific options (see EXPOPT_ flags below */
+  int (*expansion) (struct wordsplit *wsp); /* expansion function */
+};
+
+/* The following options control expansions: */
+/* Normally the exptab entry is run if its flag bit is set in struct
+   wordsplit.  The EXPOPT_NEG option negates this test so that expansion
+   is performed if its associated flag bit is not set in struct wordsplit. */
+#define EXPOPT_NEG      0x01
+/* All bits in flag must be set in order for entry to match */
+#define EXPORT_ALLOF    0x02
+/* Coalesce the input list before running the expansion. */
+#define EXPOPT_COALESCE 0x04
+
+static struct exptab exptab[] = {
+  { N_("WS trimming"),          WRDSF_WS,         0,
+    wordsplit_trimws },
+  { N_("command substitution"), WRDSF_NOCMD,      EXPOPT_NEG|EXPOPT_COALESCE,
+    wordsplit_cmdexp },
+  { N_("coalesce list"),        0,                EXPOPT_NEG|EXPOPT_COALESCE,
+    NULL },
+  { N_("tilde expansion"),      WRDSF_PATHEXPAND, 0,
+    wordsplit_tildexpand },
+  { N_("variable expansion"),   WRDSF_NOVAR,      EXPOPT_NEG,
+    wordsplit_varexp },
+  { N_("quote removal"),        0,                EXPOPT_NEG,
+    wsnode_quoteremoval },
+  { N_("coalesce list"),        0,                EXPOPT_NEG|EXPOPT_COALESCE,
+    NULL },
+  { N_("path expansion"),       WRDSF_PATHEXPAND, 0,
+    wordsplit_pathexpand },
+  { NULL }
+};
+
+static inline int
+exptab_matches(struct exptab *p, struct wordsplit *wsp)
+{
+  int result;
+
+  result = (wsp->ws_flags & p->flag);
+  if (p->opt & EXPORT_ALLOF)
+    result = result == p->flag;
+  if (p->opt & EXPOPT_NEG)
+    result = !result;
+
+  return result;
+}
+
 static int
 wordsplit_process_list (struct wordsplit *wsp, size_t start)
 {
-  if (wsp->ws_flags & WRDSF_NOSPLIT)
-    {
-      /* Treat entire input as a quoted argument */
-      if (wordsplit_add_segm (wsp, start, wsp->ws_len, _WSNF_QUOTE))
+  struct exptab *p;
+
+  if (wsp->ws_flags & WRDSF_SHOWDBG)
+    wsp->ws_debug (_("(%02d) Input:%.*s;"),
+                  wsp->ws_lvl, (int) wsp->ws_len, wsp->ws_input);
+  
+  if ((wsp->ws_flags & WRDSF_NOSPLIT)
+      || ((wsp->ws_options & WRDSO_MAXWORDS)
+         && wsp->ws_wordi + 1 == wsp->ws_maxwords))
+    {
+      /* Treat entire input as a single word */
+      if (scan_word (wsp, start, 1) == _WRDS_ERR)
        return wsp->ws_errno;
     }
   else
     {
       int rc;
 
-      while ((rc = scan_word (wsp, start)) == _WRDS_OK)
+      while ((rc = scan_word (wsp, start, 0)) == _WRDS_OK)
        start = skip_delim (wsp);
       /* Make sure tail element is not joinable */
       if (wsp->ws_tail)
@@ -1410,132 +2356,88 @@ wordsplit_process_list (struct wordsplit *wsp, size_t start)
 
   if (wsp->ws_flags & WRDSF_SHOWDBG)
     {
-      wsp->ws_debug ("Initial list:");
+      wsp->ws_debug ("(%02d) %s", wsp->ws_lvl, _("Initial list:"));
       wordsplit_dump_nodes (wsp);
     }
 
-  if (wsp->ws_flags & WRDSF_WS)
+  for (p = exptab; p->descr; p++)
     {
-      /* Trim leading and trailing whitespace */
-      wordsplit_trimws (wsp);
-      if (wsp->ws_flags & WRDSF_SHOWDBG)
+      if (exptab_matches(p, wsp))
        {
-         wsp->ws_debug ("After WS trimming:");
-         wordsplit_dump_nodes (wsp);
-       }
-    }
-
-  /* Expand variables (FIXME: & commands) */
-  if (!(wsp->ws_flags & WRDSF_NOVAR))
-    {
-      if (wordsplit_varexp (wsp))
-       {
-         wordsplit_free_nodes (wsp);
-         return wsp->ws_errno;
-       }
-      if (wsp->ws_flags & WRDSF_SHOWDBG)
-       {
-         wsp->ws_debug ("Expanded list:");
-         wordsplit_dump_nodes (wsp);
-       }
-    }
-
-  do
-    {
-      if (wsnode_quoteremoval (wsp))
-       break;
-      if (wsp->ws_flags & WRDSF_SHOWDBG)
-       {
-         wsp->ws_debug ("After quote removal:");
-         wordsplit_dump_nodes (wsp);
-       }
-
-      if (wsnode_coalesce (wsp))
-       break;
-
-      if (wsp->ws_flags & WRDSF_SHOWDBG)
-       {
-         wsp->ws_debug ("Coalesced list:");
-         wordsplit_dump_nodes (wsp);
+         if (p->opt & EXPOPT_COALESCE)
+           {
+             if (wsnode_coalesce (wsp))
+               break;
+             if (wsp->ws_flags & WRDSF_SHOWDBG)
+               {
+                 wsp->ws_debug ("(%02d) %s", wsp->ws_lvl,
+                                _("Coalesced list:"));
+                 wordsplit_dump_nodes (wsp);
+               }
+           }
+         if (p->expansion)
+           {
+             if (p->expansion (wsp))
+               break;
+             if (wsp->ws_flags & WRDSF_SHOWDBG)
+               {
+                 wsp->ws_debug ("(%02d) %s", wsp->ws_lvl, _(p->descr));
+                 wordsplit_dump_nodes (wsp);
+               }
+           }
        }
     }
-  while (0);
   return wsp->ws_errno;
 }
 
-int
-wordsplit_len (const char *command, size_t length, struct wordsplit *wsp,
-               int flags)
+static int
+wordsplit_run (const char *command, size_t length, struct wordsplit *wsp,
+               int flags, int lvl)
 {
   int rc;
   size_t start;
-  const char *cmdptr;
-  size_t cmdlen;
 
   if (!command)
     {
       if (!(flags & WRDSF_INCREMENTAL))
-       return EINVAL;
+       return _wsplt_seterr (wsp, WRDSE_USAGE);
+
+      if (wsp->ws_head)
+       return wordsplit_finish (wsp);
 
-      start = skip_delim (wsp);
+      start = skip_delim_real (wsp);
       if (wsp->ws_endp == wsp->ws_len)
-       {
-         wsp->ws_errno = WRDSE_NOINPUT;
-         if (wsp->ws_flags & WRDSF_SHOWERR)
-           wordsplit_perror (wsp);
-         return wsp->ws_errno;
-       }
+       return _wsplt_seterr (wsp, WRDSE_NOINPUT);
 
-      cmdptr = wsp->ws_input + wsp->ws_endp;
-      cmdlen = wsp->ws_len - wsp->ws_endp;
       wsp->ws_flags |= WRDSF_REUSE;
       wordsplit_init0 (wsp);
     }
   else
     {
-      cmdptr = command;
-      cmdlen = length;
       start = 0;
-      rc = wordsplit_init (wsp, cmdptr, cmdlen, flags);
+      rc = wordsplit_init (wsp, command, length, flags);
       if (rc)
        return rc;
+      wsp->ws_lvl = lvl;
     }
 
-  if (wsp->ws_flags & WRDSF_SHOWDBG)
-    wsp->ws_debug ("Input:%.*s;", (int) cmdlen, cmdptr);
-
   rc = wordsplit_process_list (wsp, start);
-  if (rc == 0 && (flags & WRDSF_INCREMENTAL))
-    {
-      while (!wsp->ws_head && wsp->ws_endp < wsp->ws_len)
-       {
-         start = skip_delim (wsp);
-         if (wsp->ws_flags & WRDSF_SHOWDBG)
-           {
-             cmdptr = wsp->ws_input + wsp->ws_endp;
-             cmdlen = wsp->ws_len - wsp->ws_endp;
-             wsp->ws_debug ("Restart:%.*s;", (int) cmdlen, cmdptr);
-           }
-         rc = wordsplit_process_list (wsp, start);
-         if (rc)
-           break;
-       }
-    }
   if (rc)
-    {
-      wordsplit_free_nodes (wsp);
-      return rc;
-    }
-  wordsplit_finish (wsp);
-  wordsplit_free_nodes (wsp);
-  return wsp->ws_errno;
+    return rc;
+  return wordsplit_finish (wsp);
+}
+
+int
+wordsplit_len (const char *command, size_t length, struct wordsplit *wsp, 
+               int flags)
+{
+  return wordsplit_run (command, length, wsp, flags, 0);
 }
 
 int
 wordsplit (const char *command, struct wordsplit *ws, int flags)
 {
-  return wordsplit_len (command, command ? strlen (command) : 0, ws,
-                          flags);
+  return wordsplit_len (command, command ? strlen (command) : 0, ws, flags);
 }
 
 void
@@ -1555,67 +2457,69 @@ wordsplit_free_words (struct wordsplit *ws)
   ws->ws_wordc = 0;
 }
 
+void
+wordsplit_free_envbuf (struct wordsplit *ws)
+{
+  if (ws->ws_flags & WRDSF_NOCMD)
+    return;
+  if (ws->ws_envbuf)
+    {
+      size_t i;
+
+      for (i = 0; ws->ws_envbuf[i]; i++)
+       free (ws->ws_envbuf[i]);
+      free (ws->ws_envbuf);
+      ws->ws_envidx = ws->ws_envsiz = 0;
+      ws->ws_envbuf = NULL;
+    }
+}
+
+void
+wordsplit_clearerr (struct wordsplit *ws)
+{
+  if (ws->ws_errno == WRDSE_USERERR)
+    free (ws->ws_usererr);
+  ws->ws_usererr = NULL;
+  ws->ws_errno = WRDSE_OK;
+}
+
 void
 wordsplit_free (struct wordsplit *ws)
 {
+  wordsplit_free_nodes (ws);
   wordsplit_free_words (ws);
   free (ws->ws_wordv);
   ws->ws_wordv = NULL;
+  wordsplit_free_envbuf (ws);
 }
 
-void
-wordsplit_perror (struct wordsplit *wsp)
+int
+wordsplit_get_words (struct wordsplit *ws, size_t *wordc, char ***wordv)
 {
-  switch (wsp->ws_errno)
-    {
-    case WRDSE_EOF:
-      wsp->ws_error (_("no error"));
-      break;
-
-    case WRDSE_QUOTE:
-      wsp->ws_error (_("missing closing %c (start near #%lu)"),
-                    wsp->ws_input[wsp->ws_endp],
-                    (unsigned long) wsp->ws_endp);
-      break;
-
-    case WRDSE_NOSPACE:
-      wsp->ws_error (_("memory exhausted"));
-      break;
-
-    case WRDSE_NOSUPP:
-      wsp->ws_error (_("command substitution is not yet supported"));
-      break;
-
-    case WRDSE_USAGE:
-      wsp->ws_error (_("invalid wordsplit usage"));
-      break;
-
-    case WRDSE_CBRACE:
-      wsp->ws_error (_("unbalanced curly brace"));
-      break;
-
-    case WRDSE_UNDEF:
-      wsp->ws_error (_("undefined variable"));
-      break;
+  char **p = realloc (ws->ws_wordv,
+                     (ws->ws_wordc + 1) * sizeof (ws->ws_wordv[0]));
+  if (!p)
+    return -1;
+  *wordv = p;
+  *wordc = ws->ws_wordc;
 
-    case WRDSE_NOINPUT:
-      wsp->ws_error (_("input exhausted"));
-      break;
+  ws->ws_wordv = NULL;
+  ws->ws_wordc = 0;
+  ws->ws_wordn = 0;
 
-    default:
-      wsp->ws_error (_("unknown error"));
-    }
+  return 0;
 }
 
 const char *_wordsplit_errstr[] = {
   N_("no error"),
   N_("missing closing quote"),
   N_("memory exhausted"),
-  N_("command substitution is not yet supported"),
   N_("invalid wordsplit usage"),
   N_("unbalanced curly brace"),
   N_("undefined variable"),
-  N_("input exhausted")
+  N_("input exhausted"),
+  N_("unbalanced parenthesis"),
+  N_("globbing error")
 };
 int _wordsplit_nerrs =
   sizeof (_wordsplit_errstr) / sizeof (_wordsplit_errstr[0]);
@@ -1623,7 +2527,26 @@ int _wordsplit_nerrs =
 const char *
 wordsplit_strerror (struct wordsplit *ws)
 {
+  if (ws->ws_errno == WRDSE_USERERR)
+    return ws->ws_usererr;
   if (ws->ws_errno < _wordsplit_nerrs)
     return _wordsplit_errstr[ws->ws_errno];
   return N_("unknown error");
 }
+
+void
+wordsplit_perror (struct wordsplit *wsp)
+{
+  switch (wsp->ws_errno)
+    {
+    case WRDSE_QUOTE:
+      wsp->ws_error (_("missing closing %c (start near #%lu)"),
+                    wsp->ws_input[wsp->ws_endp],
+                    (unsigned long) wsp->ws_endp);
+      break;
+
+    default:
+      wsp->ws_error ("%s", wordsplit_strerror (wsp));
+    }
+}
+
index 575865b6e687fc980f61244d07594c1962bc6db3..df0f4075d0aefe58761556abcdf7a7dcb29c5f68 100644 (file)
@@ -1,5 +1,5 @@
 /* wordsplit - a word splitter
-   Copyright (C) 2009-2014, 2016-2017 Free Software Foundation, Inc.
+   Copyright (C) 2009-2018 Sergey Poznyakoff
 
    This program is free software; you can redistribute it and/or modify it
    under the terms of the GNU General Public License as published by the
    GNU General Public License for more details.
 
    You should have received a copy of the GNU General Public License along
-   with this program. If not, see <http://www.gnu.org/licenses/>.
-
-   Written by Sergey Poznyakoff
-*/
+   with this program. If not, see <http://www.gnu.org/licenses/>. */
 
 #ifndef __WORDSPLIT_H
 #define __WORDSPLIT_H
 # define __WORDSPLIT_ATTRIBUTE_FORMAT(spec) /* empty */
 #endif
 
-struct wordsplit
+typedef struct wordsplit wordsplit_t;
+
+/* Structure used to direct the splitting.  Members marked with [Input]
+   can be defined before calling wordsplit(), those marked with [Output]
+   provide return values when the function returns.  If neither mark is
+   used, the member is internal and must not be used by the caller.
+
+   In the comments below, the identifiers in parentheses indicate bits that
+   must be set (or unset, if starting with !) in ws_flags (if starting with
+   WRDSF_) or ws_options (if starting with WRDSO_) to initialize or use the
+   given member.
+   
+   If not redefined explicitly, most of them are set to some reasonable
+   default value upon entry to wordsplit(). */
+struct wordsplit            
 {
-  size_t ws_wordc;
-  char **ws_wordv;
-  size_t ws_offs;
-  size_t ws_wordn;
-  int ws_flags;
-  const char *ws_delim;
-  const char *ws_comment;
-  const char *ws_escape;
-  void (*ws_alloc_die) (struct wordsplit * wsp);
+  size_t ws_wordc;          /* [Output] Number of words in ws_wordv. */
+  char **ws_wordv;          /* [Output] Array of parsed out words. */
+  size_t ws_offs;           /* [Input] (WRDSF_DOOFFS) Number of initial
+                              elements in ws_wordv to fill with NULLs. */
+  size_t ws_wordn;          /* Number of elements ws_wordv can accomodate. */ 
+  int ws_flags;             /* [Input] Flags passed to wordsplit. */
+  int ws_options;           /* [Input] (WRDSF_OPTIONS)
+                              Additional options. */
+  size_t ws_maxwords;       /* [Input] (WRDSO_MAXWORDS) Return at most that
+                              many words */
+  size_t ws_wordi;          /* [Output] (WRDSF_INCREMENTAL) Total number of
+                              words returned so far */
+
+  const char *ws_delim;     /* [Input] (WRDSF_DELIM) Word delimiters. */
+  const char *ws_comment;   /* [Input] (WRDSF_COMMENT) Comment characters. */
+  const char *ws_escape[2]; /* [Input] (WRDSF_ESCAPE) Characters to be escaped
+                              with backslash. */
+  void (*ws_alloc_die) (wordsplit_t *wsp);
+                            /* [Input] (WRDSF_ALLOC_DIE) Function called when
+                              out of memory.  Must not return. */
   void (*ws_error) (const char *, ...)
-    __WORDSPLIT_ATTRIBUTE_FORMAT ((__printf__, 1, 2));
+                   __attribute__ ((__format__ (__printf__, 1, 2)));
+                            /* [Input] (WRDSF_ERROR) Function used for error
+                              reporting */
   void (*ws_debug) (const char *, ...)
-    __WORDSPLIT_ATTRIBUTE_FORMAT ((__printf__, 1, 2));
+                   __attribute__ ((__format__ (__printf__, 1, 2)));
+                            /* [Input] (WRDSF_DEBUG) Function used for debug
+                              output. */
+  const char **ws_env;      /* [Input] (WRDSF_ENV, !WRDSF_NOVAR) Array of
+                              environment variables. */
 
-  const char **ws_env;
-  const char *(*ws_getvar) (const char *, size_t, void *);
-  void *ws_closure;
+  char **ws_envbuf;
+  size_t ws_envidx;
+  size_t ws_envsiz;
+  
+  int (*ws_getvar) (char **ret, const char *var, size_t len, void *clos);
+                            /* [Input] (WRDSF_GETVAR, !WRDSF_NOVAR) Looks up
+                              the name VAR (LEN bytes long) in the table of
+                              variables and if found returns in memory
+                              location pointed to by RET the value of that
+                              variable.  Returns WRDSE_OK (0) on success,
+                              and an error code (see WRDSE_* defines below)
+                              on error.  User-specific errors can be returned
+                              by storing the error diagnostic string in RET
+                              and returning WRDSE_USERERR.
+                               Whatever is stored in RET, it must be allocated
+                              using malloc(3). */
+  void *ws_closure;         /* [Input] (WRDSF_CLOSURE) Passed as the CLOS
+                              argument to ws_getvar and ws_command. */
+  int (*ws_command) (char **ret, const char *cmd, size_t len, char **argv,
+                     void *clos);
+                           /* [Input] (!WRDSF_NOCMD) Returns in the memory
+                              location pointed to by RET the expansion of
+                              the command CMD (LEN bytes long).  If WRDSO_ARGV
+                              option is set, ARGV contains CMD split out to
+                              words.  Otherwise ARGV is NULL.
 
-  const char *ws_input;
-  size_t ws_len;
-  size_t ws_endp;
-  int ws_errno;
+                              See ws_getvar for a discussion of possible
+                              return values. */
+       
+  const char *ws_input;     /* Input string (the S argument to wordsplit. */  
+  size_t ws_len;            /* Length of ws_input. */
+  size_t ws_endp;           /* Points past the last processed byte in
+                              ws_input. */
+  int ws_errno;             /* [Output] Error code, if an error occurred. */
+  char *ws_usererr;         /* Points to textual description of
+                              the error, if ws_errno is WRDSE_USERERR.  Must
+                              be allocated with malloc(3). */
   struct wordsplit_node *ws_head, *ws_tail;
+                            /* Doubly-linked list of parsed out nodes. */
+  int ws_lvl;               /* Invocation nesting level. */
 };
 
-/* Wordsplit flags.  Only 2 bits of a 32-bit word remain unused.
-   It is getting crowded... */
+/* Initial size for ws_env, if allocated automatically */
+#define WORDSPLIT_ENV_INIT 16
+
+/* Wordsplit flags. */
 /* Append the words found to the array resulting from a previous
    call. */
 #define WRDSF_APPEND            0x00000001
-/* Insert we_offs initial NULLs in the array ws_wordv.
+/* Insert ws_offs initial NULLs in the array ws_wordv.
    (These are not counted in the returned ws_wordc.) */
 #define WRDSF_DOOFFS            0x00000002
-/* Don't do command substitution. Reserved for future use. */
+/* Don't do command substitution. */
 #define WRDSF_NOCMD             0x00000004
 /* The parameter p resulted from a previous call to
    wordsplit(), and wordsplit_free() was not called. Reuse the
@@ -71,10 +132,8 @@ struct wordsplit
 #define WRDSF_REUSE             0x00000008
 /* Print errors */
 #define WRDSF_SHOWERR           0x00000010
-/* Consider it an error if an undefined shell variable
-   is expanded. */
+/* Consider it an error if an undefined variable is expanded. */
 #define WRDSF_UNDEF             0x00000020
-
 /* Don't do variable expansion. */
 #define WRDSF_NOVAR             0x00000040
 /* Abort on ENOMEM error */
@@ -85,7 +144,7 @@ struct wordsplit
 #define WRDSF_SQUOTE            0x00000200
 /* Handle double quotes */
 #define WRDSF_DQUOTE            0x00000400
-/* Handle quotes and escape directives */
+/* Handle single and double quotes */
 #define WRDSF_QUOTE             (WRDSF_SQUOTE|WRDSF_DQUOTE)
 /* Replace each input sequence of repeated delimiters with a single
    delimiter */
@@ -113,56 +172,106 @@ struct wordsplit
 /* Don't split input into words.  Useful for side effects. */
 #define WRDSF_NOSPLIT           0x00400000
 /* Keep undefined variables in place, instead of expanding them to
-   empty string */
+   empty strings. */
 #define WRDSF_KEEPUNDEF         0x00800000
 /* Warn about undefined variables */
 #define WRDSF_WARNUNDEF         0x01000000
 /* Handle C escapes */
 #define WRDSF_CESCAPES          0x02000000
-
 /* ws_closure is set */
 #define WRDSF_CLOSURE           0x04000000
 /* ws_env is a Key/Value environment, i.e. the value of a variable is
    stored in the element that follows its name. */
 #define WRDSF_ENV_KV            0x08000000
-
 /* ws_escape is set */
 #define WRDSF_ESCAPE            0x10000000
-
 /* Incremental mode */
 #define WRDSF_INCREMENTAL       0x20000000
+/* Perform pathname and tilde expansion */
+#define WRDSF_PATHEXPAND        0x40000000
+/* ws_options is initialized */
+#define WRDSF_OPTIONS           0x80000000
 
 #define WRDSF_DEFFLAGS        \
   (WRDSF_NOVAR | WRDSF_NOCMD | \
    WRDSF_QUOTE | WRDSF_SQUEEZE_DELIMS | WRDSF_CESCAPES)
 
-#define WRDSE_EOF        0
+/* Remove the word that produces empty string after path expansion */
+#define WRDSO_NULLGLOB        0x00000001
+/* Print error message if path expansion produces empty string */
+#define WRDSO_FAILGLOB        0x00000002
+/* Allow a leading period to be matched by metacharacters. */
+#define WRDSO_DOTGLOB         0x00000004
+/* ws_command needs argv parameter */
+#define WRDSO_ARGV            0x00000008
+/* Keep backslash in unrecognized escape sequences in words */
+#define WRDSO_BSKEEP_WORD     0x00000010
+/* Handle octal escapes in words */
+#define WRDSO_OESC_WORD       0x00000020
+/* Handle hex escapes in words */
+#define WRDSO_XESC_WORD       0x00000040
+
+/* ws_maxwords field is initialized */
+#define WRDSO_MAXWORDS        0x00000080
+
+/* Keep backslash in unrecognized escape sequences in quoted strings */
+#define WRDSO_BSKEEP_QUOTE    0x00000100
+/* Handle octal escapes in quoted strings */
+#define WRDSO_OESC_QUOTE      0x00000200
+/* Handle hex escapes in quoted strings */
+#define WRDSO_XESC_QUOTE      0x00000400
+
+#define WRDSO_BSKEEP          WRDSO_BSKEEP_WORD     
+#define WRDSO_OESC            WRDSO_OESC_WORD       
+#define WRDSO_XESC            WRDSO_XESC_WORD       
+
+/* Indices into ws_escape */
+#define WRDSX_WORD  0
+#define WRDSX_QUOTE 1
+
+/* Set escape option F in WS for words (Q==0) or quoted strings (Q==1) */
+#define WRDSO_ESC_SET(ws,q,f) ((ws)->ws_options |= ((f) << 4*(q)))
+/* Test WS for escape option F for words (Q==0) or quoted strings (Q==1) */
+#define WRDSO_ESC_TEST(ws,q,f) ((ws)->ws_options & ((f) << 4*(q)))
+
+#define WRDSE_OK         0
+#define WRDSE_EOF        WRDSE_OK
 #define WRDSE_QUOTE      1
 #define WRDSE_NOSPACE    2
-#define WRDSE_NOSUPP     3
-#define WRDSE_USAGE      4
-#define WRDSE_CBRACE     5
-#define WRDSE_UNDEF      6
-#define WRDSE_NOINPUT    7
-
-int wordsplit (const char *s, struct wordsplit *p, int flags);
-int wordsplit_len (const char *s, size_t len,
-                     struct wordsplit *p, int flags);
-void wordsplit_free (struct wordsplit *p);
-void wordsplit_free_words (struct wordsplit *ws);
+#define WRDSE_USAGE      3
+#define WRDSE_CBRACE     4
+#define WRDSE_UNDEF      5
+#define WRDSE_NOINPUT    6
+#define WRDSE_PAREN      7
+#define WRDSE_GLOBERR    8
+#define WRDSE_USERERR    9
+
+int wordsplit (const char *s, wordsplit_t *ws, int flags);
+int wordsplit_len (const char *s, size_t len, wordsplit_t *ws, int flags);
+void wordsplit_free (wordsplit_t *ws);
+void wordsplit_free_words (wordsplit_t *ws);
+void wordsplit_free_envbuf (wordsplit_t *ws);
+int wordsplit_get_words (wordsplit_t *ws, size_t *wordc, char ***wordv);
+
+static inline void wordsplit_getwords (wordsplit_t *ws, size_t *wordc, char ***wordv)
+  __attribute__ ((deprecated));
+
+static inline void
+wordsplit_getwords (wordsplit_t *ws, size_t *wordc, char ***wordv)
+{
+  wordsplit_get_words (ws, wordc, wordv);
+}
+
+int wordsplit_append (wordsplit_t *wsp, int argc, char **argv);
 
 int wordsplit_c_unquote_char (int c);
 int wordsplit_c_quote_char (int c);
-size_t wordsplit_c_quoted_length (const char *str, int quote_hex,
-                                 int *quote);
-void wordsplit_general_unquote_copy (char *dst, const char *src, size_t n,
-                                    const char *escapable);
-void wordsplit_sh_unquote_copy (char *dst, const char *src, size_t n);
-void wordsplit_c_unquote_copy (char *dst, const char *src, size_t n);
+size_t wordsplit_c_quoted_length (const char *str, int quote_hex, int *quote);
 void wordsplit_c_quote_copy (char *dst, const char *src, int quote_hex);
 
-void wordsplit_perror (struct wordsplit *ws);
-const char *wordsplit_strerror (struct wordsplit *ws);
+void wordsplit_perror (wordsplit_t *ws);
+const char *wordsplit_strerror (wordsplit_t *ws);
 
+void wordsplit_clearerr (wordsplit_t *ws);
 
 #endif
index d03eab65d31aa4be7682e9c0271b5793d8227698..4ef7f9ab13bcf8483050c932110c88f7ae9c75a7 160000 (submodule)
--- a/paxutils
+++ b/paxutils
@@ -1 +1 @@
-Subproject commit d03eab65d31aa4be7682e9c0271b5793d8227698
+Subproject commit 4ef7f9ab13bcf8483050c932110c88f7ae9c75a7