"Fossies" - the Fresh Open Source Software Archive

Member "s-nail-14.9.10/cmd-tab.c" (25 Mar 2018, 24353 Bytes) of package /linux/misc/s-nail-14.9.10.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.9_vs_14.9.10.

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