Some: stop adding $TMPDIR to ENV, automatic now with new var-handling
[s-mailx.git] / cmd_cnd.c
blob11360933903ec10f86a2fd2c9ffe8ec576d9f889
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Commands: conditional constructs.
4 * Copyright (c) 2014 - 2016 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 (options & (OPT_INTERACTIVE | OPT_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 = 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 if(*++cp == '{'){
128 size_t i = strlen(cp);
130 if(i > 0 && cp[i - 1] == '}')
131 cp = savestrbuf(++cp, i -= 2);
132 else
133 goto jesyn;
135 if(noop)
136 lhv = NULL;
137 else if((lhv = vok_vlook(cp)) == NULL)
138 lhv = getenv(cp);
140 /* Single argument, "implicit boolean" form? */
141 if (argc == 1) {
142 rv = (lhv != NULL);
143 break;
145 op = argv[1];
147 /* Three argument comparison form required, check syntax */
148 emsg = N_("unrecognized condition");
149 if (argc == 2 || (c = op[0]) == '\0')
150 goto jesyn;
151 if (op[1] == '\0') {
152 if (c != '<' && c != '>')
153 goto jesyn;
154 } else if (op[2] != '\0')
155 goto jesyn;
156 else if (c == '<' || c == '>') {
157 if (op[1] != '=')
158 goto jesyn;
159 } else if (c == '=' || c == '!') {
160 if (op[1] != '=' && op[1] != '@'
161 #ifdef HAVE_REGEX
162 && op[1] != '~'
163 #endif
165 goto jesyn;
166 } else
167 goto jesyn;
169 /* The right hand side may also be a variable, more syntax checking */
170 emsg = N_("invalid right hand side");
171 if ((rhv = argv[2]) == NULL /* Can't happen */)
172 goto jesyn;
173 if (*rhv == '$') {
174 if (*++rhv == '\0')
175 goto jesyn;
176 else if(*rhv == '{'){
177 size_t i = strlen(rhv);
179 if(i > 0 && rhv[i - 1] == '}')
180 rhv = savestrbuf(++rhv, i -= 2);
181 else{
182 cp = --rhv;
183 goto jesyn;
186 if(noop)
187 rhv = NULL;
188 else if((rhv = vok_vlook(cp = rhv)) == NULL)
189 rhv = getenv(cp);
192 /* A null value is treated as the empty string */
193 emsg = NULL;
194 if (lhv == NULL)
195 lhv = n_UNCONST(n_empty);
196 if (rhv == NULL)
197 rhv = n_UNCONST(n_empty);
199 #ifdef HAVE_REGEX
200 if (op[1] == '~') {
201 regex_t re;
203 if (regcomp(&re, rhv, REG_EXTENDED | REG_ICASE | REG_NOSUB)) {
204 emsg = N_("invalid regular expression");
205 goto jesyn;
207 if (!noop)
208 rv = (regexec(&re, lhv, 0,NULL, 0) == REG_NOMATCH) ^ (c == '=');
209 regfree(&re);
210 } else
211 #endif
212 if (noop)
213 break;
214 else if (op[1] == '@')
215 rv = (asccasestr(lhv, rhv) == NULL) ^ (c == '=');
216 else {
217 /* Try to interpret as integers, prefer those, then */
218 char *eptr;
219 sl_i sli2, sli1;
221 sli2 = strtol(rhv, &eptr, 0);
222 if (*rhv != '\0' && *eptr == '\0') {
223 sli1 = strtol((cp = lhv), &eptr, 0);
224 if (*cp != '\0' && *eptr == '\0') {
225 sli1 -= sli2;
226 switch (c) {
227 default:
228 case '=': rv = (sli1 == 0); break;
229 case '!': rv = (sli1 != 0); break;
230 case '<': rv = (op[1] == '\0') ? sli1 < 0 : sli1 <= 0; break;
231 case '>': rv = (op[1] == '\0') ? sli1 > 0 : sli1 >= 0; break;
233 break;
237 /* It is not an integer, perform string comparison */
238 sli1 = asccasecmp(lhv, rhv);
239 switch (c) {
240 default:
241 case '=': rv = (sli1 == 0); break;
242 case '!': rv = (sli1 != 0); break;
243 case '<': rv = (op[1] == '\0') ? sli1 < 0 : sli1 <= 0; break;
244 case '>': rv = (op[1] == '\0') ? sli1 > 0 : sli1 >= 0; break;
247 break;
250 if (noop && rv < 0)
251 rv = TRU1;
252 jleave:
253 NYD2_LEAVE;
254 return rv;
257 static si8_t
258 _if_group(struct if_cmd *icp, size_t level, bool_t noop)
260 char const *emsg = NULL, *arg0, * const *argv, * const *argv_max_save;
261 size_t i;
262 char unary = '\0', c;
263 enum {
264 _FIRST = 1<<0,
265 _END_OK = 1<<1,
266 _NEED_LIST = 1<<2,
268 _CANNOT_UNARY = _NEED_LIST,
269 _CANNOT_OBRACK = _NEED_LIST,
270 _CANNOT_CBRACK = _FIRST,
271 _CANNOT_LIST = _FIRST,
272 _CANNOT_COND = _NEED_LIST
273 } state = _FIRST;
274 si8_t rv = -1, xrv;
275 NYD2_ENTER;
277 for (;;) {
278 arg0 = *(argv = icp->ic_argv);
279 if (arg0 == NULL) {
280 if (!(state & _END_OK)) {
281 emsg = N_("missing expression (premature end)");
282 goto jesyn;
284 if (noop && rv < 0)
285 rv = TRU1;
286 break; /* goto jleave; */
289 switch ((c = *arg0)) {
290 case '!':
291 if (arg0[1] != '\0')
292 goto jneed_cond;
294 if (state & _CANNOT_UNARY) {
295 emsg = N_("cannot use a unary operator here");
296 goto jesyn;
299 unary = (unary != '\0') ? '\0' : c;
300 state &= ~(_FIRST | _END_OK);
301 icp->ic_argv = ++argv;
302 continue;
304 case '[':
305 case ']':
306 if (arg0[1] != '\0')
307 goto jneed_cond;
309 if (c == '[') {
310 if (state & _CANNOT_OBRACK) {
311 emsg = N_("cannot open a group here");
312 goto jesyn;
315 icp->ic_argv = ++argv;
316 if ((xrv = _if_group(icp, level + 1, noop)) < 0) {
317 rv = xrv;
318 goto jleave;
319 } else if (!noop)
320 rv = (unary != '\0') ? !xrv : xrv;
322 unary = '\0';
323 state &= ~(_FIRST | _END_OK);
324 state |= (level == 0 ? _END_OK : 0) | _NEED_LIST;
325 continue;
326 } else {
327 if (state & _CANNOT_CBRACK) {
328 emsg = N_("cannot use closing bracket here");
329 goto jesyn;
332 if (level == 0) {
333 emsg = N_("no open groups to be closed here");
334 goto jesyn;
337 icp->ic_argv = ++argv;
338 if (noop && rv < 0)
339 rv = TRU1;
340 goto jleave;/* break;break; */
343 case '|':
344 case '&':
345 if (c != arg0[1] || arg0[2] != '\0')
346 goto jneed_cond;
348 if (state & _CANNOT_LIST) {
349 emsg = N_("cannot use a AND-OR list here");
350 goto jesyn;
353 noop = ((c == '&') ^ (rv == TRU1));
354 state &= ~(_FIRST | _END_OK | _NEED_LIST);
355 icp->ic_argv = ++argv;
356 continue;
358 default:
359 jneed_cond:
360 if (state & _CANNOT_COND) {
361 emsg = N_("cannot use a `if' condition here");
362 goto jesyn;
365 for (i = 0;; ++i) {
366 if ((arg0 = argv[i]) == NULL)
367 break;
368 c = *arg0;
369 if (c == '!' && arg0[1] == '\0')
370 break;
371 if ((c == '[' || c == ']') && arg0[1] == '\0')
372 break;
373 if ((c == '&' || c == '|') && c == arg0[1] && arg0[2] == '\0')
374 break;
376 if (i == 0) {
377 emsg = N_("empty conditional group");
378 goto jesyn;
381 argv_max_save = icp->ic_argv_max;
382 icp->ic_argv_max = argv + i;
383 if ((xrv = _if_test(icp, noop)) < 0) {
384 rv = xrv;
385 goto jleave;
386 } else if (!noop)
387 rv = (unary != '\0') ? !xrv : xrv;
388 icp->ic_argv_max = argv_max_save;
390 icp->ic_argv = (argv += i);
391 unary = '\0';
392 state &= ~_FIRST;
393 state |= _END_OK | _NEED_LIST;
394 break;
398 jleave:
399 NYD2_LEAVE;
400 return rv;
401 jesyn:
402 if (emsg == NULL)
403 emsg = N_("invalid grouping");
404 _if_error(icp, V_(emsg), arg0);
405 rv = -1;
406 goto jleave;
409 FL int
410 c_if(void *v)
412 struct if_cmd ic;
413 char const * const *argv;
414 struct cond_stack *csp;
415 size_t argc;
416 si8_t xrv, rv;
417 NYD_ENTER;
419 csp = smalloc(sizeof *csp);
420 csp->c_outer = _cond_stack;
421 csp->c_error = FAL0;
422 csp->c_noop = condstack_isskip();
423 csp->c_go = TRU1;
424 csp->c_else = FAL0;
425 _cond_stack = csp;
427 if (csp->c_noop) {
428 rv = 0;
429 goto jleave;
432 /* For heaven's sake, support comments _today_ TODO wyshlist.. */
433 for (argc = 0, argv = v; argv[argc] != NULL; ++argc)
434 if(argv[argc][0] == '#'){
435 char const **nav = salloc(sizeof(char*) * (argc + 1));
436 size_t i;
438 for(i = 0; i < argc; ++i)
439 nav[i] = argv[i];
440 nav[i] = NULL;
441 argv = nav;
442 break;
444 ic.ic_argv_base = ic.ic_argv = argv;
445 ic.ic_argv_max = &argv[argc];
446 xrv = _if_group(&ic, 0, FAL0);
448 if (xrv >= 0) {
449 csp->c_go = (bool_t)xrv;
450 rv = 0;
451 } else {
452 csp->c_error = csp->c_noop = TRU1;
453 rv = 1;
455 jleave:
456 NYD_LEAVE;
457 return rv;
460 FL int
461 c_elif(void *v)
463 struct cond_stack *csp;
464 int rv;
465 NYD_ENTER;
467 if ((csp = _cond_stack) == NULL || csp->c_else) {
468 n_err(_("`elif' without matching `if'\n"));
469 rv = 1;
470 } else if (!csp->c_error) {
471 csp->c_go = !csp->c_go;
472 rv = c_if(v);
473 _cond_stack->c_outer = csp->c_outer;
474 free(csp);
475 } else
476 rv = 0;
477 NYD_LEAVE;
478 return rv;
481 FL int
482 c_else(void *v)
484 int rv;
485 NYD_ENTER;
486 n_UNUSED(v);
488 if (_cond_stack == NULL || _cond_stack->c_else) {
489 n_err(_("`else' without matching `if'\n"));
490 rv = 1;
491 } else {
492 _cond_stack->c_else = TRU1;
493 _cond_stack->c_go = !_cond_stack->c_go;
494 rv = 0;
496 NYD_LEAVE;
497 return rv;
500 FL int
501 c_endif(void *v)
503 struct cond_stack *csp;
504 int rv;
505 NYD_ENTER;
506 n_UNUSED(v);
508 if ((csp = _cond_stack) == NULL) {
509 n_err(_("`endif' without matching `if'\n"));
510 rv = 1;
511 } else {
512 _cond_stack = csp->c_outer;
513 free(csp);
514 rv = 0;
516 NYD_LEAVE;
517 return rv;
520 FL bool_t
521 condstack_isskip(void)
523 bool_t rv;
524 NYD_ENTER;
526 rv = (_cond_stack != NULL && (_cond_stack->c_noop || !_cond_stack->c_go));
527 NYD_LEAVE;
528 return rv;
531 FL void *
532 condstack_release(void)
534 void *rv;
535 NYD_ENTER;
537 rv = _cond_stack;
538 _cond_stack = NULL;
539 NYD_LEAVE;
540 return rv;
543 FL bool_t
544 condstack_take(void *self)
546 struct cond_stack *csp;
547 bool_t rv;
548 NYD_ENTER;
550 if (!(rv = ((csp = _cond_stack) == NULL)))
551 do {
552 _cond_stack = csp->c_outer;
553 free(csp);
554 } while ((csp = _cond_stack) != NULL);
556 _cond_stack = self;
557 NYD_LEAVE;
558 return rv;
561 /* s-it-mode */