No hyphen-minus prefix as indicator for "internal" namespace
[s-mailx.git] / cmd_cnd.c
blobbbb5ae8af9c48c2864f218fa005d276973fb063a
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_CONDSTACK_ISSKIP() \
26 (a_ccnd_if_stack != NULL &&\
27 (a_ccnd_if_stack->cin_noop || !a_ccnd_if_stack->cin_go))
29 struct a_ccnd_if_node{
30 struct a_ccnd_if_node *cin_outer;
31 bool_t cin_error; /* Bad expression, skip entire if..endif */
32 bool_t cin_noop; /* Outer stack !cin_go, entirely no-op */
33 bool_t cin_go; /* Green light */
34 bool_t cin_else; /* In `else' clause */
35 ui8_t cin__dummy[4];
38 struct a_ccnd_if_ctx{
39 char const * const *cic_argv_base;
40 char const * const *cic_argv_max; /* BUT: .cic_argv MUST be terminated! */
41 char const * const *cic_argv;
44 static struct a_ccnd_if_node *a_ccnd_if_stack; /* TODO -> member of Lex CTX! */
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(boolify(cp, UIZ_MAX, -1)){
121 case 0: rv = FAL0; break;
122 case 1: rv = TRU1; 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_TTYIN) != 0);
139 break;
140 case '$':
141 /* Look up the value in question, we need it anyway */
142 if(*++cp == '{'){
143 size_t i = strlen(cp);
145 if(i > 0 && cp[i - 1] == '}')
146 cp = savestrbuf(++cp, i -= 2);
147 else
148 goto jesyn;
150 if(noop)
151 lhv = NULL;
152 else
153 lhv = n_var_vlook(cp, TRU1);
155 /* Single argument, "implicit boolean" form? */
156 if(argc == 1){
157 rv = (lhv != NULL);
158 break;
160 op = argv[1];
162 /* Three argument comparison form required, check syntax */
163 emsg = N_("unrecognized condition");
164 if(argc == 2 || (c = op[0]) == '\0')
165 goto jesyn;
166 if(op[1] == '\0'){
167 if(c != '<' && c != '>')
168 goto jesyn;
169 }else if(c != '-' && op[2] != '\0')
170 goto jesyn;
171 else if(c == '<' || c == '>'){
172 if(op[1] != '=')
173 goto jesyn;
174 }else if(c == '=' || c == '!'){
175 if(op[1] != '=' && op[1] != '@'
176 #ifdef HAVE_REGEX
177 && op[1] != '~'
178 #endif
180 goto jesyn;
181 }else if(c == '-'){
182 if(op[1] == '\0' || op[2] == '\0' || op[3] != '\0')
183 goto jesyn;
184 if(op[1] == 'e'){
185 if(op[2] != 'q')
186 goto jesyn;
187 }else if(op[1] == 'g' || op[1] == 'l'){
188 if(op[2] != 'e' && op[2] != 't')
189 goto jesyn;
190 }else if(op[1] == 'n'){
191 if(op[2] != 'e')
192 goto jesyn;
193 }else
194 goto jesyn;
195 }else
196 goto jesyn;
198 /* The right hand side may also be a variable, more syntax checking */
199 emsg = N_("invalid right hand side");
200 if((rhv = argv[2]) == NULL /* Can't happen */)
201 goto jesyn;
202 if(*rhv == '$'){
203 if(*++rhv == '\0')
204 goto jesyn;
205 else if(*rhv == '{'){
206 size_t i = strlen(rhv);
208 if(i > 0 && rhv[i - 1] == '}')
209 rhv = savestrbuf(++rhv, i -= 2);
210 else{
211 cp = --rhv;
212 goto jesyn;
215 if(noop)
216 rhv = NULL;
217 else
218 rhv = n_var_vlook(cp = rhv, TRU1);
221 /* A null value is treated as the empty string */
222 emsg = NULL;
223 if(lhv == NULL)
224 lhv = n_UNCONST(n_empty);
225 if(rhv == NULL)
226 rhv = n_UNCONST(n_empty);
228 #ifdef HAVE_REGEX
229 if(op[1] == '~'){
230 regex_t re;
231 int s;
233 if((s = regcomp(&re, rhv, REG_EXTENDED | REG_ICASE | REG_NOSUB)) != 0){
234 emsg = savecat(_("invalid regular expression: "),
235 n_regex_err_to_str(&re, s));
236 goto jesyn_ntr;
238 if(!noop)
239 rv = (regexec(&re, lhv, 0,NULL, 0) == REG_NOMATCH) ^ (c == '=');
240 regfree(&re);
241 }else
242 #endif
243 if(noop)
244 break;
245 else if(op[1] == '@')
246 rv = (asccasestr(lhv, rhv) == NULL) ^ (c == '=');
247 else if(c == '-'){
248 si64_t lhvi, rhvi;
250 if(*lhv == '\0')
251 lhv = n_0;
252 if(*rhv == '\0')
253 rhv = n_0;
254 if((n_idec_si64_cp(&lhvi, lhv, 0, NULL
255 ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
256 ) != n_IDEC_STATE_CONSUMED || (n_idec_si64_cp(&rhvi, rhv, 0, NULL
257 ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
258 ) != n_IDEC_STATE_CONSUMED){
259 emsg = N_("integer expression expected");
260 goto jesyn;
263 lhvi -= rhvi;
264 switch(op[1]){
265 default:
266 case 'e': rv = (lhvi == 0); break;
267 case 'n': rv = (lhvi != 0); break;
268 case 'l': rv = (op[2] == 't') ? lhvi < 0 : lhvi <= 0; break;
269 case 'g': rv = (op[2] == 't') ? lhvi > 0 : lhvi >= 0; break;
271 break;
272 }else{
273 si32_t scmp;
275 scmp = asccasecmp(lhv, rhv);
276 switch(c){
277 default:
278 case '=': rv = (scmp == 0); break;
279 case '!': rv = (scmp != 0); break;
280 case '<': rv = (op[1] == '\0') ? scmp < 0 : scmp <= 0; break;
281 case '>': rv = (op[1] == '\0') ? scmp > 0 : scmp >= 0; break;
284 break;
287 if(noop && rv < 0)
288 rv = TRU1;
289 jleave:
290 NYD2_LEAVE;
291 return rv;
294 static si8_t
295 a_ccnd_oif_group(struct a_ccnd_if_ctx *cicp, size_t level, bool_t noop){
296 char const *emsg, *arg0, * const *argv, * const *argv_max_save;
297 size_t i;
298 char unary, c;
299 enum{
300 a_FIRST = 1<<0,
301 a_END_OK = 1<<1,
302 a_NEED_LIST = 1<<2,
304 a_CANNOT_UNARY = a_NEED_LIST,
305 a_CANNOT_OBRACK = a_NEED_LIST,
306 a_CANNOT_CBRACK = a_FIRST,
307 a_CANNOT_LIST = a_FIRST,
308 a_CANNOT_COND = a_NEED_LIST
309 } state;
310 si8_t rv, xrv;
311 NYD2_ENTER;
313 rv = -1;
314 state = a_FIRST;
315 unary = '\0';
316 emsg = NULL;
318 for(;;){
319 arg0 = *(argv = cicp->cic_argv);
320 if(arg0 == NULL){
321 if(!(state & a_END_OK)){
322 emsg = N_("missing expression (premature end)");
323 goto jesyn;
325 if(noop && rv < 0)
326 rv = TRU1;
327 break; /* goto jleave; */
330 switch((c = *arg0)){
331 case '!':
332 if(arg0[1] != '\0')
333 goto jneed_cond;
335 if(state & a_CANNOT_UNARY){
336 emsg = N_("cannot use a unary operator here");
337 goto jesyn;
340 unary = (unary != '\0') ? '\0' : c;
341 state &= ~(a_FIRST | a_END_OK);
342 cicp->cic_argv = ++argv;
343 continue;
345 case '[':
346 case ']':
347 if(arg0[1] != '\0')
348 goto jneed_cond;
350 if(c == '['){
351 if(state & a_CANNOT_OBRACK){
352 emsg = N_("cannot open a group here");
353 goto jesyn;
356 cicp->cic_argv = ++argv;
357 if((xrv = a_ccnd_oif_group(cicp, level + 1, noop)) < 0){
358 rv = xrv;
359 goto jleave;
360 }else if(!noop)
361 rv = (unary != '\0') ? !xrv : xrv;
363 unary = '\0';
364 state &= ~(a_FIRST | a_END_OK);
365 state |= (level == 0 ? a_END_OK : 0) | a_NEED_LIST;
366 continue;
367 }else{
368 if(state & a_CANNOT_CBRACK){
369 emsg = N_("cannot use closing bracket here");
370 goto jesyn;
373 if(level == 0){
374 emsg = N_("no open groups to be closed here");
375 goto jesyn;
378 cicp->cic_argv = ++argv;
379 if(noop && rv < 0)
380 rv = TRU1;
381 goto jleave;/* break;break; */
384 case '|':
385 case '&':
386 if(c != arg0[1] || arg0[2] != '\0')
387 goto jneed_cond;
389 if(state & a_CANNOT_LIST){
390 emsg = N_("cannot use a AND-OR list here");
391 goto jesyn;
394 noop = ((c == '&') ^ (rv == TRU1));
395 state &= ~(a_FIRST | a_END_OK | a_NEED_LIST);
396 cicp->cic_argv = ++argv;
397 continue;
399 default:
400 jneed_cond:
401 if(state & a_CANNOT_COND){
402 emsg = N_("cannot use a `if' condition here");
403 goto jesyn;
406 for(i = 0;; ++i){
407 if((arg0 = argv[i]) == NULL)
408 break;
409 c = *arg0;
410 if(c == '!' && arg0[1] == '\0')
411 break;
412 if((c == '[' || c == ']') && arg0[1] == '\0')
413 break;
414 if((c == '&' || c == '|') && c == arg0[1] && arg0[2] == '\0')
415 break;
417 if(i == 0){
418 emsg = N_("empty conditional group");
419 goto jesyn;
422 argv_max_save = cicp->cic_argv_max;
423 cicp->cic_argv_max = argv + i;
424 if((xrv = a_ccnd_oif_test(cicp, noop)) < 0){
425 rv = xrv;
426 goto jleave;
427 }else if(!noop)
428 rv = (unary != '\0') ? !xrv : xrv;
429 cicp->cic_argv_max = argv_max_save;
431 cicp->cic_argv = (argv += i);
432 unary = '\0';
433 state &= ~a_FIRST;
434 state |= a_END_OK | a_NEED_LIST;
435 break;
439 jleave:
440 NYD2_LEAVE;
441 return rv;
442 jesyn:
443 if(emsg == NULL)
444 emsg = N_("invalid grouping");
445 a_ccnd_oif_error(cicp, V_(emsg), arg0);
446 rv = -1;
447 goto jleave;
450 static int
451 a_ccnd_if(void *v, bool_t iselif){
452 struct a_ccnd_if_ctx cic;
453 char const * const *argv;
454 size_t argc;
455 si8_t xrv, rv;
456 struct a_ccnd_if_node *cinp;
457 NYD_ENTER;
459 if(!iselif){
460 cinp = smalloc(sizeof *cinp);
461 cinp->cin_outer = a_ccnd_if_stack;
462 }else{
463 cinp = a_ccnd_if_stack;
464 assert(cinp != NULL);
466 cinp->cin_error = FAL0;
467 cinp->cin_noop = a_CCND_CONDSTACK_ISSKIP();
468 cinp->cin_go = TRU1;
469 cinp->cin_else = FAL0;
470 if(!iselif)
471 a_ccnd_if_stack = cinp;
473 if(cinp->cin_noop){
474 rv = 0;
475 goto jleave;
478 /* For heaven's sake, support comments _today_ TODO wyshlist.. */
479 for(argc = 0, argv = v; argv[argc] != NULL; ++argc)
480 if(argv[argc][0] == '#'){
481 char const **nav = salloc(sizeof(char*) * (argc + 1));
482 size_t i;
484 for(i = 0; i < argc; ++i)
485 nav[i] = argv[i];
486 nav[i] = NULL;
487 argv = nav;
488 break;
490 cic.cic_argv_base = cic.cic_argv = argv;
491 cic.cic_argv_max = &argv[argc];
492 xrv = a_ccnd_oif_group(&cic, 0, FAL0);
494 if(xrv >= 0){
495 cinp->cin_go = (bool_t)xrv;
496 rv = 0;
497 }else{
498 cinp->cin_error = cinp->cin_noop = TRU1;
499 rv = 1;
501 jleave:
502 NYD_LEAVE;
503 return rv;
506 FL int
507 c_if(void *v){
508 int rv;
509 NYD_ENTER;
511 rv = a_ccnd_if(v, FAL0);
512 NYD_LEAVE;
513 return rv;
516 FL int
517 c_elif(void *v){
518 struct a_ccnd_if_node *cinp;
519 int rv;
520 NYD_ENTER;
522 if((cinp = a_ccnd_if_stack) == NULL || cinp->cin_else){
523 n_err(_("`elif' without a matching `if'\n"));
524 rv = 1;
525 }else if(!cinp->cin_error){
526 cinp->cin_go = !cinp->cin_go; /* Cause right CONDSTACK_ISSKIP() result */
527 rv = a_ccnd_if(v, TRU1);
528 }else
529 rv = 0;
530 NYD_LEAVE;
531 return rv;
534 FL int
535 c_else(void *v){
536 int rv;
537 NYD_ENTER;
538 n_UNUSED(v);
540 if(a_ccnd_if_stack == NULL || a_ccnd_if_stack->cin_else){
541 n_err(_("`else' without a matching `if'\n"));
542 rv = 1;
543 }else{
544 a_ccnd_if_stack->cin_else = TRU1;
545 a_ccnd_if_stack->cin_go = !a_ccnd_if_stack->cin_go;
546 rv = 0;
548 NYD_LEAVE;
549 return rv;
552 FL int
553 c_endif(void *v){
554 struct a_ccnd_if_node *cinp;
555 int rv;
556 NYD_ENTER;
557 n_UNUSED(v);
559 if((cinp = a_ccnd_if_stack) == NULL){
560 n_err(_("`endif' without a matching `if'\n"));
561 rv = 1;
562 }else{
563 a_ccnd_if_stack = cinp->cin_outer;
564 free(cinp);
565 rv = 0;
567 NYD_LEAVE;
568 return rv;
571 FL bool_t
572 condstack_isskip(void){
573 bool_t rv;
574 NYD2_ENTER;
576 rv = a_CCND_CONDSTACK_ISSKIP();
577 NYD2_LEAVE;
578 return rv;
581 FL void *
582 condstack_release(void){
583 void *rv;
584 NYD2_ENTER;
586 rv = a_ccnd_if_stack;
587 a_ccnd_if_stack = NULL;
588 NYD2_LEAVE;
589 return rv;
592 FL bool_t
593 condstack_take(void *self){
594 struct a_ccnd_if_node *cinp;
595 bool_t rv;
596 NYD2_ENTER;
598 if(!(rv = ((cinp = a_ccnd_if_stack) == NULL)))
600 a_ccnd_if_stack = cinp->cin_outer;
601 free(cinp);
602 }while((cinp = a_ccnd_if_stack) != NULL);
604 a_ccnd_if_stack = self;
605 NYD2_LEAVE;
606 return rv;
609 /* s-it-mode */