(BWDIC!) sendout.c:__mta_start(): with SMTP *mta*, do not send to aliases!
[s-mailx.git] / cmd_cnd.c
blob873d5051ede654412dcc690c5f275246be6d9d10
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 struct cond_stack {
26 struct cond_stack *c_outer;
27 bool_t c_error; /* Bad expression, skip entire if..endif */
28 bool_t c_noop; /* Outer stack !c_go, entirely no-op */
29 bool_t c_go; /* Green light */
30 bool_t c_else; /* In `else' clause */
31 ui8_t __dummy[4];
34 struct if_cmd {
35 char const * const *ic_argv_base;
36 char const * const *ic_argv_max; /* BUT: .ic_argv MUST be terminated! */
37 char const * const *ic_argv;
40 static struct cond_stack *_cond_stack;
42 /* */
43 static void _if_error(struct if_cmd const *icp, char const *msg_or_null,
44 char const *nearby_or_null);
46 /* noop and (1) don't work for real, only syntax-check and
47 * (2) non-error return is ignored */
48 static si8_t _if_test(struct if_cmd *icp, bool_t noop);
49 static si8_t _if_group(struct if_cmd *icp, size_t level, bool_t noop);
51 static void
52 _if_error(struct if_cmd const *icp, char const *msg_or_null,
53 char const *nearby_or_null)
55 struct str s;
56 NYD2_ENTER;
58 if (msg_or_null == NULL)
59 msg_or_null = _("invalid expression syntax");
61 if (nearby_or_null != NULL)
62 n_err(_("`if' conditional: %s -- near: %s\n"),
63 msg_or_null, nearby_or_null);
64 else
65 n_err(_("`if' conditional: %s\n"), msg_or_null);
67 if ((n_psonce & n_PSO_INTERACTIVE) || (n_poption & n_PO_D_V)) {
68 str_concat_cpa(&s, icp->ic_argv_base,
69 (*icp->ic_argv_base != NULL ? " " : n_empty));
70 n_err(_(" Expression: %s\n"), s.s);
72 str_concat_cpa(&s, icp->ic_argv, (*icp->ic_argv != NULL ? " " : n_empty));
73 n_err(_(" Stopped at: %s\n"), s.s);
76 NYD2_LEAVE;
79 static si8_t
80 _if_test(struct if_cmd *icp, bool_t noop)
82 char const *emsg, * const *argv, *cp, *lhv, *op, *rhv;
83 size_t argc;
84 char c;
85 si8_t rv;
86 NYD2_ENTER;
88 rv = -1;
89 emsg = NULL;
90 argv = icp->ic_argv;
91 argc = PTR2SIZE(icp->ic_argv_max - icp->ic_argv);
92 cp = argv[0];
94 if (*cp != '$') {
95 if (argc > 1)
96 goto jesyn;
97 } else if (cp[1] == '\0')
98 goto jesyn;
99 else if (argc > 3) {
100 #ifdef HAVE_REGEX
101 jesyn_ntr:
102 #endif
103 if(0){
104 jesyn:
105 if(emsg != NULL)
106 emsg = V_(emsg);
108 _if_error(icp, emsg, cp);
109 goto jleave;
112 switch (*cp) {
113 default:
114 switch (boolify(cp, UIZ_MAX, -1)) {
115 case 0: rv = FAL0; break;
116 case 1: rv = TRU1; break;
117 default:
118 emsg = N_("Expected a boolean");
119 goto jesyn;
121 break;
122 case 'R': case 'r':
123 rv = !(n_psonce & n_PSO_SENDMODE);
124 break;
125 case 'S': case 's':
126 rv = ((n_psonce & n_PSO_SENDMODE) != 0);
127 break;
128 case 'T': case 't':
129 if (!asccasecmp(cp, "true")) /* Beware! */
130 rv = TRU1;
131 else
132 rv = ((n_psonce & n_PSO_TTYIN) != 0);
133 break;
134 case '$':
135 /* Look up the value in question, we need it anyway */
136 if(*++cp == '{'){
137 size_t i = strlen(cp);
139 if(i > 0 && cp[i - 1] == '}')
140 cp = savestrbuf(++cp, i -= 2);
141 else
142 goto jesyn;
144 if(noop)
145 lhv = NULL;
146 else
147 lhv = n_var_vlook(cp, TRU1);
149 /* Single argument, "implicit boolean" form? */
150 if (argc == 1) {
151 rv = (lhv != NULL);
152 break;
154 op = argv[1];
156 /* Three argument comparison form required, check syntax */
157 emsg = N_("unrecognized condition");
158 if (argc == 2 || (c = op[0]) == '\0')
159 goto jesyn;
160 if (op[1] == '\0') {
161 if (c != '<' && c != '>')
162 goto jesyn;
163 } else if (op[2] != '\0')
164 goto jesyn;
165 else if (c == '<' || c == '>') {
166 if (op[1] != '=')
167 goto jesyn;
168 } else if (c == '=' || c == '!') {
169 if (op[1] != '=' && op[1] != '@'
170 #ifdef HAVE_REGEX
171 && op[1] != '~'
172 #endif
174 goto jesyn;
175 } else
176 goto jesyn;
178 /* The right hand side may also be a variable, more syntax checking */
179 emsg = N_("invalid right hand side");
180 if ((rhv = argv[2]) == NULL /* Can't happen */)
181 goto jesyn;
182 if (*rhv == '$') {
183 if (*++rhv == '\0')
184 goto jesyn;
185 else if(*rhv == '{'){
186 size_t i = strlen(rhv);
188 if(i > 0 && rhv[i - 1] == '}')
189 rhv = savestrbuf(++rhv, i -= 2);
190 else{
191 cp = --rhv;
192 goto jesyn;
195 if(noop)
196 rhv = NULL;
197 else
198 rhv = n_var_vlook(cp = rhv, TRU1);
201 /* A null value is treated as the empty string */
202 emsg = NULL;
203 if (lhv == NULL)
204 lhv = n_UNCONST(n_empty);
205 if (rhv == NULL)
206 rhv = n_UNCONST(n_empty);
208 #ifdef HAVE_REGEX
209 if (op[1] == '~') {
210 regex_t re;
211 int s;
213 if((s = regcomp(&re, rhv, REG_EXTENDED | REG_ICASE | REG_NOSUB)) != 0){
214 emsg = savecat(_("invalid regular expression: "),
215 n_regex_err_to_str(&re, s));
216 goto jesyn_ntr;
218 if (!noop)
219 rv = (regexec(&re, lhv, 0,NULL, 0) == REG_NOMATCH) ^ (c == '=');
220 regfree(&re);
221 } else
222 #endif
223 if (noop)
224 break;
225 else if (op[1] == '@')
226 rv = (asccasestr(lhv, rhv) == NULL) ^ (c == '=');
227 else {
228 /* Try to interpret as integers, prefer those, then */
229 si64_t lhvi, rhvi;
231 if((n_idec_si64_cp(&lhvi, lhv, 0, NULL
232 ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
233 ) == n_IDEC_STATE_CONSUMED && (n_idec_si64_cp(&rhvi, rhv, 0, NULL
234 ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
235 ) == n_IDEC_STATE_CONSUMED){
236 lhvi -= rhvi;
237 switch (c) {
238 default:
239 case '=': rv = (lhvi == 0); break;
240 case '!': rv = (lhvi != 0); break;
241 case '<': rv = (op[1] == '\0') ? lhvi < 0 : lhvi <= 0; break;
242 case '>': rv = (op[1] == '\0') ? lhvi > 0 : lhvi >= 0; break;
244 break;
245 }else{
246 /* It is not an integer, perform string comparison */
247 si32_t scmp;
249 scmp = asccasecmp(lhv, rhv);
250 switch (c) {
251 default:
252 case '=': rv = (scmp == 0); break;
253 case '!': rv = (scmp != 0); break;
254 case '<': rv = (op[1] == '\0') ? scmp < 0 : scmp <= 0; break;
255 case '>': rv = (op[1] == '\0') ? scmp > 0 : scmp >= 0; break;
259 break;
262 if (noop && rv < 0)
263 rv = TRU1;
264 jleave:
265 NYD2_LEAVE;
266 return rv;
269 static si8_t
270 _if_group(struct if_cmd *icp, size_t level, bool_t noop)
272 char const *emsg = NULL, *arg0, * const *argv, * const *argv_max_save;
273 size_t i;
274 char unary = '\0', c;
275 enum {
276 _FIRST = 1<<0,
277 _END_OK = 1<<1,
278 _NEED_LIST = 1<<2,
280 _CANNOT_UNARY = _NEED_LIST,
281 _CANNOT_OBRACK = _NEED_LIST,
282 _CANNOT_CBRACK = _FIRST,
283 _CANNOT_LIST = _FIRST,
284 _CANNOT_COND = _NEED_LIST
285 } state = _FIRST;
286 si8_t rv = -1, xrv;
287 NYD2_ENTER;
289 for (;;) {
290 arg0 = *(argv = icp->ic_argv);
291 if (arg0 == NULL) {
292 if (!(state & _END_OK)) {
293 emsg = N_("missing expression (premature end)");
294 goto jesyn;
296 if (noop && rv < 0)
297 rv = TRU1;
298 break; /* goto jleave; */
301 switch ((c = *arg0)) {
302 case '!':
303 if (arg0[1] != '\0')
304 goto jneed_cond;
306 if (state & _CANNOT_UNARY) {
307 emsg = N_("cannot use a unary operator here");
308 goto jesyn;
311 unary = (unary != '\0') ? '\0' : c;
312 state &= ~(_FIRST | _END_OK);
313 icp->ic_argv = ++argv;
314 continue;
316 case '[':
317 case ']':
318 if (arg0[1] != '\0')
319 goto jneed_cond;
321 if (c == '[') {
322 if (state & _CANNOT_OBRACK) {
323 emsg = N_("cannot open a group here");
324 goto jesyn;
327 icp->ic_argv = ++argv;
328 if ((xrv = _if_group(icp, level + 1, noop)) < 0) {
329 rv = xrv;
330 goto jleave;
331 } else if (!noop)
332 rv = (unary != '\0') ? !xrv : xrv;
334 unary = '\0';
335 state &= ~(_FIRST | _END_OK);
336 state |= (level == 0 ? _END_OK : 0) | _NEED_LIST;
337 continue;
338 } else {
339 if (state & _CANNOT_CBRACK) {
340 emsg = N_("cannot use closing bracket here");
341 goto jesyn;
344 if (level == 0) {
345 emsg = N_("no open groups to be closed here");
346 goto jesyn;
349 icp->ic_argv = ++argv;
350 if (noop && rv < 0)
351 rv = TRU1;
352 goto jleave;/* break;break; */
355 case '|':
356 case '&':
357 if (c != arg0[1] || arg0[2] != '\0')
358 goto jneed_cond;
360 if (state & _CANNOT_LIST) {
361 emsg = N_("cannot use a AND-OR list here");
362 goto jesyn;
365 noop = ((c == '&') ^ (rv == TRU1));
366 state &= ~(_FIRST | _END_OK | _NEED_LIST);
367 icp->ic_argv = ++argv;
368 continue;
370 default:
371 jneed_cond:
372 if (state & _CANNOT_COND) {
373 emsg = N_("cannot use a `if' condition here");
374 goto jesyn;
377 for (i = 0;; ++i) {
378 if ((arg0 = argv[i]) == NULL)
379 break;
380 c = *arg0;
381 if (c == '!' && arg0[1] == '\0')
382 break;
383 if ((c == '[' || c == ']') && arg0[1] == '\0')
384 break;
385 if ((c == '&' || c == '|') && c == arg0[1] && arg0[2] == '\0')
386 break;
388 if (i == 0) {
389 emsg = N_("empty conditional group");
390 goto jesyn;
393 argv_max_save = icp->ic_argv_max;
394 icp->ic_argv_max = argv + i;
395 if ((xrv = _if_test(icp, noop)) < 0) {
396 rv = xrv;
397 goto jleave;
398 } else if (!noop)
399 rv = (unary != '\0') ? !xrv : xrv;
400 icp->ic_argv_max = argv_max_save;
402 icp->ic_argv = (argv += i);
403 unary = '\0';
404 state &= ~_FIRST;
405 state |= _END_OK | _NEED_LIST;
406 break;
410 jleave:
411 NYD2_LEAVE;
412 return rv;
413 jesyn:
414 if (emsg == NULL)
415 emsg = N_("invalid grouping");
416 _if_error(icp, V_(emsg), arg0);
417 rv = -1;
418 goto jleave;
421 FL int
422 c_if(void *v)
424 struct if_cmd ic;
425 char const * const *argv;
426 struct cond_stack *csp;
427 size_t argc;
428 si8_t xrv, rv;
429 NYD_ENTER;
431 csp = smalloc(sizeof *csp);
432 csp->c_outer = _cond_stack;
433 csp->c_error = FAL0;
434 csp->c_noop = condstack_isskip();
435 csp->c_go = TRU1;
436 csp->c_else = FAL0;
437 _cond_stack = csp;
439 if (csp->c_noop) {
440 rv = 0;
441 goto jleave;
444 /* For heaven's sake, support comments _today_ TODO wyshlist.. */
445 for (argc = 0, argv = v; argv[argc] != NULL; ++argc)
446 if(argv[argc][0] == '#'){
447 char const **nav = salloc(sizeof(char*) * (argc + 1));
448 size_t i;
450 for(i = 0; i < argc; ++i)
451 nav[i] = argv[i];
452 nav[i] = NULL;
453 argv = nav;
454 break;
456 ic.ic_argv_base = ic.ic_argv = argv;
457 ic.ic_argv_max = &argv[argc];
458 xrv = _if_group(&ic, 0, FAL0);
460 if (xrv >= 0) {
461 csp->c_go = (bool_t)xrv;
462 rv = 0;
463 } else {
464 csp->c_error = csp->c_noop = TRU1;
465 rv = 1;
467 jleave:
468 NYD_LEAVE;
469 return rv;
472 FL int
473 c_elif(void *v)
475 struct cond_stack *csp;
476 int rv;
477 NYD_ENTER;
479 if ((csp = _cond_stack) == NULL || csp->c_else) {
480 n_err(_("`elif' without matching `if'\n"));
481 rv = 1;
482 } else if (!csp->c_error) {
483 csp->c_go = !csp->c_go;
484 rv = c_if(v);
485 _cond_stack->c_outer = csp->c_outer;
486 free(csp);
487 } else
488 rv = 0;
489 NYD_LEAVE;
490 return rv;
493 FL int
494 c_else(void *v)
496 int rv;
497 NYD_ENTER;
498 n_UNUSED(v);
500 if (_cond_stack == NULL || _cond_stack->c_else) {
501 n_err(_("`else' without matching `if'\n"));
502 rv = 1;
503 } else {
504 _cond_stack->c_else = TRU1;
505 _cond_stack->c_go = !_cond_stack->c_go;
506 rv = 0;
508 NYD_LEAVE;
509 return rv;
512 FL int
513 c_endif(void *v)
515 struct cond_stack *csp;
516 int rv;
517 NYD_ENTER;
518 n_UNUSED(v);
520 if ((csp = _cond_stack) == NULL) {
521 n_err(_("`endif' without matching `if'\n"));
522 rv = 1;
523 } else {
524 _cond_stack = csp->c_outer;
525 free(csp);
526 rv = 0;
528 NYD_LEAVE;
529 return rv;
532 FL bool_t
533 condstack_isskip(void)
535 bool_t rv;
536 NYD_ENTER;
538 rv = (_cond_stack != NULL && (_cond_stack->c_noop || !_cond_stack->c_go));
539 NYD_LEAVE;
540 return rv;
543 FL void *
544 condstack_release(void)
546 void *rv;
547 NYD_ENTER;
549 rv = _cond_stack;
550 _cond_stack = NULL;
551 NYD_LEAVE;
552 return rv;
555 FL bool_t
556 condstack_take(void *self)
558 struct cond_stack *csp;
559 bool_t rv;
560 NYD_ENTER;
562 if (!(rv = ((csp = _cond_stack) == NULL)))
563 do {
564 _cond_stack = csp->c_outer;
565 free(csp);
566 } while ((csp = _cond_stack) != NULL);
568 _cond_stack = self;
569 NYD_LEAVE;
570 return rv;
573 /* s-it-mode */