nail.1: some tweaks
[s-mailx.git] / cmd-cnd.c
blobdbbe40ab8f4a83bb999535649901960af8056c13
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_INTERACTIVE) != 0);
138 break;
139 case '$':{
140 enum{
141 a_NONE,
142 a_ICASE = 1u<<0
143 } flags = a_NONE;
145 /* Look up the value in question, we need it anyway */
146 if(*++cp == '{'){
147 size_t i = strlen(cp);
149 if(i > 0 && cp[i - 1] == '}')
150 cp = savestrbuf(++cp, i -= 2);
151 else
152 goto jesyn;
154 if(noop)
155 lhv = NULL;
156 else
157 lhv = n_var_vlook(cp, TRU1);
159 /* Single argument, "implicit boolean" form? */
160 if(argc == 1){
161 rv = (lhv != NULL);
162 break;
164 op = argv[1];
166 /* Three argument comparison form required, check syntax */
167 emsg = N_("unrecognized condition");
168 if(argc == 2 || (c = op[0]) == '\0')
169 goto jesyn;
171 /* May be modifier */
172 if(c == '@'){
173 for(;;){
174 c = *++op;
175 if(c == 'i')
176 flags |= a_ICASE;
177 else
178 break;
180 if(flags == a_NONE)
181 flags = a_ICASE;
184 if(op[1] == '\0'){
185 if(c != '<' && c != '>')
186 goto jesyn;
187 }else if(c != '-' && op[2] != '\0')
188 goto jesyn;
189 else if(c == '<' || c == '>'){
190 if(op[1] != '=')
191 goto jesyn;
192 }else if(c == '=' || c == '!'){
193 if(op[1] != '=' && op[1] != '%' && op[1] != '@'
194 #ifdef HAVE_REGEX
195 && op[1] != '~'
196 #endif
198 goto jesyn;
199 }else if(c == '-'){
200 if(op[1] == '\0' || op[2] == '\0' || op[3] != '\0')
201 goto jesyn;
202 if(op[1] == 'e'){
203 if(op[2] != 'q')
204 goto jesyn;
205 }else if(op[1] == 'g' || op[1] == 'l'){
206 if(op[2] != 'e' && op[2] != 't')
207 goto jesyn;
208 }else if(op[1] == 'n'){
209 if(op[2] != 'e')
210 goto jesyn;
211 }else
212 goto jesyn;
213 }else
214 goto jesyn;
216 /* The right hand side may also be a variable, more syntax checking */
217 emsg = N_("invalid right hand side");
218 if((rhv = argv[2]) == NULL /* Can't happen */)
219 goto jesyn;
220 if(*rhv == '$'){
221 if(*++rhv == '\0')
222 goto jesyn;
223 else if(*rhv == '{'){
224 size_t i = strlen(rhv);
226 if(i > 0 && rhv[i - 1] == '}')
227 rhv = savestrbuf(++rhv, i -= 2);
228 else{
229 cp = --rhv;
230 goto jesyn;
233 if(noop)
234 rhv = NULL;
235 else
236 rhv = n_var_vlook(cp = rhv, TRU1);
239 /* A null value is treated as the empty string */
240 emsg = NULL;
241 if(lhv == NULL)
242 lhv = n_UNCONST(n_empty);
243 if(rhv == NULL)
244 rhv = n_UNCONST(n_empty);
246 #ifdef HAVE_REGEX
247 if(op[1] == '~'){
248 regex_t re;
249 int s;
251 if((s = regcomp(&re, rhv, REG_EXTENDED | REG_NOSUB |
252 (flags & a_ICASE ? REG_ICASE : 0))) != 0){
253 emsg = savecat(_("invalid regular expression: "),
254 n_regex_err_to_doc(&re, s));
255 goto jesyn_ntr;
257 if(!noop)
258 rv = (regexec(&re, lhv, 0,NULL, 0) == REG_NOMATCH) ^ (c == '=');
259 regfree(&re);
260 }else
261 #endif
262 if(noop)
263 break;
264 else if(op[1] == '%' || op[1] == '@'){
265 if(op[1] == '@')
266 n_OBSOLETE("`if'++: \"=@\" and \"!@\" became \"=%\" and \"!%\"");
267 rv = ((flags & a_ICASE ? asccasestr(lhv, rhv) : strstr(lhv, rhv)
268 ) == NULL) ^ (c == '=');
269 }else if(c == '-'){
270 si64_t lhvi, rhvi;
272 if(*lhv == '\0')
273 lhv = n_0;
274 if(*rhv == '\0')
275 rhv = n_0;
276 if((n_idec_si64_cp(&lhvi, lhv, 0, NULL
277 ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
278 ) != n_IDEC_STATE_CONSUMED || (n_idec_si64_cp(&rhvi, rhv, 0, NULL
279 ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
280 ) != n_IDEC_STATE_CONSUMED){
281 emsg = N_("integer expression expected");
282 goto jesyn;
285 lhvi -= rhvi;
286 switch(op[1]){
287 default:
288 case 'e': rv = (lhvi == 0); break;
289 case 'n': rv = (lhvi != 0); break;
290 case 'l': rv = (op[2] == 't') ? lhvi < 0 : lhvi <= 0; break;
291 case 'g': rv = (op[2] == 't') ? lhvi > 0 : lhvi >= 0; break;
293 break;
294 }else{
295 si32_t scmp;
297 scmp = (flags & a_ICASE) ? asccasecmp(lhv, rhv) : strcmp(lhv, rhv);
298 switch(c){
299 default:
300 case '=': rv = (scmp == 0); break;
301 case '!': rv = (scmp != 0); break;
302 case '<': rv = (op[1] == '\0') ? scmp < 0 : scmp <= 0; break;
303 case '>': rv = (op[1] == '\0') ? scmp > 0 : scmp >= 0; break;
306 } break;
309 if(noop && rv < 0)
310 rv = TRU1;
311 jleave:
312 NYD2_LEAVE;
313 return rv;
316 static si8_t
317 a_ccnd_oif_group(struct a_ccnd_if_ctx *cicp, size_t level, bool_t noop){
318 char const *emsg, *arg0, * const *argv, * const *argv_max_save;
319 size_t i;
320 char unary, c;
321 enum{
322 a_FIRST = 1<<0,
323 a_END_OK = 1<<1,
324 a_NEED_LIST = 1<<2,
326 a_CANNOT_UNARY = a_NEED_LIST,
327 a_CANNOT_OBRACK = a_NEED_LIST,
328 a_CANNOT_CBRACK = a_FIRST,
329 a_CANNOT_LIST = a_FIRST,
330 a_CANNOT_COND = a_NEED_LIST
331 } state;
332 si8_t rv, xrv;
333 NYD2_ENTER;
335 rv = -1;
336 state = a_FIRST;
337 unary = '\0';
338 emsg = NULL;
340 for(;;){
341 arg0 = *(argv = cicp->cic_argv);
342 if(arg0 == NULL){
343 if(!(state & a_END_OK)){
344 emsg = N_("missing expression (premature end)");
345 goto jesyn;
347 if(noop && rv < 0)
348 rv = TRU1;
349 break; /* goto jleave; */
352 switch((c = *arg0)){
353 case '!':
354 if(arg0[1] != '\0')
355 goto jneed_cond;
357 if(state & a_CANNOT_UNARY){
358 emsg = N_("cannot use a unary operator here");
359 goto jesyn;
362 unary = (unary != '\0') ? '\0' : c;
363 state &= ~(a_FIRST | a_END_OK);
364 cicp->cic_argv = ++argv;
365 continue;
367 case '[':
368 case ']':
369 if(arg0[1] != '\0')
370 goto jneed_cond;
372 if(c == '['){
373 if(state & a_CANNOT_OBRACK){
374 emsg = N_("cannot open a group here");
375 goto jesyn;
378 cicp->cic_argv = ++argv;
379 if((xrv = a_ccnd_oif_group(cicp, level + 1, noop)) < 0){
380 rv = xrv;
381 goto jleave;
382 }else if(!noop)
383 rv = (unary != '\0') ? !xrv : xrv;
385 unary = '\0';
386 state &= ~(a_FIRST | a_END_OK);
387 state |= (level == 0 ? a_END_OK : 0) | a_NEED_LIST;
388 continue;
389 }else{
390 if(state & a_CANNOT_CBRACK){
391 emsg = N_("cannot use closing bracket here");
392 goto jesyn;
395 if(level == 0){
396 emsg = N_("no open groups to be closed here");
397 goto jesyn;
400 cicp->cic_argv = ++argv;
401 if(noop && rv < 0)
402 rv = TRU1;
403 goto jleave;/* break;break; */
406 case '|':
407 case '&':
408 if(c != arg0[1] || arg0[2] != '\0')
409 goto jneed_cond;
411 if(state & a_CANNOT_LIST){
412 emsg = N_("cannot use a AND-OR list here");
413 goto jesyn;
416 noop = ((c == '&') ^ (rv == TRU1));
417 state &= ~(a_FIRST | a_END_OK | a_NEED_LIST);
418 cicp->cic_argv = ++argv;
419 continue;
421 default:
422 jneed_cond:
423 if(state & a_CANNOT_COND){
424 emsg = N_("cannot use a `if' condition here");
425 goto jesyn;
428 for(i = 0;; ++i){
429 if((arg0 = argv[i]) == NULL)
430 break;
431 c = *arg0;
432 if(c == '!' && arg0[1] == '\0')
433 break;
434 if((c == '[' || c == ']') && arg0[1] == '\0')
435 break;
436 if((c == '&' || c == '|') && c == arg0[1] && arg0[2] == '\0')
437 break;
439 if(i == 0){
440 emsg = N_("empty conditional group");
441 goto jesyn;
444 argv_max_save = cicp->cic_argv_max;
445 cicp->cic_argv_max = argv + i;
446 if((xrv = a_ccnd_oif_test(cicp, noop)) < 0){
447 rv = xrv;
448 goto jleave;
449 }else if(!noop)
450 rv = (unary != '\0') ? !xrv : xrv;
451 cicp->cic_argv_max = argv_max_save;
453 cicp->cic_argv = (argv += i);
454 unary = '\0';
455 state &= ~a_FIRST;
456 state |= a_END_OK | a_NEED_LIST;
457 break;
461 jleave:
462 NYD2_LEAVE;
463 return rv;
464 jesyn:
465 if(emsg == NULL)
466 emsg = N_("invalid grouping");
467 a_ccnd_oif_error(cicp, V_(emsg), arg0);
468 rv = -1;
469 goto jleave;
472 static int
473 a_ccnd_if(void *v, bool_t iselif){
474 struct a_ccnd_if_ctx cic;
475 char const * const *argv;
476 size_t argc;
477 si8_t xrv, rv;
478 struct a_ccnd_if_node *cinp;
479 NYD_ENTER;
481 if(!iselif){
482 cinp = smalloc(sizeof *cinp);
483 cinp->cin_outer = n_go_data->gdc_ifcond;
484 }else{
485 cinp = n_go_data->gdc_ifcond;
486 assert(cinp != NULL);
488 cinp->cin_error = FAL0;
489 cinp->cin_noop = a_CCND_IF_ISSKIP();
490 cinp->cin_go = TRU1;
491 cinp->cin_else = FAL0;
492 if(!iselif)
493 n_go_data->gdc_ifcond = cinp;
495 if(cinp->cin_noop){
496 rv = 0;
497 goto jleave;
500 /* For heaven's sake, support comments _today_ TODO wyshlist.. */
501 for(argc = 0, argv = v; argv[argc] != NULL; ++argc)
502 if(argv[argc][0] == '#'){
503 char const **nav = salloc(sizeof(char*) * (argc + 1));
504 size_t i;
506 for(i = 0; i < argc; ++i)
507 nav[i] = argv[i];
508 nav[i] = NULL;
509 argv = nav;
510 break;
512 cic.cic_argv_base = cic.cic_argv = argv;
513 cic.cic_argv_max = &argv[argc];
514 xrv = a_ccnd_oif_group(&cic, 0, FAL0);
516 if(xrv >= 0){
517 cinp->cin_go = (bool_t)xrv;
518 rv = 0;
519 }else{
520 cinp->cin_error = cinp->cin_noop = TRU1;
521 rv = 1;
523 jleave:
524 NYD_LEAVE;
525 return rv;
528 FL int
529 c_if(void *v){
530 int rv;
531 NYD_ENTER;
533 rv = a_ccnd_if(v, FAL0);
534 NYD_LEAVE;
535 return rv;
538 FL int
539 c_elif(void *v){
540 struct a_ccnd_if_node *cinp;
541 int rv;
542 NYD_ENTER;
544 if((cinp = n_go_data->gdc_ifcond) == NULL || cinp->cin_else){
545 n_err(_("`elif' without a matching `if'\n"));
546 rv = 1;
547 }else if(!cinp->cin_error){
548 cinp->cin_go = !cinp->cin_go; /* Cause right _IF_ISSKIP() result */
549 rv = a_ccnd_if(v, TRU1);
550 }else
551 rv = 0;
552 NYD_LEAVE;
553 return rv;
556 FL int
557 c_else(void *v){
558 int rv;
559 struct a_ccnd_if_node *cinp;
560 NYD_ENTER;
561 n_UNUSED(v);
563 if((cinp = n_go_data->gdc_ifcond) == NULL || cinp->cin_else){
564 n_err(_("`else' without a matching `if'\n"));
565 rv = 1;
566 }else{
567 cinp->cin_else = TRU1;
568 cinp->cin_go = !cinp->cin_go;
569 rv = 0;
571 NYD_LEAVE;
572 return rv;
575 FL int
576 c_endif(void *v){
577 int rv;
578 struct a_ccnd_if_node *cinp;
579 NYD_ENTER;
580 n_UNUSED(v);
582 if((cinp = n_go_data->gdc_ifcond) == NULL){
583 n_err(_("`endif' without a matching `if'\n"));
584 rv = 1;
585 }else{
586 n_go_data->gdc_ifcond = cinp->cin_outer;
587 free(cinp);
588 rv = 0;
590 NYD_LEAVE;
591 return rv;
594 FL bool_t
595 n_cnd_if_isskip(void){
596 bool_t rv;
597 NYD2_ENTER;
599 rv = a_CCND_IF_ISSKIP();
600 NYD2_LEAVE;
601 return rv;
604 FL void
605 n_cnd_if_stack_del(void *vp){
606 struct a_ccnd_if_node *cinp;
607 NYD2_ENTER;
610 cinp = vp;
611 vp = cinp->cin_outer;
612 free(cinp);
613 }while((cinp = vp) != NULL);
614 NYD2_LEAVE;
617 /* s-it-mode */