THANKS: Olav Mørkrid
[s-mailx.git] / cmd-cnd.c
blob3340ed114761dffe7b478a1e9a649f172adbf905
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Commands: conditional constructs.
4 * Copyright (c) 2014 - 2018 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((rv = n_boolify(cp, UIZ_MAX, TRUM1))){
120 case FAL0:
121 case TRU1:
122 break;
123 default:
124 emsg = N_("Expected a boolean");
125 goto jesyn;
127 break;
128 case 'R': case 'r':
129 rv = !(n_psonce & n_PSO_SENDMODE);
130 break;
131 case 'S': case 's':
132 rv = ((n_psonce & n_PSO_SENDMODE) != 0);
133 break;
134 case 'T': case 't':
135 if(!asccasecmp(cp, "true")) /* Beware! */
136 rv = TRU1;
137 else
138 rv = ((n_psonce & n_PSO_INTERACTIVE) != 0);
139 break;
140 case '$':{
141 enum{
142 a_NONE,
143 a_ICASE = 1u<<0
144 } flags = a_NONE;
146 /* Look up the value in question, we need it anyway */
147 if(*++cp == '{'){
148 size_t i = strlen(cp);
150 if(i > 0 && cp[i - 1] == '}')
151 cp = savestrbuf(++cp, i -= 2);
152 else
153 goto jesyn;
155 if(noop)
156 lhv = NULL;
157 else
158 lhv = n_var_vlook(cp, TRU1);
160 /* Single argument, "implicit boolean" form? */
161 if(argc == 1){
162 rv = (lhv != NULL);
163 break;
165 op = argv[1];
167 /* Three argument comparison form required, check syntax */
168 emsg = N_("unrecognized condition");
169 if(argc == 2 || (c = op[0]) == '\0')
170 goto jesyn;
172 /* May be modifier */
173 if(c == '@'){
174 for(;;){
175 c = *++op;
176 if(c == 'i')
177 flags |= a_ICASE;
178 else
179 break;
181 if(flags == a_NONE)
182 flags = a_ICASE;
185 if(op[1] == '\0'){
186 if(c != '<' && c != '>')
187 goto jesyn;
188 }else if(c != '-' && op[2] != '\0')
189 goto jesyn;
190 else if(c == '<' || c == '>'){
191 if(op[1] != '=')
192 goto jesyn;
193 }else if(c == '=' || c == '!'){
194 if(op[1] != '=' && op[1] != '%' && op[1] != '@'
195 #ifdef HAVE_REGEX
196 && op[1] != '~'
197 #endif
199 goto jesyn;
200 }else if(c == '-'){
201 if(op[1] == '\0' || op[2] == '\0' || op[3] != '\0')
202 goto jesyn;
203 if(op[1] == 'e'){
204 if(op[2] != 'q')
205 goto jesyn;
206 }else if(op[1] == 'g' || op[1] == 'l'){
207 if(op[2] != 'e' && op[2] != 't')
208 goto jesyn;
209 }else if(op[1] == 'n'){
210 if(op[2] != 'e')
211 goto jesyn;
212 }else
213 goto jesyn;
214 }else
215 goto jesyn;
217 /* The right hand side may also be a variable, more syntax checking */
218 emsg = N_("invalid right hand side");
219 if((rhv = argv[2]) == NULL /* Can't happen */)
220 goto jesyn;
221 if(*rhv == '$'){
222 if(*++rhv == '\0')
223 goto jesyn;
224 else if(*rhv == '{'){
225 size_t i = strlen(rhv);
227 if(i > 0 && rhv[i - 1] == '}')
228 rhv = savestrbuf(++rhv, i -= 2);
229 else{
230 cp = --rhv;
231 goto jesyn;
234 if(noop)
235 rhv = NULL;
236 else
237 rhv = n_var_vlook(cp = rhv, TRU1);
240 /* A null value is treated as the empty string */
241 emsg = NULL;
242 if(lhv == NULL)
243 lhv = n_UNCONST(n_empty);
244 if(rhv == NULL)
245 rhv = n_UNCONST(n_empty);
247 #ifdef HAVE_REGEX
248 if(op[1] == '~'){
249 regex_t re;
250 int s;
252 if((s = regcomp(&re, rhv, REG_EXTENDED | REG_NOSUB |
253 (flags & a_ICASE ? REG_ICASE : 0))) != 0){
254 emsg = savecat(_("invalid regular expression: "),
255 n_regex_err_to_doc(NULL, s));
256 goto jesyn_ntr;
258 if(!noop)
259 rv = (regexec(&re, lhv, 0,NULL, 0) == REG_NOMATCH) ^ (c == '=');
260 regfree(&re);
261 }else
262 #endif
263 if(noop)
264 break;
265 else if(op[1] == '%' || op[1] == '@'){
266 if(op[1] == '@')
267 n_OBSOLETE("`if'++: \"=@\" and \"!@\" became \"=%\" and \"!%\"");
268 rv = ((flags & a_ICASE ? asccasestr(lhv, rhv) : strstr(lhv, rhv)
269 ) == NULL) ^ (c == '=');
270 }else if(c == '-'){
271 si64_t lhvi, rhvi;
273 if(*lhv == '\0')
274 lhv = n_0;
275 if(*rhv == '\0')
276 rhv = n_0;
277 if((n_idec_si64_cp(&lhvi, lhv, 0, NULL
278 ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
279 ) != n_IDEC_STATE_CONSUMED || (n_idec_si64_cp(&rhvi, rhv, 0, NULL
280 ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
281 ) != n_IDEC_STATE_CONSUMED){
282 emsg = N_("integer expression expected");
283 goto jesyn;
286 lhvi -= rhvi;
287 switch(op[1]){
288 default:
289 case 'e': rv = (lhvi == 0); break;
290 case 'n': rv = (lhvi != 0); break;
291 case 'l': rv = (op[2] == 't') ? lhvi < 0 : lhvi <= 0; break;
292 case 'g': rv = (op[2] == 't') ? lhvi > 0 : lhvi >= 0; break;
294 break;
295 }else{
296 si32_t scmp;
298 scmp = (flags & a_ICASE) ? asccasecmp(lhv, rhv) : strcmp(lhv, rhv);
299 switch(c){
300 default:
301 case '=': rv = (scmp == 0); break;
302 case '!': rv = (scmp != 0); break;
303 case '<': rv = (op[1] == '\0') ? scmp < 0 : scmp <= 0; break;
304 case '>': rv = (op[1] == '\0') ? scmp > 0 : scmp >= 0; break;
307 }break;
310 if(noop && rv < 0)
311 rv = TRU1;
312 jleave:
313 NYD2_LEAVE;
314 return rv;
317 static si8_t
318 a_ccnd_oif_group(struct a_ccnd_if_ctx *cicp, size_t level, bool_t noop){
319 char const *emsg, *arg0, * const *argv, * const *argv_max_save;
320 size_t i;
321 char unary, c;
322 enum{
323 a_FIRST = 1<<0,
324 a_END_OK = 1<<1,
325 a_NEED_LIST = 1<<2,
327 a_CANNOT_UNARY = a_NEED_LIST,
328 a_CANNOT_OBRACK = a_NEED_LIST,
329 a_CANNOT_CBRACK = a_FIRST,
330 a_CANNOT_LIST = a_FIRST,
331 a_CANNOT_COND = a_NEED_LIST
332 } state;
333 si8_t rv, xrv;
334 NYD2_ENTER;
336 rv = -1;
337 state = a_FIRST;
338 unary = '\0';
339 emsg = NULL;
341 for(;;){
342 arg0 = *(argv = cicp->cic_argv);
343 if(arg0 == NULL){
344 if(!(state & a_END_OK)){
345 emsg = N_("missing expression (premature end)");
346 goto jesyn;
348 if(noop && rv < 0)
349 rv = TRU1;
350 break; /* goto jleave; */
353 switch((c = *arg0)){
354 case '!':
355 if(arg0[1] != '\0')
356 goto jneed_cond;
358 if(state & a_CANNOT_UNARY){
359 emsg = N_("cannot use a unary operator here");
360 goto jesyn;
363 unary = (unary != '\0') ? '\0' : c;
364 state &= ~(a_FIRST | a_END_OK);
365 cicp->cic_argv = ++argv;
366 continue;
368 case '[':
369 case ']':
370 if(arg0[1] != '\0')
371 goto jneed_cond;
373 if(c == '['){
374 if(state & a_CANNOT_OBRACK){
375 emsg = N_("cannot open a group here");
376 goto jesyn;
379 cicp->cic_argv = ++argv;
380 if((xrv = a_ccnd_oif_group(cicp, level + 1, noop)) < 0){
381 rv = xrv;
382 goto jleave;
383 }else if(!noop)
384 rv = (unary != '\0') ? !xrv : xrv;
386 unary = '\0';
387 state &= ~(a_FIRST | a_END_OK);
388 state |= (level == 0 ? a_END_OK : 0) | a_NEED_LIST;
389 continue;
390 }else{
391 if(state & a_CANNOT_CBRACK){
392 emsg = N_("cannot use closing bracket here");
393 goto jesyn;
396 if(level == 0){
397 emsg = N_("no open groups to be closed here");
398 goto jesyn;
401 cicp->cic_argv = ++argv;
402 if(noop && rv < 0)
403 rv = TRU1;
404 goto jleave;/* break;break; */
407 case '|':
408 case '&':
409 if(c != arg0[1] || arg0[2] != '\0')
410 goto jneed_cond;
412 if(state & a_CANNOT_LIST){
413 emsg = N_("cannot use a AND-OR list here");
414 goto jesyn;
417 noop = ((c == '&') ^ (rv == TRU1));
418 state &= ~(a_FIRST | a_END_OK | a_NEED_LIST);
419 cicp->cic_argv = ++argv;
420 continue;
422 default:
423 jneed_cond:
424 if(state & a_CANNOT_COND){
425 emsg = N_("cannot use a `if' condition here");
426 goto jesyn;
429 for(i = 0;; ++i){
430 if((arg0 = argv[i]) == NULL)
431 break;
432 c = *arg0;
433 if(c == '!' && arg0[1] == '\0')
434 break;
435 if((c == '[' || c == ']') && arg0[1] == '\0')
436 break;
437 if((c == '&' || c == '|') && c == arg0[1] && arg0[2] == '\0')
438 break;
440 if(i == 0){
441 emsg = N_("empty conditional group");
442 goto jesyn;
445 argv_max_save = cicp->cic_argv_max;
446 cicp->cic_argv_max = argv + i;
447 if((xrv = a_ccnd_oif_test(cicp, noop)) < 0){
448 rv = xrv;
449 goto jleave;
450 }else if(!noop)
451 rv = (unary != '\0') ? !xrv : xrv;
452 cicp->cic_argv_max = argv_max_save;
454 cicp->cic_argv = (argv += i);
455 unary = '\0';
456 state &= ~a_FIRST;
457 state |= a_END_OK | a_NEED_LIST;
458 break;
462 jleave:
463 NYD2_LEAVE;
464 return rv;
465 jesyn:
466 if(emsg == NULL)
467 emsg = N_("invalid grouping");
468 a_ccnd_oif_error(cicp, V_(emsg), arg0);
469 rv = -1;
470 goto jleave;
473 static int
474 a_ccnd_if(void *v, bool_t iselif){
475 struct a_ccnd_if_ctx cic;
476 char const * const *argv;
477 size_t argc;
478 si8_t xrv, rv;
479 struct a_ccnd_if_node *cinp;
480 NYD_ENTER;
482 if(!iselif){
483 cinp = smalloc(sizeof *cinp);
484 cinp->cin_outer = n_go_data->gdc_ifcond;
485 }else{
486 cinp = n_go_data->gdc_ifcond;
487 assert(cinp != NULL);
489 cinp->cin_error = FAL0;
490 cinp->cin_noop = a_CCND_IF_ISSKIP();
491 cinp->cin_go = TRU1;
492 cinp->cin_else = FAL0;
493 if(!iselif)
494 n_go_data->gdc_ifcond = cinp;
496 if(cinp->cin_noop){
497 rv = 0;
498 goto jleave;
501 /* For heaven's sake, support comments _today_ TODO wyshlist.. */
502 for(argc = 0, argv = v; argv[argc] != NULL; ++argc)
503 if(argv[argc][0] == '#'){
504 char const **nav = salloc(sizeof(char*) * (argc + 1));
505 size_t i;
507 for(i = 0; i < argc; ++i)
508 nav[i] = argv[i];
509 nav[i] = NULL;
510 argv = nav;
511 break;
513 cic.cic_argv_base = cic.cic_argv = argv;
514 cic.cic_argv_max = &argv[argc];
515 xrv = a_ccnd_oif_group(&cic, 0, FAL0);
517 if(xrv >= 0){
518 cinp->cin_go = (bool_t)xrv;
519 rv = 0;
520 }else{
521 cinp->cin_error = cinp->cin_noop = TRU1;
522 rv = 1;
524 jleave:
525 NYD_LEAVE;
526 return rv;
529 FL int
530 c_if(void *v){
531 int rv;
532 NYD_ENTER;
534 rv = a_ccnd_if(v, FAL0);
535 NYD_LEAVE;
536 return rv;
539 FL int
540 c_elif(void *v){
541 struct a_ccnd_if_node *cinp;
542 int rv;
543 NYD_ENTER;
545 if((cinp = n_go_data->gdc_ifcond) == NULL || cinp->cin_else){
546 n_err(_("`elif' without a matching `if'\n"));
547 rv = 1;
548 }else if(!cinp->cin_error){
549 cinp->cin_go = !cinp->cin_go; /* Cause right _IF_ISSKIP() result */
550 rv = a_ccnd_if(v, TRU1);
551 }else
552 rv = 0;
553 NYD_LEAVE;
554 return rv;
557 FL int
558 c_else(void *v){
559 int rv;
560 struct a_ccnd_if_node *cinp;
561 NYD_ENTER;
562 n_UNUSED(v);
564 if((cinp = n_go_data->gdc_ifcond) == NULL || cinp->cin_else){
565 n_err(_("`else' without a matching `if'\n"));
566 rv = 1;
567 }else{
568 cinp->cin_else = TRU1;
569 cinp->cin_go = !cinp->cin_go;
570 rv = 0;
572 NYD_LEAVE;
573 return rv;
576 FL int
577 c_endif(void *v){
578 int rv;
579 struct a_ccnd_if_node *cinp;
580 NYD_ENTER;
581 n_UNUSED(v);
583 if((cinp = n_go_data->gdc_ifcond) == NULL){
584 n_err(_("`endif' without a matching `if'\n"));
585 rv = 1;
586 }else{
587 n_go_data->gdc_ifcond = cinp->cin_outer;
588 free(cinp);
589 rv = 0;
591 NYD_LEAVE;
592 return rv;
595 FL bool_t
596 n_cnd_if_isskip(void){
597 bool_t rv;
598 NYD2_ENTER;
600 rv = a_CCND_IF_ISSKIP();
601 NYD2_LEAVE;
602 return rv;
605 FL void
606 n_cnd_if_stack_del(struct n_go_data_ctx *gdcp){
607 struct a_ccnd_if_node *vp, *cinp;
608 NYD2_ENTER;
610 vp = gdcp->gdc_ifcond;
611 gdcp->gdc_ifcond = NULL;
613 while((cinp = vp) != NULL){
614 vp = cinp->cin_outer;
615 free(cinp);
617 NYD2_LEAVE;
620 /* s-it-mode */