"Fossies" - the Fresh Open Source Software Archive

Member "s-nail-14.9.11/cmd-tab.c" (8 Aug 2018, 29238 Bytes) of package /linux/misc/s-nail-14.9.11.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "cmd-tab.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 14.9.10_vs_14.9.11.

    1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
    2  *@ n_cmd_firstfit(): the table of commands + `help' and `list'.
    3  *@ And n_cmd_arg_parse(), the (new) argument list parser. TODO this is
    4  *@ TODO too stupid yet, however: it should fully support subcommands, too, so
    5  *@ TODO that, e.g., "vexpr regex" arguments can be fully prepared by the
    6  *@ TODO generic parser.  But at least a bit.
    7  *@ TODO See cmd-tab.h for sort and speedup TODOs.
    8  *
    9  * Copyright (c) 2012 - 2018 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
   10  * SPDX-License-Identifier: BSD-3-Clause TODO ISC
   11  */
   12 /* Command table and getrawlist() also:
   13  * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
   14  *
   15  * Copyright (c) 1980, 1993
   16  *      The Regents of the University of California.  All rights reserved.
   17  *
   18  * Redistribution and use in source and binary forms, with or without
   19  * modification, are permitted provided that the following conditions
   20  * are met:
   21  * 1. Redistributions of source code must retain the above copyright
   22  *    notice, this list of conditions and the following disclaimer.
   23  * 2. Redistributions in binary form must reproduce the above copyright
   24  *    notice, this list of conditions and the following disclaimer in the
   25  *    documentation and/or other materials provided with the distribution.
   26  * 3. Neither the name of the University nor the names of its contributors
   27  *    may be used to endorse or promote products derived from this software
   28  *    without specific prior written permission.
   29  *
   30  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
   31  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   32  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   33  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
   34  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   35  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
   36  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
   37  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   38  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
   39  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   40  * SUCH DAMAGE.
   41  */
   42 #undef n_FILE
   43 #define n_FILE cmd_tab
   44 
   45 #ifndef HAVE_AMALGAMATION
   46 # include "nail.h"
   47 #endif
   48 
   49 /* Create a multiline info string about all known additional infos for lcp */
   50 #ifdef HAVE_DOCSTRINGS
   51 static char const *a_ctab_cmdinfo(struct n_cmd_desc const *cdp);
   52 #endif
   53 
   54 /* Print a list of all commands */
   55 static int a_ctab_c_list(void *vp);
   56 
   57 static int a_ctab__pcmd_cmp(void const *s1, void const *s2);
   58 
   59 /* `help' / `?' command */
   60 static int a_ctab_c_help(void *vp);
   61 
   62 /* List of all commands; but first their n_cmd_arg_desc instances */
   63 #include "cmd-tab.h"
   64 static struct n_cmd_desc const a_ctab_ctable[] = {
   65 #include "cmd-tab.h"
   66 };
   67 
   68 /* And a list of things which are special to the lexer in go.c, so that we can
   69  * provide help and list them.
   70  * This cross-file relationship is a bit unfortunate.. */
   71 #ifdef HAVE_DOCSTRINGS
   72 # define DS(S) , S
   73 #else
   74 # define DS(S)
   75 #endif
   76 static struct n_cmd_desc const a_ctab_ctable_plus[] = {
   77    { n_ns, (int(*)(void*))-1, n_CMD_ARG_TYPE_STRING, 0, 0, NULL
   78       DS(N_("Comment command: ignore remaining (continuable) line")) },
   79    { n_hy, (int(*)(void*))-1, n_CMD_ARG_TYPE_WYSH, 0, 0, NULL
   80       DS(N_("Print out the preceding message")) }
   81 };
   82 #undef DS
   83 
   84 #ifdef HAVE_DOCSTRINGS
   85 static char const *
   86 a_ctab_cmdinfo(struct n_cmd_desc const *cdp){
   87    struct n_string rvb, *rv;
   88    char const *cp;
   89    NYD2_ENTER;
   90 
   91    rv = n_string_creat_auto(&rvb);
   92    rv = n_string_reserve(rv, 80);
   93 
   94    switch(cdp->cd_caflags & n_CMD_ARG_TYPE_MASK){
   95    case n_CMD_ARG_TYPE_MSGLIST:
   96       cp = N_("message-list");
   97       break;
   98    case n_CMD_ARG_TYPE_NDMLIST:
   99       cp = N_("message-list (without default)");
  100       break;
  101    case n_CMD_ARG_TYPE_STRING:
  102    case n_CMD_ARG_TYPE_RAWDAT:
  103       cp = N_("string data");
  104       break;
  105    case n_CMD_ARG_TYPE_RAWLIST:
  106       cp = N_("old-style quoting");
  107       break;
  108    case n_CMD_ARG_TYPE_WYRA:
  109       cp = N_("`wysh' for sh(1)ell-style quoting");
  110       break;
  111    case n_CMD_ARG_TYPE_WYSH:
  112       cp = (cdp->cd_minargs == 0 && cdp->cd_maxargs == 0)
  113             ? N_("sh(1)ell-style quoting (takes no arguments)")
  114             : N_("sh(1)ell-style quoting");
  115       break;
  116    default:
  117    case n_CMD_ARG_TYPE_ARG:{
  118       ui32_t flags, xflags;
  119       size_t i;
  120       struct n_cmd_arg_desc const *cadp;
  121 
  122       rv = n_string_push_cp(rv, _("argument tokens: "));
  123 
  124       for(cadp = cdp->cd_cadp, i = 0; i < cadp->cad_no; ++i){
  125          xflags = flags = cadp->cad_ent_flags[i][0];
  126 jfakeent:
  127          if(i != 0)
  128             rv = n_string_push_c(rv, ',');
  129 
  130          if(flags & n_CMD_ARG_DESC_OPTION)
  131             rv = n_string_push_c(rv, '[');
  132          if(flags & n_CMD_ARG_DESC_GREEDY)
  133             rv = n_string_push_c(rv, ':');
  134          switch(flags & n__CMD_ARG_DESC_TYPE_MASK){
  135          default:
  136          case n_CMD_ARG_DESC_SHEXP:
  137             rv = n_string_push_cp(rv, _("(shell-)token"));
  138             break;
  139          case n_CMD_ARG_DESC_MSGLIST:
  140             rv = n_string_push_cp(rv, _("(shell-)msglist"));
  141             break;
  142          case n_CMD_ARG_DESC_NDMSGLIST:
  143             rv = n_string_push_cp(rv, _("(shell-)msglist (no default)"));
  144             break;
  145          case n_CMD_ARG_DESC_MSGLIST_AND_TARGET:
  146             rv = n_string_push_cp(rv, _("(shell-)msglist"));
  147             ++i;
  148             xflags = n_CMD_ARG_DESC_SHEXP;
  149          }
  150          if(flags & n_CMD_ARG_DESC_GREEDY)
  151             rv = n_string_push_c(rv, ':');
  152          if(flags & n_CMD_ARG_DESC_OPTION)
  153             rv = n_string_push_c(rv, ']');
  154 
  155          if(xflags != flags){
  156             flags = xflags;
  157             goto jfakeent;
  158          }
  159       }
  160       cp = NULL;
  161       }break;
  162    }
  163    if(cp != NULL)
  164       rv = n_string_push_cp(rv, V_(cp));
  165 
  166    /* Note: on updates, change the manual! */
  167    if(cdp->cd_caflags & n_CMD_ARG_L)
  168       rv = n_string_push_cp(rv, _(" | `local'"));
  169    if(cdp->cd_caflags & n_CMD_ARG_V)
  170       rv = n_string_push_cp(rv, _(" | `vput'"));
  171    if(cdp->cd_caflags & n_CMD_ARG_EM)
  172       rv = n_string_push_cp(rv, _(" | *!*"));
  173 
  174    if(cdp->cd_caflags & n_CMD_ARG_A)
  175       rv = n_string_push_cp(rv, _(" | needs-box"));
  176 
  177    if(cdp->cd_caflags & (n_CMD_ARG_I | n_CMD_ARG_M | n_CMD_ARG_X)){
  178       rv = n_string_push_cp(rv, _(" | ok:"));
  179       if(cdp->cd_caflags & n_CMD_ARG_I)
  180          rv = n_string_push_cp(rv, _(" batch/interactive"));
  181       if(cdp->cd_caflags & n_CMD_ARG_M)
  182          rv = n_string_push_cp(rv, _(" send-mode"));
  183       if(cdp->cd_caflags & n_CMD_ARG_X)
  184          rv = n_string_push_cp(rv, _(" subprocess"));
  185    }
  186 
  187    if(cdp->cd_caflags & (n_CMD_ARG_R | n_CMD_ARG_S)){
  188       rv = n_string_push_cp(rv, _(" | not ok:"));
  189       if(cdp->cd_caflags & n_CMD_ARG_R)
  190          rv = n_string_push_cp(rv, _(" compose-mode"));
  191       if(cdp->cd_caflags & n_CMD_ARG_S)
  192          rv = n_string_push_cp(rv, _(" startup"));
  193    }
  194 
  195    if(cdp->cd_caflags & n_CMD_ARG_G)
  196       rv = n_string_push_cp(rv, _(" | gabby"));
  197 
  198    cp = n_string_cp(rv);
  199    NYD2_LEAVE;
  200    return cp;
  201 }
  202 #endif /* HAVE_DOCSTRINGS */
  203 
  204 static int
  205 a_ctab_c_list(void *vp){
  206    FILE *fp;
  207    struct n_cmd_desc const **cdpa, *cdp, **cdpa_curr;
  208    size_t i, l, scrwid;
  209    NYD_ENTER;
  210 
  211    i = n_NELEM(a_ctab_ctable) + n_NELEM(a_ctab_ctable_plus) +1;
  212    cdpa = n_autorec_alloc(sizeof(cdp) * i);
  213 
  214    for(i = 0; i < n_NELEM(a_ctab_ctable); ++i)
  215       cdpa[i] = &a_ctab_ctable[i];
  216    for(l = 0; l < n_NELEM(a_ctab_ctable_plus); ++i, ++l)
  217       cdpa[i] = &a_ctab_ctable_plus[l];
  218    cdpa[i] = NULL;
  219 
  220    if(*(void**)vp == NULL)
  221       qsort(cdpa, i, sizeof(*cdpa), &a_ctab__pcmd_cmp);
  222 
  223    if((fp = Ftmp(NULL, "list", OF_RDWR | OF_UNLINK | OF_REGISTER)) == NULL)
  224       fp = n_stdout;
  225 
  226    scrwid = n_SCRNWIDTH_FOR_LISTS;
  227 
  228    fprintf(fp, _("Commands are:\n"));
  229    l = 1;
  230    for(i = 0, cdpa_curr = cdpa; (cdp = *cdpa_curr++) != NULL;){
  231       char const *pre, *suf;
  232 
  233       if(cdp->cd_func == NULL)
  234          pre = "[", suf = "]";
  235       else
  236          pre = suf = n_empty;
  237 
  238 #ifdef HAVE_DOCSTRINGS
  239       if(n_poption & n_PO_D_V){
  240          fprintf(fp, "%s%s%s\n", pre, cdp->cd_name, suf);
  241          ++l;
  242          fprintf(fp, "  : %s\n", V_(cdp->cd_doc));
  243          ++l;
  244          fprintf(fp, "  : %s\n", a_ctab_cmdinfo(cdp));
  245          ++l;
  246       }else
  247 #endif
  248            {
  249          size_t j;
  250 
  251          j = strlen(cdp->cd_name);
  252          if(*pre != '\0')
  253             j += 2;
  254 
  255          if((i += j + 2) > scrwid){
  256             i = j;
  257             fprintf(fp, "\n");
  258             ++l;
  259          }
  260          fprintf(fp, (*cdpa_curr != NULL ? "%s%s%s, " : "%s%s%s\n"),
  261             pre, cdp->cd_name, suf);
  262       }
  263    }
  264 
  265    if(fp != n_stdout){
  266       page_or_print(fp, l);
  267       Fclose(fp);
  268    }
  269    NYD_LEAVE;
  270    return 0;
  271 }
  272 
  273 static int
  274 a_ctab__pcmd_cmp(void const *s1, void const *s2){
  275    struct n_cmd_desc const * const *cdpa1, * const *cdpa2;
  276    int rv;
  277    NYD2_ENTER;
  278 
  279    cdpa1 = s1;
  280    cdpa2 = s2;
  281    rv = strcmp((*cdpa1)->cd_name, (*cdpa2)->cd_name);
  282    NYD2_LEAVE;
  283    return rv;
  284 }
  285 
  286 static int
  287 a_ctab_c_help(void *vp){
  288    int rv;
  289    char const *arg;
  290    NYD_ENTER;
  291 
  292    /* Help for a single command? */
  293    if((arg = *(char const**)vp) != NULL){
  294       struct n_cmd_desc const *cdp, *cdp_max;
  295       struct str const *alias_exp;
  296       char const *alias_name, *aepx;
  297 
  298       /* Aliases take precedence.
  299        * Avoid self-recursion; since a commandalias can shadow a command of
  300        * equal name allow one level of expansion to return an equal result:
  301        * "commandalias q q;commandalias x q;x" should be "x->q->q->quit" */
  302       alias_name = NULL;
  303       while((aepx = n_commandalias_exists(arg, &alias_exp)) != NULL &&
  304             (alias_name == NULL || strcmp(alias_name, aepx))){
  305          alias_name = aepx;
  306          fprintf(n_stdout, "%s -> ", arg);
  307          arg = alias_exp->s;
  308       }
  309 
  310       cdp_max = &(cdp = a_ctab_ctable)[n_NELEM(a_ctab_ctable)];
  311 jredo:
  312       for(; cdp < cdp_max; ++cdp){
  313          if(is_prefix(arg, cdp->cd_name)){
  314             fputs(arg, n_stdout);
  315             if(strcmp(arg, cdp->cd_name))
  316                fprintf(n_stdout, " (%s)", cdp->cd_name);
  317          }else
  318             continue;
  319 
  320 #ifdef HAVE_DOCSTRINGS
  321          fprintf(n_stdout, ": %s", V_(cdp->cd_doc));
  322          if(n_poption & n_PO_D_V)
  323             fprintf(n_stdout, "\n  : %s", a_ctab_cmdinfo(cdp));
  324 #endif
  325          putc('\n', n_stdout);
  326          rv = 0;
  327          goto jleave;
  328       }
  329 
  330       if(cdp_max == &a_ctab_ctable[n_NELEM(a_ctab_ctable)]){
  331          cdp_max = &(cdp =
  332                a_ctab_ctable_plus)[n_NELEM(a_ctab_ctable_plus)];
  333          goto jredo;
  334       }
  335 
  336       if(alias_name != NULL){
  337          fprintf(n_stdout, "%s\n", n_shexp_quote_cp(arg, TRU1));
  338          rv = 0;
  339       }else{
  340          n_err(_("Unknown command: `%s'\n"), arg);
  341          rv = 1;
  342       }
  343    }else{
  344       /* Very ugly, but take care for compiler supported string lengths :( */
  345 #ifdef HAVE_UISTRINGS
  346       fputs(n_progname, n_stdout);
  347       fputs(_(
  348          " commands -- <msglist> denotes message specification tokens,\n"
  349          "e.g., 1-5, :n or . (current, the \"dot\"), separated by *ifs*:\n"),
  350          n_stdout);
  351       fputs(_(
  352 "\n"
  353 "type <msglist>         type (`print') messages (honour `headerpick' etc.)\n"
  354 "Type <msglist>         like `type' but always show all headers\n"
  355 "next                   goto and type next message\n"
  356 "headers                header summary ... for messages surrounding \"dot\"\n"
  357 "search <msglist>       ... for the given list (alias for `from')\n"
  358 "delete <msglist>       delete messages (can be `undelete'd)\n"),
  359          n_stdout);
  360 
  361       fputs(_(
  362 "\n"
  363 "save <msglist> folder  append messages to folder and mark as saved\n"
  364 "copy <msglist> folder  like `save', but do not mark them (`move' moves)\n"
  365 "write <msglist> file   write message contents to file (prompts for parts)\n"
  366 "Reply <msglist>        reply to message sender(s) only\n"
  367 "reply <msglist>        like `Reply', but address all recipients\n"
  368 "Lreply <msglist>       forced mailing list `reply' (see `mlist')\n"),
  369          n_stdout);
  370 
  371       fputs(_(
  372 "\n"
  373 "mail <recipients>      compose a mail for the given recipients\n"
  374 "file folder            change to another mailbox\n"
  375 "File folder            like `file', but open readonly\n"
  376 "quit                   quit and apply changes to the current mailbox\n"
  377 "xit or exit            like `quit', but discard changes\n"
  378 "!shell command         shell escape\n"
  379 "list [<anything>]      all available commands [in search order]\n"),
  380          n_stdout);
  381 #endif /* HAVE_UISTRINGS */
  382 
  383       rv = (ferror(n_stdout) != 0);
  384    }
  385 jleave:
  386    NYD_LEAVE;
  387    return rv;
  388 }
  389 
  390 FL char const *
  391 n_cmd_isolate(char const *cmd){
  392    NYD2_ENTER;
  393    while(*cmd != '\0' &&
  394          strchr("\\!~|? \t0123456789&%@$^.:/-+*'\",;(`", *cmd) == NULL)
  395       ++cmd;
  396    NYD2_LEAVE;
  397    return n_UNCONST(cmd);
  398 }
  399 
  400 FL struct n_cmd_desc const *
  401 n_cmd_firstfit(char const *cmd){ /* TODO *hashtable*! linear list search!!! */
  402    struct n_cmd_desc const *cdp;
  403    NYD2_ENTER;
  404 
  405    for(cdp = a_ctab_ctable; cdp < &a_ctab_ctable[n_NELEM(a_ctab_ctable)]; ++cdp)
  406       if(*cmd == *cdp->cd_name && cdp->cd_func != NULL &&
  407             is_prefix(cmd, cdp->cd_name))
  408          goto jleave;
  409    cdp = NULL;
  410 jleave:
  411    NYD2_LEAVE;
  412    return cdp;
  413 }
  414 
  415 FL struct n_cmd_desc const *
  416 n_cmd_default(void){
  417    struct n_cmd_desc const *cdp;
  418    NYD2_ENTER;
  419 
  420    cdp = &a_ctab_ctable[0];
  421    NYD2_LEAVE;
  422    return cdp;
  423 }
  424 
  425 FL bool_t
  426 n_cmd_arg_parse(struct n_cmd_arg_ctx *cacp){
  427    struct n_cmd_arg ncap, *lcap, *target_argp, **target_argpp, *cap;
  428    struct str shin_orig, shin;
  429    bool_t stoploop, greedyjoin;
  430    void const *cookie;
  431    size_t cad_idx, parsed_args;
  432    struct n_cmd_arg_desc const *cadp;
  433    NYD_ENTER;
  434 
  435    assert(cacp->cac_inlen == 0 || cacp->cac_indat != NULL);
  436    assert(cacp->cac_desc->cad_no > 0);
  437 #ifdef HAVE_DEBUG
  438    /* C99 */{
  439       bool_t opt_seen = FAL0;
  440 
  441       for(cadp = cacp->cac_desc, cad_idx = 0;
  442             cad_idx < cadp->cad_no; ++cad_idx){
  443          assert(cadp->cad_ent_flags[cad_idx][0] & n__CMD_ARG_DESC_TYPE_MASK);
  444 
  445          /* TODO n_CMD_ARG_DESC_MSGLIST+ may only be used as the last entry */
  446          assert(!(cadp->cad_ent_flags[cad_idx][0] & n_CMD_ARG_DESC_MSGLIST) ||
  447             cad_idx + 1 == cadp->cad_no);
  448          assert(!(cadp->cad_ent_flags[cad_idx][0] &
  449                n_CMD_ARG_DESC_NDMSGLIST) || cad_idx + 1 == cadp->cad_no);
  450          assert(!(cadp->cad_ent_flags[cad_idx][0] &
  451                n_CMD_ARG_DESC_MSGLIST_AND_TARGET) ||
  452             cad_idx + 1 == cadp->cad_no);
  453 
  454          assert(!opt_seen ||
  455             (cadp->cad_ent_flags[cad_idx][0] & n_CMD_ARG_DESC_OPTION));
  456          if(cadp->cad_ent_flags[cad_idx][0] & n_CMD_ARG_DESC_OPTION)
  457             opt_seen = TRU1;
  458          assert(!(cadp->cad_ent_flags[cad_idx][0] & n_CMD_ARG_DESC_GREEDY) ||
  459             cad_idx + 1 == cadp->cad_no);
  460 
  461          /* TODO n_CMD_ARG_DESC_MSGLIST+ can only be n_CMD_ARG_DESC_GREEDY.
  462           * TODO And they may not be n_CMD_ARG_DESC_OPTION */
  463          assert(!(cadp->cad_ent_flags[cad_idx][0] & n_CMD_ARG_DESC_MSGLIST) ||
  464             (cadp->cad_ent_flags[cad_idx][0] & n_CMD_ARG_DESC_GREEDY));
  465          assert(!(cadp->cad_ent_flags[cad_idx][0] &
  466                n_CMD_ARG_DESC_NDMSGLIST) ||
  467             (cadp->cad_ent_flags[cad_idx][0] & n_CMD_ARG_DESC_GREEDY));
  468          assert(!(cadp->cad_ent_flags[cad_idx][0] &
  469                n_CMD_ARG_DESC_MSGLIST_AND_TARGET) ||
  470             (cadp->cad_ent_flags[cad_idx][0] & n_CMD_ARG_DESC_GREEDY));
  471 
  472          assert(!(cadp->cad_ent_flags[cad_idx][0] & n_CMD_ARG_DESC_MSGLIST) ||
  473             !(cadp->cad_ent_flags[cad_idx][0] & n_CMD_ARG_DESC_OPTION));
  474          assert(!(cadp->cad_ent_flags[cad_idx][0] &
  475                n_CMD_ARG_DESC_NDMSGLIST) ||
  476             !(cadp->cad_ent_flags[cad_idx][0] & n_CMD_ARG_DESC_OPTION));
  477          assert(!(cadp->cad_ent_flags[cad_idx][0] &
  478                n_CMD_ARG_DESC_MSGLIST_AND_TARGET) ||
  479             !(cadp->cad_ent_flags[cad_idx][0] & n_CMD_ARG_DESC_OPTION));
  480       }
  481    }
  482 #endif /* HAVE_DEBUG */
  483 
  484    n_pstate_err_no = n_ERR_NONE;
  485    shin.s = n_UNCONST(cacp->cac_indat); /* "logical" only */
  486    shin.l = (cacp->cac_inlen == UIZ_MAX ? strlen(shin.s) : cacp->cac_inlen);
  487    shin_orig = shin;
  488    cacp->cac_no = 0;
  489    cacp->cac_arg = lcap = NULL;
  490 
  491    cookie = NULL;
  492    parsed_args = 0;
  493    greedyjoin = FAL0;
  494 
  495    /* TODO We need to test >= 0 in order to deal with MSGLIST arguments, as
  496     * TODO those use getmsglist() and that needs to deal with that situation.
  497     * TODO In the future that should change; see jmsglist_related TODO below */
  498    for(cadp = cacp->cac_desc, cad_idx = 0;
  499          /*shin.l >= 0 &&*/ cad_idx < cadp->cad_no; ++cad_idx){
  500 jredo:
  501       memset(&ncap, 0, sizeof ncap);
  502       ncap.ca_indat = shin.s;
  503       /* >ca_inline once we know */
  504       memcpy(&ncap.ca_ent_flags[0], &cadp->cad_ent_flags[cad_idx][0],
  505          sizeof ncap.ca_ent_flags);
  506       target_argpp = NULL;
  507       stoploop = FAL0;
  508 
  509       switch(ncap.ca_ent_flags[0] & n__CMD_ARG_DESC_TYPE_MASK){
  510       default:
  511       case n_CMD_ARG_DESC_SHEXP:{
  512          struct n_string shou, *shoup;
  513          enum n_shexp_state shs;
  514          ui32_t addflags;
  515 
  516          if(shin.l == 0) goto jmsglist_related; /* TODO */
  517 
  518          if(cad_idx == cadp->cad_no - 1 ||
  519                (cadp->cad_ent_flags[cad_idx + 1][0] & n_CMD_ARG_DESC_OPTION))
  520             addflags = n_SHEXP_PARSE_META_SEMICOLON;
  521          else
  522             addflags = n_SHEXP_PARSE_NONE;
  523 
  524          shoup = n_string_creat_auto(&shou);
  525          ncap.ca_arg_flags =
  526          shs = n_shexp_parse_token((ncap.ca_ent_flags[1] | addflags |
  527                   n_SHEXP_PARSE_TRIM_SPACE | n_SHEXP_PARSE_LOG),
  528                shoup, &shin,
  529                (ncap.ca_ent_flags[0] & n_CMD_ARG_DESC_GREEDY ? &cookie : NULL));
  530          ncap.ca_inlen = PTR2SIZE(shin.s - ncap.ca_indat);
  531          if((shs & (n_SHEXP_STATE_OUTPUT | n_SHEXP_STATE_ERR_MASK)) ==
  532                n_SHEXP_STATE_OUTPUT){
  533             if((shs & n_SHEXP_STATE_META_SEMICOLON) && shou.s_len == 0)
  534                break;
  535             ncap.ca_arg.ca_str.s = n_string_cp(shoup);
  536             ncap.ca_arg.ca_str.l = shou.s_len;
  537             shoup = n_string_drop_ownership(shoup);
  538          }
  539          n_string_gut(shoup);
  540 
  541          if(shs & n_SHEXP_STATE_ERR_MASK)
  542             goto jerr;
  543          if((shs & n_SHEXP_STATE_STOP) &&
  544                (ncap.ca_ent_flags[0] & n_CMD_ARG_DESC_HONOUR_STOP)){
  545             if(!(shs & n_SHEXP_STATE_OUTPUT))
  546                goto jleave;
  547             stoploop = TRU1;
  548          }else if(!(shs & n_SHEXP_STATE_OUTPUT)) /* XXX Is this right? */
  549             goto jerr;
  550          }break;
  551       case n_CMD_ARG_DESC_MSGLIST_AND_TARGET:
  552          target_argpp = &target_argp;
  553          /* FALLTHRU */
  554       case n_CMD_ARG_DESC_MSGLIST:
  555       case n_CMD_ARG_DESC_NDMSGLIST:
  556          /* TODO _MSGLIST yet at end and greedy only (fast hack).
  557           * TODO And consumes too much memory */
  558          assert(shin.s[shin.l] == '\0');
  559          if(n_getmsglist(shin.s, (ncap.ca_arg.ca_msglist =
  560                   n_autorec_calloc(msgCount +1, sizeof *ncap.ca_arg.ca_msglist)
  561                ), cacp->cac_msgflag, target_argpp) < 0){
  562             n_pstate_err_no = n_ERR_INVAL; /* XXX should come from getmsglist*/
  563             goto jerr;
  564          }
  565 
  566          if(ncap.ca_arg.ca_msglist[0] == 0){
  567             ui32_t e;
  568 
  569             switch(ncap.ca_ent_flags[0] & n__CMD_ARG_DESC_TYPE_MASK){
  570             case n_CMD_ARG_DESC_MSGLIST_AND_TARGET:
  571             case n_CMD_ARG_DESC_MSGLIST:
  572                if((ncap.ca_arg.ca_msglist[0] = first(cacp->cac_msgflag,
  573                      cacp->cac_msgmask)) == 0){
  574                   if(!(n_pstate & (n_PS_HOOK_MASK | n_PS_ROBOT)) ||
  575                         (n_poption & n_PO_D_V))
  576                      n_err(_("No applicable messages\n"));
  577 
  578                   e = n_CMD_ARG_DESC_TO_ERRNO(ncap.ca_ent_flags[0]);
  579                   if(e == 0)
  580                      e = n_ERR_NODATA;
  581                   n_pstate_err_no = e;
  582                   goto jerr;
  583                }
  584                ncap.ca_arg.ca_msglist[1] = 0;
  585 
  586                /* TODO For the MSGLIST_AND_TARGET case an entirely empty input
  587                 * TODO results in no _TARGET argument: ensure it is there! */
  588                if(target_argpp != NULL && (cap = *target_argpp) == NULL){
  589                   cap = n_autorec_calloc(1, sizeof *cap);
  590                   cap->ca_arg.ca_str.s = n_UNCONST(n_empty);
  591                   *target_argpp = cap;
  592                }
  593                /* FALLTHRU */
  594             default:
  595                break;
  596             }
  597          }else if((ncap.ca_ent_flags[0] & n_CMD_ARG_DESC_MSGLIST_NEEDS_SINGLE
  598                ) && ncap.ca_arg.ca_msglist[1] != 0){
  599             if(!(n_pstate & (n_PS_HOOK_MASK | n_PS_ROBOT)) ||
  600                   (n_poption & n_PO_D_V))
  601                n_err(_("Cannot specify multiple messages at once\n"));
  602             n_pstate_err_no = n_ERR_NOTSUP;
  603             goto jerr;
  604          }
  605          shin.l = 0;
  606          stoploop = TRU1; /* XXX Asserted to be last above! */
  607          break;
  608       }
  609       ++parsed_args;
  610 
  611       if(greedyjoin == TRU1){ /* TODO speed this up! */
  612          char *cp;
  613          size_t i;
  614 
  615          assert((ncap.ca_ent_flags[0] & n__CMD_ARG_DESC_TYPE_MASK
  616             ) != n_CMD_ARG_DESC_MSGLIST);
  617          assert(lcap != NULL);
  618          assert(target_argpp == NULL);
  619          i = lcap->ca_arg.ca_str.l;
  620          lcap->ca_arg.ca_str.l += 1 + ncap.ca_arg.ca_str.l;
  621          cp = n_autorec_alloc(lcap->ca_arg.ca_str.l +1);
  622          memcpy(cp, lcap->ca_arg.ca_str.s, i);
  623          lcap->ca_arg.ca_str.s = cp;
  624          cp[i++] = ' ';
  625          memcpy(&cp[i], ncap.ca_arg.ca_str.s, ncap.ca_arg.ca_str.l +1);
  626       }else{
  627          cap = n_autorec_alloc(sizeof *cap);
  628          memcpy(cap, &ncap, sizeof ncap);
  629          if(lcap == NULL)
  630             cacp->cac_arg = cap;
  631          else
  632             lcap->ca_next = cap;
  633          lcap = cap;
  634          ++cacp->cac_no;
  635 
  636          if(target_argpp != NULL){
  637             lcap->ca_next = cap = *target_argpp;
  638             if(cap != NULL){
  639                lcap = cap;
  640                ++cacp->cac_no;
  641             }
  642          }
  643       }
  644 
  645       if(stoploop)
  646          goto jleave;
  647 
  648       if((shin.l > 0 || cookie != NULL) &&
  649             (ncap.ca_ent_flags[0] & n_CMD_ARG_DESC_GREEDY)){
  650          if(!greedyjoin)
  651             greedyjoin = ((ncap.ca_ent_flags[0] & n_CMD_ARG_DESC_GREEDY_JOIN) &&
  652                      (ncap.ca_ent_flags[0] & n_CMD_ARG_DESC_SHEXP))
  653                   ? TRU1 : TRUM1;
  654          goto jredo;
  655       }
  656    }
  657 
  658 jmsglist_related:
  659    if(cad_idx < cadp->cad_no &&
  660          !(cadp->cad_ent_flags[cad_idx][0] & n_CMD_ARG_DESC_OPTION))
  661       goto jerr;
  662 
  663    lcap = (struct n_cmd_arg*)-1;
  664 jleave:
  665    NYD_LEAVE;
  666    return (lcap != NULL);
  667 
  668 jerr:
  669    if(n_pstate_err_no == n_ERR_NONE){
  670       n_pstate_err_no = n_ERR_INVAL;
  671 
  672       if(!(n_pstate & (n_PS_HOOK_MASK | n_PS_ROBOT)) ||
  673             (n_poption & n_PO_D_V)){
  674          size_t i;
  675 
  676          for(i = 0; (i < cadp->cad_no &&
  677                !(cadp->cad_ent_flags[i][0] & n_CMD_ARG_DESC_OPTION)); ++i)
  678             ;
  679 
  680          n_err(_("`%s': parsing stopped after %" PRIuZ " arguments "
  681                "(need %" PRIuZ "%s)\n"
  682                "     Input: %.*s\n"
  683                "   Stopped: %.*s\n"),
  684             cadp->cad_name, parsed_args,
  685                i, (i == cadp->cad_no ? n_empty : "+"),
  686             (int)shin_orig.l, shin_orig.s,
  687             (int)shin.l, shin.s);
  688       }
  689    }
  690    lcap = NULL;
  691    goto jleave;
  692 }
  693 
  694 FL void *
  695 n_cmd_arg_save_to_heap(struct n_cmd_arg_ctx const *cacp){
  696    struct n_cmd_arg *ncap;
  697    struct n_cmd_arg_ctx *ncacp;
  698    char *buf;
  699    struct n_cmd_arg const *cap;
  700    size_t len, i;
  701    NYD2_ENTER;
  702 
  703    /* For simplicity, save it all in once chunk, so that it can be thrown away
  704     * with a simple n_free() from whoever is concerned */
  705    len = sizeof *cacp;
  706    for(cap = cacp->cac_arg; cap != NULL; cap = cap->ca_next){
  707       i = cap->ca_arg.ca_str.l +1;
  708       i = n_ALIGN(i);
  709       len += sizeof(*cap) + i;
  710    }
  711    if(cacp->cac_vput != NULL)
  712       len += strlen(cacp->cac_vput) +1;
  713 
  714    ncacp = n_alloc(len);
  715    *ncacp = *cacp;
  716    buf = (char*)&ncacp[1];
  717 
  718    for(ncap = NULL, cap = cacp->cac_arg; cap != NULL; cap = cap->ca_next){
  719       void *vp;
  720 
  721       vp = buf;
  722       DBG( memset(vp, 0, sizeof *ncap); )
  723 
  724       if(ncap == NULL)
  725          ncacp->cac_arg = vp;
  726       else
  727          ncap->ca_next = vp;
  728       ncap = vp;
  729       ncap->ca_next = NULL;
  730       ncap->ca_ent_flags[0] = cap->ca_ent_flags[0];
  731       ncap->ca_ent_flags[1] = cap->ca_ent_flags[1];
  732       ncap->ca_arg_flags = cap->ca_arg_flags;
  733       memcpy(ncap->ca_arg.ca_str.s = (char*)&ncap[1], cap->ca_arg.ca_str.s,
  734             (ncap->ca_arg.ca_str.l = i = cap->ca_arg.ca_str.l) +1);
  735 
  736       i = n_ALIGN(i);
  737       buf += sizeof(*ncap) + i;
  738    }
  739 
  740    if(cacp->cac_vput != NULL){
  741       ncacp->cac_vput = buf;
  742       memcpy(buf, cacp->cac_vput, strlen(cacp->cac_vput) +1);
  743    }else
  744       ncacp->cac_vput = NULL;
  745    NYD2_LEAVE;
  746    return ncacp;
  747 }
  748 
  749 FL struct n_cmd_arg_ctx *
  750 n_cmd_arg_restore_from_heap(void *vp){
  751    struct n_cmd_arg *cap, *ncap;
  752    struct n_cmd_arg_ctx *cacp, *rv;
  753    NYD2_ENTER;
  754 
  755    rv = n_autorec_alloc(sizeof *rv);
  756    cacp = vp;
  757    *rv = *cacp;
  758 
  759    for(ncap = NULL, cap = cacp->cac_arg; cap != NULL; cap = cap->ca_next){
  760       vp = n_autorec_alloc(sizeof(*ncap) + cap->ca_arg.ca_str.l +1);
  761       DBG( memset(vp, 0, sizeof *ncap); )
  762 
  763       if(ncap == NULL)
  764          rv->cac_arg = vp;
  765       else
  766          ncap->ca_next = vp;
  767       ncap = vp;
  768       ncap->ca_next = NULL;
  769       ncap->ca_ent_flags[0] = cap->ca_ent_flags[0];
  770       ncap->ca_ent_flags[1] = cap->ca_ent_flags[1];
  771       ncap->ca_arg_flags = cap->ca_arg_flags;
  772       memcpy(ncap->ca_arg.ca_str.s = (char*)&ncap[1], cap->ca_arg.ca_str.s,
  773             (ncap->ca_arg.ca_str.l = cap->ca_arg.ca_str.l) +1);
  774    }
  775 
  776    if(cacp->cac_vput != NULL)
  777       rv->cac_vput = savestr(cacp->cac_vput);
  778    NYD2_LEAVE;
  779    return rv;
  780 }
  781 
  782 FL int
  783 getrawlist(bool_t wysh, char **res_dat, size_t res_size,
  784       char const *line, size_t linesize){
  785    int res_no;
  786    NYD_ENTER;
  787 
  788    n_pstate &= ~n_PS_ARGLIST_MASK;
  789 
  790    if(res_size == 0){
  791       res_no = -1;
  792       goto jleave;
  793    }else if(UICMP(z, res_size, >, INT_MAX))
  794       res_size = INT_MAX;
  795    else
  796       --res_size;
  797    res_no = 0;
  798 
  799    if(!wysh){
  800       /* And assuming result won't grow input */
  801       char c2, c, quotec, *cp2, *linebuf;
  802 
  803       linebuf = n_lofi_alloc(linesize);
  804 
  805       for(;;){
  806          for(; blankchar(*line); ++line)
  807             ;
  808          if(*line == '\0')
  809             break;
  810 
  811          if(UICMP(z, res_no, >=, res_size)){
  812             n_err(_("Too many input tokens for result storage\n"));
  813             res_no = -1;
  814             break;
  815          }
  816 
  817          cp2 = linebuf;
  818          quotec = '\0';
  819 
  820          /* TODO v15: complete switch in order mirror known behaviour */
  821          while((c = *line++) != '\0'){
  822             if(quotec != '\0'){
  823                if(c == quotec){
  824                   quotec = '\0';
  825                   continue;
  826                }else if(c == '\\'){
  827                   if((c2 = *line++) == quotec)
  828                      c = c2;
  829                   else
  830                      --line;
  831                }
  832             }else if(c == '"' || c == '\''){
  833                quotec = c;
  834                continue;
  835             }else if(c == '\\'){
  836                if((c2 = *line++) != '\0')
  837                   c = c2;
  838                else
  839                   --line;
  840             }else if(blankchar(c))
  841                break;
  842             *cp2++ = c;
  843          }
  844 
  845          res_dat[res_no++] = savestrbuf(linebuf, PTR2SIZE(cp2 - linebuf));
  846          if(c == '\0')
  847             break;
  848       }
  849 
  850       n_lofi_free(linebuf);
  851    }else{
  852       /* sh(1) compat mode.  Prepare shell token-wise */
  853       struct n_string store;
  854       struct str input;
  855       void const *cookie;
  856 
  857       n_string_creat_auto(&store);
  858       input.s = n_UNCONST(line);
  859       input.l = linesize;
  860       cookie = NULL;
  861 
  862       for(;;){
  863          if(UICMP(z, res_no, >=, res_size)){
  864             n_err(_("Too many input tokens for result storage\n"));
  865             res_no = -1;
  866             break;
  867          }
  868 
  869          /* C99 */{
  870             enum n_shexp_state shs;
  871 
  872             if((shs = n_shexp_parse_token((n_SHEXP_PARSE_LOG |
  873                         (cookie == NULL ? n_SHEXP_PARSE_TRIM_SPACE : 0) |
  874                         /* TODO not here in old style n_SHEXP_PARSE_IFS_VAR |*/
  875                         n_SHEXP_PARSE_META_SEMICOLON),
  876                      &store, &input, &cookie)
  877                   ) & n_SHEXP_STATE_ERR_MASK){
  878                /* Simply ignore Unicode error, just keep the normalized \[Uu] */
  879                if((shs & n_SHEXP_STATE_ERR_MASK) != n_SHEXP_STATE_ERR_UNICODE){
  880                   res_no = -1;
  881                   break;
  882                }
  883             }
  884 
  885             if(shs & n_SHEXP_STATE_OUTPUT){
  886                res_dat[res_no++] = n_string_cp(&store);
  887                n_string_drop_ownership(&store);
  888             }
  889 
  890             if(shs & n_SHEXP_STATE_STOP)
  891                break;
  892          }
  893       }
  894 
  895       n_string_gut(&store);
  896    }
  897 
  898    if(res_no >= 0)
  899       res_dat[(size_t)res_no] = NULL;
  900 jleave:
  901    NYD_LEAVE;
  902    return res_no;
  903 }
  904 
  905 /* s-it-mode */