C****Y BSD signal handling! Try improve ^C stability..
[s-mailx.git] / cmd-cnd.c
blobdd967be215b1a5622aeac5dd30dc3514b177102a
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Commands: conditional constructs.
4 * Copyright (c) 2014 - 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 #undef n_FILE
19 #define n_FILE cmd_cnd
21 #ifndef HAVE_AMALGAMATION
22 # include "nail.h"
23 #endif
25 #define a_CCND_IF_ISSKIP() \
26 (n_go_data->gdc_ifcond != NULL &&\
27 (((struct a_ccnd_if_node*)n_go_data->gdc_ifcond)->cin_noop ||\
28 !((struct a_ccnd_if_node*)n_go_data->gdc_ifcond)->cin_go))
30 struct a_ccnd_if_node{
31 struct a_ccnd_if_node *cin_outer;
32 bool_t cin_error; /* Bad expression, skip entire if..endif */
33 bool_t cin_noop; /* Outer stack !cin_go, entirely no-op */
34 bool_t cin_go; /* Green light */
35 bool_t cin_else; /* In `else' clause */
36 ui8_t cin__dummy[4];
39 struct a_ccnd_if_ctx{
40 char const * const *cic_argv_base;
41 char const * const *cic_argv_max; /* BUT: .cic_argv MUST be terminated! */
42 char const * const *cic_argv;
45 /* */
46 static void a_ccnd_oif_error(struct a_ccnd_if_ctx const *cicp,
47 char const *msg_or_null, char const *nearby_or_null);
49 /* noop and (1) don't work for real, only syntax-check and
50 * (2) non-error return is ignored */
51 static si8_t a_ccnd_oif_test(struct a_ccnd_if_ctx *cicp, bool_t noop);
52 static si8_t a_ccnd_oif_group(struct a_ccnd_if_ctx *cicp, size_t level,
53 bool_t noop);
55 /* Shared `if' / `elif' implementation */
56 static int a_ccnd_if(void *v, bool_t iselif);
58 static void
59 a_ccnd_oif_error(struct a_ccnd_if_ctx const *cicp, char const *msg_or_null,
60 char const *nearby_or_null){
61 struct str s;
62 NYD2_ENTER;
64 if(msg_or_null == NULL)
65 msg_or_null = _("invalid expression syntax");
67 if(nearby_or_null != NULL)
68 n_err(_("`if' conditional: %s -- near: %s\n"),
69 msg_or_null, nearby_or_null);
70 else
71 n_err(_("`if' conditional: %s\n"), msg_or_null);
73 if((n_psonce & n_PSO_INTERACTIVE) || (n_poption & n_PO_D_V)){
74 str_concat_cpa(&s, cicp->cic_argv_base,
75 (*cicp->cic_argv_base != NULL ? " " : n_empty));
76 n_err(_(" Expression: %s\n"), s.s);
78 str_concat_cpa(&s, cicp->cic_argv,
79 (*cicp->cic_argv != NULL ? " " : n_empty));
80 n_err(_(" Stopped at: %s\n"), s.s);
82 NYD2_LEAVE;
85 static si8_t
86 a_ccnd_oif_test(struct a_ccnd_if_ctx *cicp, bool_t noop){
87 char const *emsg, * const *argv, *cp, *lhv, *op, *rhv;
88 size_t argc;
89 char c;
90 si8_t rv;
91 NYD2_ENTER;
93 rv = -1;
94 emsg = NULL;
95 argv = cicp->cic_argv;
96 argc = PTR2SIZE(cicp->cic_argv_max - cicp->cic_argv);
97 cp = argv[0];
99 if(*cp != '$'){
100 if(argc > 1)
101 goto jesyn;
102 }else if(cp[1] == '\0')
103 goto jesyn;
104 else if(argc > 3){
105 #ifdef HAVE_REGEX
106 jesyn_ntr:
107 #endif
108 if(0){
109 jesyn:
110 if(emsg != NULL)
111 emsg = V_(emsg);
113 a_ccnd_oif_error(cicp, emsg, cp);
114 goto jleave;
117 switch(*cp){
118 default:
119 switch(boolify(cp, UIZ_MAX, -1)){
120 case 0: rv = FAL0; break;
121 case 1: rv = TRU1; break;
122 default:
123 emsg = N_("Expected a boolean");
124 goto jesyn;
126 break;
127 case 'R': case 'r':
128 rv = !(n_psonce & n_PSO_SENDMODE);
129 break;
130 case 'S': case 's':
131 rv = ((n_psonce & n_PSO_SENDMODE) != 0);
132 break;
133 case 'T': case 't':
134 if(!asccasecmp(cp, "true")) /* Beware! */
135 rv = TRU1;
136 else
137 rv = ((n_psonce & n_PSO_TTYIN) != 0);
138 break;
139 case '$':
140 /* Look up the value in question, we need it anyway */
141 if(*++cp == '{'){
142 size_t i = strlen(cp);
144 if(i > 0 && cp[i - 1] == '}')
145 cp = savestrbuf(++cp, i -= 2);
146 else
147 goto jesyn;
149 if(noop)
150 lhv = NULL;
151 else
152 lhv = n_var_vlook(cp, TRU1);
154 /* Single argument, "implicit boolean" form? */
155 if(argc == 1){
156 rv = (lhv != NULL);
157 break;
159 op = argv[1];
161 /* Three argument comparison form required, check syntax */
162 emsg = N_("unrecognized condition");
163 if(argc == 2 || (c = op[0]) == '\0')
164 goto jesyn;
165 if(op[1] == '\0'){
166 if(c != '<' && c != '>')
167 goto jesyn;
168 }else if(c != '-' && op[2] != '\0')
169 goto jesyn;
170 else if(c == '<' || c == '>'){
171 if(op[1] != '=')
172 goto jesyn;
173 }else if(c == '=' || c == '!'){
174 if(op[1] != '=' && op[1] != '@'
175 #ifdef HAVE_REGEX
176 && op[1] != '~'
177 #endif
179 goto jesyn;
180 }else if(c == '-'){
181 if(op[1] == '\0' || op[2] == '\0' || op[3] != '\0')
182 goto jesyn;
183 if(op[1] == 'e'){
184 if(op[2] != 'q')
185 goto jesyn;
186 }else if(op[1] == 'g' || op[1] == 'l'){
187 if(op[2] != 'e' && op[2] != 't')
188 goto jesyn;
189 }else if(op[1] == 'n'){
190 if(op[2] != 'e')
191 goto jesyn;
192 }else
193 goto jesyn;
194 }else
195 goto jesyn;
197 /* The right hand side may also be a variable, more syntax checking */
198 emsg = N_("invalid right hand side");
199 if((rhv = argv[2]) == NULL /* Can't happen */)
200 goto jesyn;
201 if(*rhv == '$'){
202 if(*++rhv == '\0')
203 goto jesyn;
204 else if(*rhv == '{'){
205 size_t i = strlen(rhv);
207 if(i > 0 && rhv[i - 1] == '}')
208 rhv = savestrbuf(++rhv, i -= 2);
209 else{
210 cp = --rhv;
211 goto jesyn;
214 if(noop)
215 rhv = NULL;
216 else
217 rhv = n_var_vlook(cp = rhv, TRU1);
220 /* A null value is treated as the empty string */
221 emsg = NULL;
222 if(lhv == NULL)
223 lhv = n_UNCONST(n_empty);
224 if(rhv == NULL)
225 rhv = n_UNCONST(n_empty);
227 #ifdef HAVE_REGEX
228 if(op[1] == '~'){
229 regex_t re;
230 int s;
232 if((s = regcomp(&re, rhv, REG_EXTENDED | REG_ICASE | REG_NOSUB)) != 0){
233 emsg = savecat(_("invalid regular expression: "),
234 n_regex_err_to_doc(&re, s));
235 goto jesyn_ntr;
237 if(!noop)
238 rv = (regexec(&re, lhv, 0,NULL, 0) == REG_NOMATCH) ^ (c == '=');
239 regfree(&re);
240 }else
241 #endif
242 if(noop)
243 break;
244 else if(op[1] == '@')
245 rv = (asccasestr(lhv, rhv) == NULL) ^ (c == '=');
246 else if(c == '-'){
247 si64_t lhvi, rhvi;
249 if(*lhv == '\0')
250 lhv = n_0;
251 if(*rhv == '\0')
252 rhv = n_0;
253 if((n_idec_si64_cp(&lhvi, lhv, 0, NULL
254 ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
255 ) != n_IDEC_STATE_CONSUMED || (n_idec_si64_cp(&rhvi, rhv, 0, NULL
256 ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
257 ) != n_IDEC_STATE_CONSUMED){
258 emsg = N_("integer expression expected");
259 goto jesyn;
262 lhvi -= rhvi;
263 switch(op[1]){
264 default:
265 case 'e': rv = (lhvi == 0); break;
266 case 'n': rv = (lhvi != 0); break;
267 case 'l': rv = (op[2] == 't') ? lhvi < 0 : lhvi <= 0; break;
268 case 'g': rv = (op[2] == 't') ? lhvi > 0 : lhvi >= 0; break;
270 break;
271 }else{
272 si32_t scmp;
274 scmp = asccasecmp(lhv, rhv);
275 switch(c){
276 default:
277 case '=': rv = (scmp == 0); break;
278 case '!': rv = (scmp != 0); break;
279 case '<': rv = (op[1] == '\0') ? scmp < 0 : scmp <= 0; break;
280 case '>': rv = (op[1] == '\0') ? scmp > 0 : scmp >= 0; break;
283 break;
286 if(noop && rv < 0)
287 rv = TRU1;
288 jleave:
289 NYD2_LEAVE;
290 return rv;
293 static si8_t
294 a_ccnd_oif_group(struct a_ccnd_if_ctx *cicp, size_t level, bool_t noop){
295 char const *emsg, *arg0, * const *argv, * const *argv_max_save;
296 size_t i;
297 char unary, c;
298 enum{
299 a_FIRST = 1<<0,
300 a_END_OK = 1<<1,
301 a_NEED_LIST = 1<<2,
303 a_CANNOT_UNARY = a_NEED_LIST,
304 a_CANNOT_OBRACK = a_NEED_LIST,
305 a_CANNOT_CBRACK = a_FIRST,
306 a_CANNOT_LIST = a_FIRST,
307 a_CANNOT_COND = a_NEED_LIST
308 } state;
309 si8_t rv, xrv;
310 NYD2_ENTER;
312 rv = -1;
313 state = a_FIRST;
314 unary = '\0';
315 emsg = NULL;
317 for(;;){
318 arg0 = *(argv = cicp->cic_argv);
319 if(arg0 == NULL){
320 if(!(state & a_END_OK)){
321 emsg = N_("missing expression (premature end)");
322 goto jesyn;
324 if(noop && rv < 0)
325 rv = TRU1;
326 break; /* goto jleave; */
329 switch((c = *arg0)){
330 case '!':
331 if(arg0[1] != '\0')
332 goto jneed_cond;
334 if(state & a_CANNOT_UNARY){
335 emsg = N_("cannot use a unary operator here");
336 goto jesyn;
339 unary = (unary != '\0') ? '\0' : c;
340 state &= ~(a_FIRST | a_END_OK);
341 cicp->cic_argv = ++argv;
342 continue;
344 case '[':
345 case ']':
346 if(arg0[1] != '\0')
347 goto jneed_cond;
349 if(c == '['){
350 if(state & a_CANNOT_OBRACK){
351 emsg = N_("cannot open a group here");
352 goto jesyn;
355 cicp->cic_argv = ++argv;
356 if((xrv = a_ccnd_oif_group(cicp, level + 1, noop)) < 0){
357 rv = xrv;
358 goto jleave;
359 }else if(!noop)
360 rv = (unary != '\0') ? !xrv : xrv;
362 unary = '\0';
363 state &= ~(a_FIRST | a_END_OK);
364 state |= (level == 0 ? a_END_OK : 0) | a_NEED_LIST;
365 continue;
366 }else{
367 if(state & a_CANNOT_CBRACK){
368 emsg = N_("cannot use closing bracket here");
369 goto jesyn;
372 if(level == 0){
373 emsg = N_("no open groups to be closed here");
374 goto jesyn;
377 cicp->cic_argv = ++argv;
378 if(noop && rv < 0)
379 rv = TRU1;
380 goto jleave;/* break;break; */
383 case '|':
384 case '&':
385 if(c != arg0[1] || arg0[2] != '\0')
386 goto jneed_cond;
388 if(state & a_CANNOT_LIST){
389 emsg = N_("cannot use a AND-OR list here");
390 goto jesyn;
393 noop = ((c == '&') ^ (rv == TRU1));
394 state &= ~(a_FIRST | a_END_OK | a_NEED_LIST);
395 cicp->cic_argv = ++argv;
396 continue;
398 default:
399 jneed_cond:
400 if(state & a_CANNOT_COND){
401 emsg = N_("cannot use a `if' condition here");
402 goto jesyn;
405 for(i = 0;; ++i){
406 if((arg0 = argv[i]) == NULL)
407 break;
408 c = *arg0;
409 if(c == '!' && arg0[1] == '\0')
410 break;
411 if((c == '[' || c == ']') && arg0[1] == '\0')
412 break;
413 if((c == '&' || c == '|') && c == arg0[1] && arg0[2] == '\0')
414 break;
416 if(i == 0){
417 emsg = N_("empty conditional group");
418 goto jesyn;
421 argv_max_save = cicp->cic_argv_max;
422 cicp->cic_argv_max = argv + i;
423 if((xrv = a_ccnd_oif_test(cicp, noop)) < 0){
424 rv = xrv;
425 goto jleave;
426 }else if(!noop)
427 rv = (unary != '\0') ? !xrv : xrv;
428 cicp->cic_argv_max = argv_max_save;
430 cicp->cic_argv = (argv += i);
431 unary = '\0';
432 state &= ~a_FIRST;
433 state |= a_END_OK | a_NEED_LIST;
434 break;
438 jleave:
439 NYD2_LEAVE;
440 return rv;
441 jesyn:
442 if(emsg == NULL)
443 emsg = N_("invalid grouping");
444 a_ccnd_oif_error(cicp, V_(emsg), arg0);
445 rv = -1;
446 goto jleave;
449 static int
450 a_ccnd_if(void *v, bool_t iselif){
451 struct a_ccnd_if_ctx cic;
452 char const * const *argv;
453 size_t argc;
454 si8_t xrv, rv;
455 struct a_ccnd_if_node *cinp;
456 NYD_ENTER;
458 if(!iselif){
459 cinp = smalloc(sizeof *cinp);
460 cinp->cin_outer = n_go_data->gdc_ifcond;
461 }else{
462 cinp = n_go_data->gdc_ifcond;
463 assert(cinp != NULL);
465 cinp->cin_error = FAL0;
466 cinp->cin_noop = a_CCND_IF_ISSKIP();
467 cinp->cin_go = TRU1;
468 cinp->cin_else = FAL0;
469 if(!iselif)
470 n_go_data->gdc_ifcond = cinp;
472 if(cinp->cin_noop){
473 rv = 0;
474 goto jleave;
477 /* For heaven's sake, support comments _today_ TODO wyshlist.. */
478 for(argc = 0, argv = v; argv[argc] != NULL; ++argc)
479 if(argv[argc][0] == '#'){
480 char const **nav = salloc(sizeof(char*) * (argc + 1));
481 size_t i;
483 for(i = 0; i < argc; ++i)
484 nav[i] = argv[i];
485 nav[i] = NULL;
486 argv = nav;
487 break;
489 cic.cic_argv_base = cic.cic_argv = argv;
490 cic.cic_argv_max = &argv[argc];
491 xrv = a_ccnd_oif_group(&cic, 0, FAL0);
493 if(xrv >= 0){
494 cinp->cin_go = (bool_t)xrv;
495 rv = 0;
496 }else{
497 cinp->cin_error = cinp->cin_noop = TRU1;
498 rv = 1;
500 jleave:
501 NYD_LEAVE;
502 return rv;
505 FL int
506 c_if(void *v){
507 int rv;
508 NYD_ENTER;
510 rv = a_ccnd_if(v, FAL0);
511 NYD_LEAVE;
512 return rv;
515 FL int
516 c_elif(void *v){
517 struct a_ccnd_if_node *cinp;
518 int rv;
519 NYD_ENTER;
521 if((cinp = n_go_data->gdc_ifcond) == NULL || cinp->cin_else){
522 n_err(_("`elif' without a matching `if'\n"));
523 rv = 1;
524 }else if(!cinp->cin_error){
525 cinp->cin_go = !cinp->cin_go; /* Cause right _IF_ISSKIP() result */
526 rv = a_ccnd_if(v, TRU1);
527 }else
528 rv = 0;
529 NYD_LEAVE;
530 return rv;
533 FL int
534 c_else(void *v){
535 int rv;
536 struct a_ccnd_if_node *cinp;
537 NYD_ENTER;
538 n_UNUSED(v);
540 if((cinp = n_go_data->gdc_ifcond) == NULL || cinp->cin_else){
541 n_err(_("`else' without a matching `if'\n"));
542 rv = 1;
543 }else{
544 cinp->cin_else = TRU1;
545 cinp->cin_go = !cinp->cin_go;
546 rv = 0;
548 NYD_LEAVE;
549 return rv;
552 FL int
553 c_endif(void *v){
554 int rv;
555 struct a_ccnd_if_node *cinp;
556 NYD_ENTER;
557 n_UNUSED(v);
559 if((cinp = n_go_data->gdc_ifcond) == NULL){
560 n_err(_("`endif' without a matching `if'\n"));
561 rv = 1;
562 }else{
563 n_go_data->gdc_ifcond = cinp->cin_outer;
564 free(cinp);
565 rv = 0;
567 NYD_LEAVE;
568 return rv;
571 FL bool_t
572 n_cnd_if_isskip(void){
573 bool_t rv;
574 NYD2_ENTER;
576 rv = a_CCND_IF_ISSKIP();
577 NYD2_LEAVE;
578 return rv;
581 FL void
582 n_cnd_if_stack_del(void *vp){
583 struct a_ccnd_if_node *cinp;
584 NYD2_ENTER;
587 cinp = vp;
588 vp = cinp->cin_outer;
589 free(cinp);
590 }while((cinp = vp) != NULL);
591 NYD2_LEAVE;
594 /* s-it-mode */