make-config.in: complete path (leftover of [807f64e2], 2015-12-26!)
[s-mailx.git] / cmd-cnd.c
blobd0ca92cdb818386173e153c6958a5aee7042e8e8
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>.
5 * SPDX-License-Identifier: ISC
7 * Permission to use, copy, modify, and/or distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19 #undef n_FILE
20 #define n_FILE cmd_cnd
22 #ifndef HAVE_AMALGAMATION
23 # include "nail.h"
24 #endif
26 #define a_CCND_IF_ISSKIP() \
27 (n_go_data->gdc_ifcond != NULL &&\
28 (((struct a_ccnd_if_node*)n_go_data->gdc_ifcond)->cin_noop ||\
29 !((struct a_ccnd_if_node*)n_go_data->gdc_ifcond)->cin_go))
31 struct a_ccnd_if_node{
32 struct a_ccnd_if_node *cin_outer;
33 bool_t cin_error; /* Bad expression, skip entire if..endif */
34 bool_t cin_noop; /* Outer stack !cin_go, entirely no-op */
35 bool_t cin_go; /* Green light */
36 bool_t cin_else; /* In `else' clause */
37 ui8_t cin__dummy[4];
40 struct a_ccnd_if_ctx{
41 char const * const *cic_argv_base;
42 char const * const *cic_argv_max; /* BUT: .cic_argv MUST be terminated! */
43 char const * const *cic_argv;
46 /* */
47 static void a_ccnd_oif_error(struct a_ccnd_if_ctx const *cicp,
48 char const *msg_or_null, char const *nearby_or_null);
50 /* noop and (1) don't work for real, only syntax-check and
51 * (2) non-error return is ignored */
52 static si8_t a_ccnd_oif_test(struct a_ccnd_if_ctx *cicp, bool_t noop);
53 static si8_t a_ccnd_oif_group(struct a_ccnd_if_ctx *cicp, size_t level,
54 bool_t noop);
56 /* Shared `if' / `elif' implementation */
57 static int a_ccnd_if(void *v, bool_t iselif);
59 static void
60 a_ccnd_oif_error(struct a_ccnd_if_ctx const *cicp, char const *msg_or_null,
61 char const *nearby_or_null){
62 struct str s;
63 NYD2_ENTER;
65 if(msg_or_null == NULL)
66 msg_or_null = _("invalid expression syntax");
68 if(nearby_or_null != NULL)
69 n_err(_("`if' conditional: %s -- near: %s\n"),
70 msg_or_null, nearby_or_null);
71 else
72 n_err(_("`if' conditional: %s\n"), msg_or_null);
74 if((n_psonce & n_PSO_INTERACTIVE) || (n_poption & n_PO_D_V)){
75 str_concat_cpa(&s, cicp->cic_argv_base,
76 (*cicp->cic_argv_base != NULL ? " " : n_empty));
77 n_err(_(" Expression: %s\n"), s.s);
79 str_concat_cpa(&s, cicp->cic_argv,
80 (*cicp->cic_argv != NULL ? " " : n_empty));
81 n_err(_(" Stopped at: %s\n"), s.s);
83 NYD2_LEAVE;
86 static si8_t
87 a_ccnd_oif_test(struct a_ccnd_if_ctx *cicp, bool_t noop){
88 char const *emsg, * const *argv, *cp, *lhv, *op, *rhv;
89 size_t argc;
90 char c;
91 si8_t rv;
92 NYD2_ENTER;
94 rv = -1;
95 emsg = NULL;
96 argv = cicp->cic_argv;
97 argc = PTR2SIZE(cicp->cic_argv_max - cicp->cic_argv);
98 cp = argv[0];
100 if(*cp != '$'){
101 if(argc > 1)
102 goto jesyn;
103 }else if(cp[1] == '\0')
104 goto jesyn;
105 else if(argc > 3){
106 #ifdef HAVE_REGEX
107 jesyn_ntr:
108 #endif
109 if(0){
110 jesyn:
111 if(emsg != NULL)
112 emsg = V_(emsg);
114 a_ccnd_oif_error(cicp, emsg, cp);
115 goto jleave;
118 switch(*cp){
119 default:
120 switch((rv = n_boolify(cp, UIZ_MAX, TRUM1))){
121 case FAL0:
122 case TRU1:
123 break;
124 default:
125 emsg = N_("Expected a boolean");
126 goto jesyn;
128 break;
129 case 'R': case 'r':
130 rv = !(n_psonce & n_PSO_SENDMODE);
131 break;
132 case 'S': case 's':
133 rv = ((n_psonce & n_PSO_SENDMODE) != 0);
134 break;
135 case 'T': case 't':
136 if(!asccasecmp(cp, "true")) /* Beware! */
137 rv = TRU1;
138 else
139 rv = ((n_psonce & n_PSO_INTERACTIVE) != 0);
140 break;
141 case '$':{
142 enum{
143 a_NONE,
144 a_ICASE = 1u<<0
145 } flags = a_NONE;
147 /* Look up the value in question, we need it anyway */
148 if(*++cp == '{'){
149 size_t i = strlen(cp);
151 if(i > 0 && cp[i - 1] == '}')
152 cp = savestrbuf(++cp, i -= 2);
153 else
154 goto jesyn;
156 if(noop)
157 lhv = NULL;
158 else
159 lhv = n_var_vlook(cp, TRU1);
161 /* Single argument, "implicit boolean" form? */
162 if(argc == 1){
163 rv = (lhv != NULL);
164 break;
166 op = argv[1];
168 /* Three argument comparison form required, check syntax */
169 emsg = N_("unrecognized condition");
170 if(argc == 2 || (c = op[0]) == '\0')
171 goto jesyn;
173 /* May be modifier */
174 if(c == '@'){
175 for(;;){
176 c = *++op;
177 if(c == 'i')
178 flags |= a_ICASE;
179 else
180 break;
182 if(flags == a_NONE)
183 flags = a_ICASE;
186 if(op[1] == '\0'){
187 if(c != '<' && c != '>')
188 goto jesyn;
189 }else if(c != '-' && op[2] != '\0')
190 goto jesyn;
191 else if(c == '<' || c == '>'){
192 if(op[1] != '=')
193 goto jesyn;
194 }else if(c == '=' || c == '!'){
195 if(op[1] != '=' && op[1] != '%' && op[1] != '@'
196 #ifdef HAVE_REGEX
197 && op[1] != '~'
198 #endif
200 goto jesyn;
201 }else if(c == '-'){
202 if(op[1] == '\0' || op[2] == '\0' || op[3] != '\0')
203 goto jesyn;
204 if(op[1] == 'e'){
205 if(op[2] != 'q')
206 goto jesyn;
207 }else if(op[1] == 'g' || op[1] == 'l'){
208 if(op[2] != 'e' && op[2] != 't')
209 goto jesyn;
210 }else if(op[1] == 'n'){
211 if(op[2] != 'e')
212 goto jesyn;
213 }else
214 goto jesyn;
215 }else
216 goto jesyn;
218 /* The right hand side may also be a variable, more syntax checking */
219 emsg = N_("invalid right hand side");
220 if((rhv = argv[2]) == NULL /* Can't happen */)
221 goto jesyn;
222 if(*rhv == '$'){
223 if(*++rhv == '\0')
224 goto jesyn;
225 else if(*rhv == '{'){
226 size_t i = strlen(rhv);
228 if(i > 0 && rhv[i - 1] == '}')
229 rhv = savestrbuf(++rhv, i -= 2);
230 else{
231 cp = --rhv;
232 goto jesyn;
235 if(noop)
236 rhv = NULL;
237 else
238 rhv = n_var_vlook(cp = rhv, TRU1);
241 /* A null value is treated as the empty string */
242 emsg = NULL;
243 if(lhv == NULL)
244 lhv = n_UNCONST(n_empty);
245 if(rhv == NULL)
246 rhv = n_UNCONST(n_empty);
248 #ifdef HAVE_REGEX
249 if(op[1] == '~'){
250 regex_t re;
251 int s;
253 if((s = regcomp(&re, rhv, REG_EXTENDED | REG_NOSUB |
254 (flags & a_ICASE ? REG_ICASE : 0))) != 0){
255 emsg = savecat(_("invalid regular expression: "),
256 n_regex_err_to_doc(NULL, s));
257 goto jesyn_ntr;
259 if(!noop)
260 rv = (regexec(&re, lhv, 0,NULL, 0) == REG_NOMATCH) ^ (c == '=');
261 regfree(&re);
262 }else
263 #endif
264 if(noop)
265 break;
266 else if(op[1] == '%' || op[1] == '@'){
267 if(op[1] == '@')
268 n_OBSOLETE("`if'++: \"=@\" and \"!@\" became \"=%\" and \"!%\"");
269 rv = ((flags & a_ICASE ? asccasestr(lhv, rhv) : strstr(lhv, rhv)
270 ) == NULL) ^ (c == '=');
271 }else if(c == '-'){
272 si64_t lhvi, rhvi;
274 if(*lhv == '\0')
275 lhv = n_0;
276 if(*rhv == '\0')
277 rhv = n_0;
278 if((n_idec_si64_cp(&lhvi, lhv, 0, NULL
279 ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
280 ) != n_IDEC_STATE_CONSUMED || (n_idec_si64_cp(&rhvi, rhv, 0, NULL
281 ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
282 ) != n_IDEC_STATE_CONSUMED){
283 emsg = N_("integer expression expected");
284 goto jesyn;
287 lhvi -= rhvi;
288 switch(op[1]){
289 default:
290 case 'e': rv = (lhvi == 0); break;
291 case 'n': rv = (lhvi != 0); break;
292 case 'l': rv = (op[2] == 't') ? lhvi < 0 : lhvi <= 0; break;
293 case 'g': rv = (op[2] == 't') ? lhvi > 0 : lhvi >= 0; break;
295 break;
296 }else{
297 si32_t scmp;
299 scmp = (flags & a_ICASE) ? asccasecmp(lhv, rhv) : strcmp(lhv, rhv);
300 switch(c){
301 default:
302 case '=': rv = (scmp == 0); break;
303 case '!': rv = (scmp != 0); break;
304 case '<': rv = (op[1] == '\0') ? scmp < 0 : scmp <= 0; break;
305 case '>': rv = (op[1] == '\0') ? scmp > 0 : scmp >= 0; break;
308 }break;
311 if(noop && rv < 0)
312 rv = TRU1;
313 jleave:
314 NYD2_LEAVE;
315 return rv;
318 static si8_t
319 a_ccnd_oif_group(struct a_ccnd_if_ctx *cicp, size_t level, bool_t noop){
320 char const *emsg, *arg0, * const *argv, * const *argv_max_save;
321 size_t i;
322 char unary, c;
323 enum{
324 a_FIRST = 1<<0,
325 a_END_OK = 1<<1,
326 a_NEED_LIST = 1<<2,
328 a_CANNOT_UNARY = a_NEED_LIST,
329 a_CANNOT_OBRACK = a_NEED_LIST,
330 a_CANNOT_CBRACK = a_FIRST,
331 a_CANNOT_LIST = a_FIRST,
332 a_CANNOT_COND = a_NEED_LIST
333 } state;
334 si8_t rv, xrv;
335 NYD2_ENTER;
337 rv = -1;
338 state = a_FIRST;
339 unary = '\0';
340 emsg = NULL;
342 for(;;){
343 arg0 = *(argv = cicp->cic_argv);
344 if(arg0 == NULL){
345 if(!(state & a_END_OK)){
346 emsg = N_("missing expression (premature end)");
347 goto jesyn;
349 if(noop && rv < 0)
350 rv = TRU1;
351 break; /* goto jleave; */
354 switch((c = *arg0)){
355 case '!':
356 if(arg0[1] != '\0')
357 goto jneed_cond;
359 if(state & a_CANNOT_UNARY){
360 emsg = N_("cannot use a unary operator here");
361 goto jesyn;
364 unary = (unary != '\0') ? '\0' : c;
365 state &= ~(a_FIRST | a_END_OK);
366 cicp->cic_argv = ++argv;
367 continue;
369 case '[':
370 case ']':
371 if(arg0[1] != '\0')
372 goto jneed_cond;
374 if(c == '['){
375 if(state & a_CANNOT_OBRACK){
376 emsg = N_("cannot open a group here");
377 goto jesyn;
380 cicp->cic_argv = ++argv;
381 if((xrv = a_ccnd_oif_group(cicp, level + 1, noop)) < 0){
382 rv = xrv;
383 goto jleave;
384 }else if(!noop)
385 rv = (unary != '\0') ? !xrv : xrv;
387 unary = '\0';
388 state &= ~(a_FIRST | a_END_OK);
389 state |= (level == 0 ? a_END_OK : 0) | a_NEED_LIST;
390 continue;
391 }else{
392 if(state & a_CANNOT_CBRACK){
393 emsg = N_("cannot use closing bracket here");
394 goto jesyn;
397 if(level == 0){
398 emsg = N_("no open groups to be closed here");
399 goto jesyn;
402 cicp->cic_argv = ++argv;
403 if(noop && rv < 0)
404 rv = TRU1;
405 goto jleave;/* break;break; */
408 case '|':
409 case '&':
410 if(c != arg0[1] || arg0[2] != '\0')
411 goto jneed_cond;
413 if(state & a_CANNOT_LIST){
414 emsg = N_("cannot use a AND-OR list here");
415 goto jesyn;
418 noop = ((c == '&') ^ (rv == TRU1));
419 state &= ~(a_FIRST | a_END_OK | a_NEED_LIST);
420 cicp->cic_argv = ++argv;
421 continue;
423 default:
424 jneed_cond:
425 if(state & a_CANNOT_COND){
426 emsg = N_("cannot use a `if' condition here");
427 goto jesyn;
430 for(i = 0;; ++i){
431 if((arg0 = argv[i]) == NULL)
432 break;
433 c = *arg0;
434 if(c == '!' && arg0[1] == '\0')
435 break;
436 if((c == '[' || c == ']') && arg0[1] == '\0')
437 break;
438 if((c == '&' || c == '|') && c == arg0[1] && arg0[2] == '\0')
439 break;
441 if(i == 0){
442 emsg = N_("empty conditional group");
443 goto jesyn;
446 argv_max_save = cicp->cic_argv_max;
447 cicp->cic_argv_max = argv + i;
448 if((xrv = a_ccnd_oif_test(cicp, noop)) < 0){
449 rv = xrv;
450 goto jleave;
451 }else if(!noop)
452 rv = (unary != '\0') ? !xrv : xrv;
453 cicp->cic_argv_max = argv_max_save;
455 cicp->cic_argv = (argv += i);
456 unary = '\0';
457 state &= ~a_FIRST;
458 state |= a_END_OK | a_NEED_LIST;
459 break;
463 jleave:
464 NYD2_LEAVE;
465 return rv;
466 jesyn:
467 if(emsg == NULL)
468 emsg = N_("invalid grouping");
469 a_ccnd_oif_error(cicp, V_(emsg), arg0);
470 rv = -1;
471 goto jleave;
474 static int
475 a_ccnd_if(void *v, bool_t iselif){
476 struct a_ccnd_if_ctx cic;
477 char const * const *argv;
478 size_t argc;
479 si8_t xrv, rv;
480 struct a_ccnd_if_node *cinp;
481 NYD_ENTER;
483 if(!iselif){
484 cinp = n_alloc(sizeof *cinp);
485 cinp->cin_outer = n_go_data->gdc_ifcond;
486 }else{
487 cinp = n_go_data->gdc_ifcond;
488 assert(cinp != NULL);
490 cinp->cin_error = FAL0;
491 cinp->cin_noop = a_CCND_IF_ISSKIP();
492 cinp->cin_go = TRU1;
493 cinp->cin_else = FAL0;
494 if(!iselif)
495 n_go_data->gdc_ifcond = cinp;
497 if(cinp->cin_noop){
498 rv = 0;
499 goto jleave;
502 /* For heaven's sake, support comments _today_ TODO wyshlist.. */
503 for(argc = 0, argv = v; argv[argc] != NULL; ++argc)
504 if(argv[argc][0] == '#'){
505 char const **nav = n_autorec_alloc(sizeof(char*) * (argc + 1));
506 size_t i;
508 for(i = 0; i < argc; ++i)
509 nav[i] = argv[i];
510 nav[i] = NULL;
511 argv = nav;
512 break;
514 cic.cic_argv_base = cic.cic_argv = argv;
515 cic.cic_argv_max = &argv[argc];
516 xrv = a_ccnd_oif_group(&cic, 0, FAL0);
518 if(xrv >= 0){
519 cinp->cin_go = (bool_t)xrv;
520 rv = 0;
521 }else{
522 cinp->cin_error = cinp->cin_noop = TRU1;
523 rv = 1;
525 jleave:
526 NYD_LEAVE;
527 return rv;
530 FL int
531 c_if(void *v){
532 int rv;
533 NYD_ENTER;
535 rv = a_ccnd_if(v, FAL0);
536 NYD_LEAVE;
537 return rv;
540 FL int
541 c_elif(void *v){
542 struct a_ccnd_if_node *cinp;
543 int rv;
544 NYD_ENTER;
546 if((cinp = n_go_data->gdc_ifcond) == NULL || cinp->cin_else){
547 n_err(_("`elif' without a matching `if'\n"));
548 rv = 1;
549 }else if(!cinp->cin_error){
550 cinp->cin_go = !cinp->cin_go; /* Cause right _IF_ISSKIP() result */
551 rv = a_ccnd_if(v, TRU1);
552 }else
553 rv = 0;
554 NYD_LEAVE;
555 return rv;
558 FL int
559 c_else(void *v){
560 int rv;
561 struct a_ccnd_if_node *cinp;
562 NYD_ENTER;
563 n_UNUSED(v);
565 if((cinp = n_go_data->gdc_ifcond) == NULL || cinp->cin_else){
566 n_err(_("`else' without a matching `if'\n"));
567 rv = 1;
568 }else{
569 cinp->cin_else = TRU1;
570 cinp->cin_go = !cinp->cin_go;
571 rv = 0;
573 NYD_LEAVE;
574 return rv;
577 FL int
578 c_endif(void *v){
579 int rv;
580 struct a_ccnd_if_node *cinp;
581 NYD_ENTER;
582 n_UNUSED(v);
584 if((cinp = n_go_data->gdc_ifcond) == NULL){
585 n_err(_("`endif' without a matching `if'\n"));
586 rv = 1;
587 }else{
588 n_go_data->gdc_ifcond = cinp->cin_outer;
589 n_free(cinp);
590 rv = 0;
592 NYD_LEAVE;
593 return rv;
596 FL bool_t
597 n_cnd_if_isskip(void){
598 bool_t rv;
599 NYD2_ENTER;
601 rv = a_CCND_IF_ISSKIP();
602 NYD2_LEAVE;
603 return rv;
606 FL void
607 n_cnd_if_stack_del(struct n_go_data_ctx *gdcp){
608 struct a_ccnd_if_node *vp, *cinp;
609 NYD2_ENTER;
611 vp = gdcp->gdc_ifcond;
612 gdcp->gdc_ifcond = NULL;
614 while((cinp = vp) != NULL){
615 vp = cinp->cin_outer;
616 n_free(cinp);
618 NYD2_LEAVE;
621 /* s-it-mode */