*inbox*: if empty, only bypass *folder* to $MAIL or builtin default
[s-mailx.git] / cmd_cnd.c
blobd4aeb8742d57f866e10627f199c65c2e8d0a7032
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Commands: conditional constructs.
4 * Copyright (c) 2014 - 2015 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
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;
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 (options & (OPT_INTERACTIVE | OPT_D_V)) {
68 str_concat_cpa(&s, icp->ic_argv_base,
69 (*icp->ic_argv_base != NULL ? " " : ""));
70 n_err(_(" Expression: %s\n"), s.s);
72 str_concat_cpa(&s, icp->ic_argv, (*icp->ic_argv != NULL ? " " : ""));
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 = NULL, * const *argv, *cp, *lhv, *op, *rhv;
83 size_t argc;
84 char c;
85 si8_t rv = -1;
86 NYD2_ENTER;
88 argv = icp->ic_argv;
89 argc = PTR2SIZE(icp->ic_argv_max - icp->ic_argv);
90 cp = argv[0];
92 if (*cp != '$') {
93 if (argc > 1)
94 goto jesyn;
95 } else if (cp[1] == '\0')
96 goto jesyn;
97 else if (argc > 3) {
98 jesyn:
99 _if_error(icp, emsg, cp);
100 goto jleave;
103 switch (*cp) {
104 default:
105 switch (boolify(cp, UIZ_MAX, -1)) {
106 case 0: rv = FAL0; break;
107 case 1: rv = TRU1; break;
108 default:
109 emsg = N_("Expected a boolean");
110 goto jesyn;
112 break;
113 case 'R': case 'r':
114 rv = !(options & OPT_SENDMODE);
115 break;
116 case 'S': case 's':
117 rv = ((options & OPT_SENDMODE) != 0);
118 break;
119 case 'T': case 't':
120 if (!asccasecmp(cp, "true")) /* Beware! */
121 rv = TRU1;
122 else
123 rv = ((options & OPT_TTYIN) != 0);
124 break;
125 case '$':
126 /* Look up the value in question, we need it anyway */
127 ++cp;
128 lhv = noop ? NULL : vok_vlook(cp);
130 /* Single argument, "implicit boolean" form? */
131 if (argc == 1) {
132 rv = (lhv != NULL);
133 break;
135 op = argv[1];
137 /* Three argument comparison form required, check syntax */
138 emsg = N_("unrecognized condition");
139 if (argc == 2 || (c = op[0]) == '\0')
140 goto jesyn;
141 if (op[1] == '\0') {
142 if (c != '<' && c != '>')
143 goto jesyn;
144 } else if (op[2] != '\0')
145 goto jesyn;
146 else if (c == '<' || c == '>') {
147 if (op[1] != '=')
148 goto jesyn;
149 } else if (c == '=' || c == '!') {
150 if (op[1] != '=' && op[1] != '@'
151 #ifdef HAVE_REGEX
152 && op[1] != '~'
153 #endif
155 goto jesyn;
156 } else
157 goto jesyn;
159 /* The right hand side may also be a variable, more syntax checking */
160 emsg = N_("invalid right hand side");
161 if ((rhv = argv[2]) == NULL /* Can't happen */)
162 goto jesyn;
163 if (*rhv == '$') {
164 if (*++rhv == '\0')
165 goto jesyn;
166 rhv = noop ? NULL : vok_vlook(rhv);
169 /* A null value is treated as the empty string */
170 emsg = NULL;
171 if (lhv == NULL)
172 lhv = UNCONST("");
173 if (rhv == NULL)
174 rhv = UNCONST("");
176 #ifdef HAVE_REGEX
177 if (op[1] == '~') {
178 regex_t re;
180 if (regcomp(&re, rhv, REG_EXTENDED | REG_ICASE | REG_NOSUB)) {
181 emsg = N_("invalid regular expression");
182 goto jesyn;
184 if (!noop)
185 rv = (regexec(&re, lhv, 0,NULL, 0) == REG_NOMATCH) ^ (c == '=');
186 regfree(&re);
187 } else
188 #endif
189 if (noop)
190 break;
191 else if (op[1] == '@')
192 rv = (asccasestr(lhv, rhv) == NULL) ^ (c == '=');
193 else {
194 /* Try to interpret as integers, prefer those, then */
195 char *eptr;
196 sl_i sli2, sli1;
198 sli2 = strtol(rhv, &eptr, 0);
199 if (*rhv != '\0' && *eptr == '\0') {
200 sli1 = strtol((cp = lhv), &eptr, 0);
201 if (*cp != '\0' && *eptr == '\0') {
202 sli1 -= sli2;
203 switch (c) {
204 default:
205 case '=': rv = (sli1 == 0); break;
206 case '!': rv = (sli1 != 0); break;
207 case '<': rv = (op[1] == '\0') ? sli1 < 0 : sli1 <= 0; break;
208 case '>': rv = (op[1] == '\0') ? sli1 > 0 : sli1 >= 0; break;
210 break;
214 /* It is not an integer, perform string comparison */
215 sli1 = asccasecmp(lhv, rhv);
216 switch (c) {
217 default:
218 case '=': rv = (sli1 == 0); break;
219 case '!': rv = (sli1 != 0); break;
220 case '<': rv = (op[1] == '\0') ? sli1 < 0 : sli1 <= 0; break;
221 case '>': rv = (op[1] == '\0') ? sli1 > 0 : sli1 >= 0; break;
224 break;
227 if (noop && rv < 0)
228 rv = TRU1;
229 jleave:
230 NYD2_LEAVE;
231 return rv;
234 static si8_t
235 _if_group(struct if_cmd *icp, size_t level, bool_t noop)
237 char const *emsg = NULL, *arg0, * const *argv, * const *argv_max_save;
238 size_t i;
239 char unary = '\0', c;
240 enum {
241 _FIRST = 1<<0,
242 _END_OK = 1<<1,
243 _NEED_LIST = 1<<2,
245 _CANNOT_UNARY = _NEED_LIST,
246 _CANNOT_OBRACK = _NEED_LIST,
247 _CANNOT_CBRACK = _FIRST,
248 _CANNOT_LIST = _FIRST,
249 _CANNOT_COND = _NEED_LIST
250 } state = _FIRST;
251 si8_t rv = -1, xrv;
252 NYD2_ENTER;
254 for (;;) {
255 arg0 = *(argv = icp->ic_argv);
256 if (arg0 == NULL) {
257 if (!(state & _END_OK)) {
258 emsg = N_("missing expression (premature end)");
259 goto jesyn;
261 if (noop && rv < 0)
262 rv = TRU1;
263 break; /* goto jleave; */
266 switch ((c = *arg0)) {
267 case '!':
268 if (arg0[1] != '\0')
269 goto jneed_cond;
271 if (state & _CANNOT_UNARY) {
272 emsg = N_("cannot use a unary operator here");
273 goto jesyn;
276 unary = (unary != '\0') ? '\0' : c;
277 state &= ~(_FIRST | _END_OK);
278 icp->ic_argv = ++argv;
279 continue;
281 case '[':
282 case ']':
283 if (arg0[1] != '\0')
284 goto jneed_cond;
286 if (c == '[') {
287 if (state & _CANNOT_OBRACK) {
288 emsg = N_("cannot open a group here");
289 goto jesyn;
292 icp->ic_argv = ++argv;
293 if ((xrv = _if_group(icp, level + 1, noop)) < 0) {
294 rv = xrv;
295 goto jleave;
296 } else if (!noop)
297 rv = (unary != '\0') ? !xrv : xrv;
299 unary = '\0';
300 state &= ~(_FIRST | _END_OK);
301 state |= (level == 0 ? _END_OK : 0) | _NEED_LIST;
302 continue;
303 } else {
304 if (state & _CANNOT_CBRACK) {
305 emsg = N_("cannot use closing bracket here");
306 goto jesyn;
309 if (level == 0) {
310 emsg = N_("no open groups to be closed here");
311 goto jesyn;
314 icp->ic_argv = ++argv;
315 if (noop && rv < 0)
316 rv = TRU1;
317 goto jleave;/* break;break; */
320 case '|':
321 case '&':
322 if (c != arg0[1] || arg0[2] != '\0')
323 goto jneed_cond;
325 if (state & _CANNOT_LIST) {
326 emsg = N_("cannot use a AND-OR list here");
327 goto jesyn;
330 noop = ((c == '&') ^ (rv == TRU1));
331 state &= ~(_FIRST | _END_OK | _NEED_LIST);
332 icp->ic_argv = ++argv;
333 continue;
335 default:
336 jneed_cond:
337 if (state & _CANNOT_COND) {
338 emsg = N_("cannot use a `if' condition here");
339 goto jesyn;
342 for (i = 0;; ++i) {
343 if ((arg0 = argv[i]) == NULL)
344 break;
345 c = *arg0;
346 if (c == '!' && arg0[1] == '\0')
347 break;
348 if ((c == '[' || c == ']') && arg0[1] == '\0')
349 break;
350 if ((c == '&' || c == '|') && c == arg0[1] && arg0[2] == '\0')
351 break;
353 if (i == 0) {
354 emsg = N_("empty conditional group");
355 goto jesyn;
358 argv_max_save = icp->ic_argv_max;
359 icp->ic_argv_max = argv + i;
360 if ((xrv = _if_test(icp, noop)) < 0) {
361 rv = xrv;
362 goto jleave;
363 } else if (!noop)
364 rv = (unary != '\0') ? !xrv : xrv;
365 icp->ic_argv_max = argv_max_save;
367 icp->ic_argv = (argv += i);
368 unary = '\0';
369 state &= ~_FIRST;
370 state |= _END_OK | _NEED_LIST;
371 break;
375 jleave:
376 NYD2_LEAVE;
377 return rv;
378 jesyn:
379 if (emsg == NULL)
380 emsg = N_("invalid grouping");
381 _if_error(icp, V_(emsg), arg0);
382 rv = -1;
383 goto jleave;
386 FL int
387 c_if(void *v)
389 struct if_cmd ic;
390 struct cond_stack *csp;
391 size_t argc;
392 si8_t xrv, rv = 1;
393 char const * const *argv = v;
394 NYD_ENTER;
396 csp = smalloc(sizeof *csp);
397 csp->c_outer = _cond_stack;
398 csp->c_error = FAL0;
399 csp->c_noop = condstack_isskip();
400 csp->c_go = TRU1;
401 csp->c_else = FAL0;
402 _cond_stack = csp;
404 if (csp->c_noop) {
405 rv = 0;
406 goto jleave;
409 for (argc = 0; argv[argc] != NULL; ++argc)
411 assert(argc > 0); /* (Minimum argument count for command) */
412 ic.ic_argv_base = ic.ic_argv = argv;
413 ic.ic_argv_max = argv + argc;
414 xrv = _if_group(&ic, 0, FAL0);
416 if (xrv >= 0) {
417 csp->c_go = (bool_t)xrv;
418 rv = 0;
419 } else
420 csp->c_error = csp->c_noop = TRU1;
421 jleave:
422 NYD_LEAVE;
423 return rv;
426 FL int
427 c_elif(void *v)
429 struct cond_stack *csp;
430 int rv;
431 NYD_ENTER;
433 if ((csp = _cond_stack) == NULL || csp->c_else) {
434 n_err(_("`elif' without matching `if'\n"));
435 rv = 1;
436 } else if (!csp->c_error) {
437 csp->c_go = !csp->c_go;
438 rv = c_if(v);
439 _cond_stack->c_outer = csp->c_outer;
440 free(csp);
441 } else
442 rv = 0;
443 NYD_LEAVE;
444 return rv;
447 FL int
448 c_else(void *v)
450 int rv;
451 NYD_ENTER;
452 UNUSED(v);
454 if (_cond_stack == NULL || _cond_stack->c_else) {
455 n_err(_("`else' without matching `if'\n"));
456 rv = 1;
457 } else {
458 _cond_stack->c_else = TRU1;
459 _cond_stack->c_go = !_cond_stack->c_go;
460 rv = 0;
462 NYD_LEAVE;
463 return rv;
466 FL int
467 c_endif(void *v)
469 struct cond_stack *csp;
470 int rv;
471 NYD_ENTER;
472 UNUSED(v);
474 if ((csp = _cond_stack) == NULL) {
475 n_err(_("`endif' without matching `if'\n"));
476 rv = 1;
477 } else {
478 _cond_stack = csp->c_outer;
479 free(csp);
480 rv = 0;
482 NYD_LEAVE;
483 return rv;
486 FL bool_t
487 condstack_isskip(void)
489 bool_t rv;
490 NYD_ENTER;
492 rv = (_cond_stack != NULL && (_cond_stack->c_noop || !_cond_stack->c_go));
493 NYD_LEAVE;
494 return rv;
497 FL void *
498 condstack_release(void)
500 void *rv;
501 NYD_ENTER;
503 rv = _cond_stack;
504 _cond_stack = NULL;
505 NYD_LEAVE;
506 return rv;
509 FL bool_t
510 condstack_take(void *self)
512 struct cond_stack *csp;
513 bool_t rv;
514 NYD_ENTER;
516 if (!(rv = ((csp = _cond_stack) == NULL)))
517 do {
518 _cond_stack = csp->c_outer;
519 free(csp);
520 } while ((csp = _cond_stack) != NULL);
522 _cond_stack = self;
523 NYD_LEAVE;
524 return rv;
527 /* s-it-mode */