Bump S-nail v14.9.0-pre4, 2017-04-13
[s-mailx.git] / cmd-arg.c
blob4bebf2cb66040d788674c7a2e0487cd2be4c84ae
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Command argument list parser(s). TODO partial: no msglist in here, etc.
4 * Copyright (c) 2016 - 2017 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
6 * Permission to use, copy, modify, and/or distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 /* getrawlist(): */
20 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
21 * Copyright (c) 2012 - 2017 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
24 * Copyright (c) 1980, 1993
25 * The Regents of the University of California. All rights reserved.
27 * Redistribution and use in source and binary forms, with or without
28 * modification, are permitted provided that the following conditions
29 * are met:
30 * 1. Redistributions of source code must retain the above copyright
31 * notice, this list of conditions and the following disclaimer.
32 * 2. Redistributions in binary form must reproduce the above copyright
33 * notice, this list of conditions and the following disclaimer in the
34 * documentation and/or other materials provided with the distribution.
35 * 3. Neither the name of the University nor the names of its contributors
36 * may be used to endorse or promote products derived from this software
37 * without specific prior written permission.
39 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
40 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
41 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
42 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
43 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
44 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
45 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
46 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
47 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
48 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
49 * SUCH DAMAGE.
51 #undef n_FILE
52 #define n_FILE cmd_arg
54 #ifndef HAVE_AMALGAMATION
55 # include "nail.h"
56 #endif
58 FL int
59 getrawlist(bool_t wysh, char **res_dat, size_t res_size,
60 char const *line, size_t linesize){
61 int res_no;
62 NYD_ENTER;
64 n_pstate &= ~n_PS_ARGLIST_MASK;
66 if(res_size == 0){
67 res_no = -1;
68 goto jleave;
69 }else if(UICMP(z, res_size, >, INT_MAX))
70 res_size = INT_MAX;
71 else
72 --res_size;
73 res_no = 0;
75 if(!wysh){
76 /* And assuming result won't grow input */
77 char c2, c, quotec, *cp2, *linebuf;
79 linebuf = n_lofi_alloc(linesize);
81 for(;;){
82 for(; blankchar(*line); ++line)
84 if(*line == '\0')
85 break;
87 if(UICMP(z, res_no, >=, res_size)){
88 n_err(_("Too many input tokens for result storage\n"));
89 res_no = -1;
90 break;
93 cp2 = linebuf;
94 quotec = '\0';
96 /* TODO v15: complete switch in order mirror known behaviour */
97 while((c = *line++) != '\0'){
98 if(quotec != '\0'){
99 if(c == quotec){
100 quotec = '\0';
101 continue;
102 }else if(c == '\\'){
103 if((c2 = *line++) == quotec)
104 c = c2;
105 else
106 --line;
108 }else if(c == '"' || c == '\''){
109 quotec = c;
110 continue;
111 }else if(c == '\\'){
112 if((c2 = *line++) != '\0')
113 c = c2;
114 else
115 --line;
116 }else if(blankchar(c))
117 break;
118 *cp2++ = c;
121 res_dat[res_no++] = savestrbuf(linebuf, PTR2SIZE(cp2 - linebuf));
122 if(c == '\0')
123 break;
126 n_lofi_free(linebuf);
127 }else{
128 /* sh(1) compat mode. Prepare shell token-wise */
129 struct n_string store;
130 struct str input;
131 void const *cookie;
133 n_string_creat_auto(&store);
134 input.s = n_UNCONST(line);
135 input.l = linesize;
136 cookie = NULL;
138 for(;;){
139 if(cookie == NULL){
140 for(; blankchar(*line); ++line)
142 if(*line == '\0')
143 break;
146 if(UICMP(z, res_no, >=, res_size)){
147 n_err(_("Too many input tokens for result storage\n"));
148 res_no = -1;
149 break;
152 input.l -= PTR2SIZE(line - input.s);
153 input.s = n_UNCONST(line);
154 /* C99 */{
155 enum n_shexp_state shs;
157 if((shs = n_shexp_parse_token((n_SHEXP_PARSE_LOG |
158 n_SHEXP_PARSE_META_SEMICOLON), &store, &input, &cookie)
159 ) & n_SHEXP_STATE_ERR_MASK){
160 /* Simply ignore Unicode error, just keep the normalized \[Uu] */
161 if((shs & n_SHEXP_STATE_ERR_MASK) != n_SHEXP_STATE_ERR_UNICODE){
162 res_no = -1;
163 break;
167 if(shs & n_SHEXP_STATE_OUTPUT){
168 if(shs & n_SHEXP_STATE_CONTROL)
169 n_pstate |= n_PS_WYSHLIST_SAW_CONTROL;
171 res_dat[res_no++] = n_string_cp(&store);
172 n_string_drop_ownership(&store);
175 if(shs & n_SHEXP_STATE_STOP)
176 break;
178 line = input.s;
181 n_string_gut(&store);
184 if(res_no >= 0)
185 res_dat[(size_t)res_no] = NULL;
186 jleave:
187 NYD_LEAVE;
188 return res_no;
191 FL bool_t
192 n_cmd_arg_parse(struct n_cmd_arg_ctx *cacp){ /* TODO use this for cmd-tab.h! */
193 struct n_cmd_arg ncap, *lcap;
194 struct str shin_orig, shin;
195 bool_t addca;
196 void const *cookie;
197 size_t cad_no, parsed_args;
198 struct n_cmd_arg_desc const *cadp;
199 NYD_ENTER;
201 assert(cacp->cac_inlen == 0 || cacp->cac_indat != NULL);
202 assert(cacp->cac_desc->cad_no > 0);
203 #ifdef HAVE_DEBUG
204 /* C99 */{
205 bool_t opt_seen = FAL0;
207 for(cadp = cacp->cac_desc, cad_no = 0; cad_no < cadp->cad_no; ++cad_no){
208 assert(cadp->cad_ent_flags[cad_no][0] & n__CMD_ARG_DESC_TYPE_MASK);
209 assert(!opt_seen ||
210 (cadp->cad_ent_flags[cad_no][0] & n_CMD_ARG_DESC_OPTION));
211 if(cadp->cad_ent_flags[cad_no][0] & n_CMD_ARG_DESC_OPTION)
212 opt_seen = TRU1;
213 assert(!(cadp->cad_ent_flags[cad_no][0] & n_CMD_ARG_DESC_GREEDY) ||
214 cad_no + 1 == cadp->cad_no);
217 #endif
219 shin.s = n_UNCONST(cacp->cac_indat); /* "logical" only */
220 shin.l = (cacp->cac_inlen == UIZ_MAX ? strlen(shin.s) : cacp->cac_inlen);
221 shin_orig = shin;
222 cacp->cac_no = 0;
223 cacp->cac_arg = lcap = NULL;
225 cookie = NULL;
226 parsed_args = 0;
227 for(cadp = cacp->cac_desc, cad_no = 0; shin.l > 0 && cad_no < cadp->cad_no;
228 ++cad_no){
229 jredo:
230 memset(&ncap, 0, sizeof ncap);
231 ncap.ca_indat = shin.s;
232 /* >ca_inline once we know */
233 memcpy(&ncap.ca_ent_flags[0], &cadp->cad_ent_flags[cad_no][0],
234 sizeof ncap.ca_ent_flags);
235 addca = FAL0;
237 switch(ncap.ca_ent_flags[0] & n__CMD_ARG_DESC_TYPE_MASK){
238 case n_CMD_ARG_DESC_STRING:{ /* TODO \ escaping? additional type!? */
239 char /*const*/ *cp = shin.s;
240 size_t i = shin.l;
242 while(i > 0 && blankspacechar(*cp))
243 ++cp, --i;
245 ncap.ca_arg.ca_str.s = cp;
246 while(i > 0 && !blankspacechar(*cp))
247 ++cp, --i;
248 ncap.ca_arg.ca_str.s = savestrbuf(ncap.ca_arg.ca_str.s,
249 ncap.ca_arg.ca_str.l = PTR2SIZE(cp - ncap.ca_arg.ca_str.s));
251 while(i > 0 && blankspacechar(*cp))
252 ++cp, --i;
253 ncap.ca_inlen = PTR2SIZE(cp - ncap.ca_indat);
254 shin.s = cp;
255 shin.l = i;
256 addca = TRU1;
257 } break;
258 default:
259 case n_CMD_ARG_DESC_WYSH:{
260 struct n_string shou, *shoup;
261 enum n_shexp_state shs;
263 shoup = n_string_creat_auto(&shou);
264 ncap.ca_arg_flags =
265 shs = n_shexp_parse_token((ncap.ca_ent_flags[1] |
266 n_SHEXP_PARSE_TRIMSPACE | n_SHEXP_PARSE_LOG), shoup, &shin,
267 (ncap.ca_ent_flags[0] & n_CMD_ARG_DESC_GREEDY ? &cookie : NULL));
268 ncap.ca_inlen = PTR2SIZE(shin.s - ncap.ca_indat);
269 if((shs & (n_SHEXP_STATE_OUTPUT | n_SHEXP_STATE_ERR_MASK)) ==
270 n_SHEXP_STATE_OUTPUT){
271 ncap.ca_arg.ca_str.s = n_string_cp(shoup);
272 ncap.ca_arg.ca_str.l = shou.s_len;
273 shoup = n_string_drop_ownership(shoup);
275 n_string_gut(shoup);
277 if(shs & n_SHEXP_STATE_ERR_MASK)
278 goto jerr;
279 if((shs & n_SHEXP_STATE_STOP) &&
280 (ncap.ca_ent_flags[0] & n_CMD_ARG_DESC_HONOUR_STOP)){
281 if(!(shs & n_SHEXP_STATE_OUTPUT))
282 goto jleave;
283 addca = TRUM1;
284 }else
285 addca = TRU1;
286 } break;
288 ++parsed_args;
290 if(addca){
291 struct n_cmd_arg *cap;
293 cap = salloc(sizeof *cap);
294 memcpy(cap, &ncap, sizeof ncap);
295 if(lcap == NULL)
296 cacp->cac_arg = cap;
297 else
298 lcap->ca_next = cap;
299 lcap = cap;
300 ++cacp->cac_no;
302 if(addca == TRUM1)
303 goto jleave;
306 if((shin.l > 0 || cookie != NULL) &&
307 (ncap.ca_ent_flags[0] & n_CMD_ARG_DESC_GREEDY))
308 goto jredo;
311 if(cad_no < cadp->cad_no &&
312 !(cadp->cad_ent_flags[cad_no][0] & n_CMD_ARG_DESC_OPTION))
313 goto jerr;
315 jleave:
316 NYD_LEAVE;
317 return (lcap != NULL);
319 jerr:{
320 size_t i;
322 for(i = 0; (i < cadp->cad_no &&
323 !(cadp->cad_ent_flags[i][0] & n_CMD_ARG_DESC_OPTION)); ++i)
326 n_err(_("`%s': parsing stopped after %" PRIuZ " arguments "
327 "(need %" PRIuZ "%s)\n"
328 " Input: %.*s\n"
329 " Stopped: %.*s\n"),
330 cadp->cad_name, parsed_args, i, (i == cadp->cad_no ? n_empty : "+"),
331 (int)shin_orig.l, shin_orig.s,
332 (int)shin.l, shin.s);
334 lcap = NULL;
335 goto jleave;
338 FL struct n_string *
339 n_cmd_arg_join_greedy(struct n_cmd_arg_ctx const *cacp, struct n_string *store){
340 struct n_cmd_arg *cap;
341 NYD_ENTER;
343 for(cap = cacp->cac_arg;
344 (cap != NULL && !(cap->ca_ent_flags[0] & n_CMD_ARG_DESC_GREEDY));
345 cap = cap->ca_next)
347 /* Can only join strings */
348 assert(cap == NULL ||
349 (cap->ca_ent_flags[0] & (n_CMD_ARG_DESC_STRING | n_CMD_ARG_DESC_WYSH)));
351 while(cap != NULL){
352 store = n_string_push_buf(store,
353 cap->ca_arg.ca_str.s, cap->ca_arg.ca_str.l);
354 if((cap = cap->ca_next) != NULL)
355 store = n_string_push_c(store, ' ');
357 NYD_LEAVE;
358 return store;
361 /* s-it-mode */