NEWS: adjust for v14.8.1
[s-mailx.git] / cmd_cnd.c
blob15fcc47344238b234b1772257e3f5667ff90b416
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 fprintf(stderr, _("`if' conditional: %s -- near \"%s\"\n"),
63 msg_or_null, nearby_or_null);
64 else
65 fprintf(stderr, _("`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 fprintf(stderr, _(" Expression: %s\n"), s.s);
72 str_concat_cpa(&s, icp->ic_argv, (*icp->ic_argv != NULL ? " " : ""));
73 fprintf(stderr, _(" Left to parse: %s\n"), s.s);
76 NYD2_LEAVE;
79 static si8_t
80 _if_test(struct if_cmd *icp, bool_t noop)
82 char const * 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, NULL, 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 fprintf(stderr, _("Unrecognized if-keyword: \"%s\"\n"), cp);
110 break;
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 if (argc == 2 || (c = op[0]) == '\0')
139 goto jesyn;
140 if (op[1] == '\0') {
141 if (c != '<' && c != '>')
142 goto jesyn;
143 } else if (op[2] != '\0')
144 goto jesyn;
145 else if (c == '<' || c == '>') {
146 if (op[1] != '=')
147 goto jesyn;
148 } else if (c == '=' || c == '!') {
149 if (op[1] != '=' && op[1] != '@'
150 #ifdef HAVE_REGEX
151 && op[1] != '~'
152 #endif
154 goto jesyn;
157 /* The right hand side may also be a variable, more syntax checking */
158 if ((rhv = argv[2]) == NULL /* Can't happen */)
159 goto jesyn;
160 if (*rhv == '$') {
161 if (*++rhv == '\0')
162 goto jesyn;
163 rhv = noop ? NULL : vok_vlook(rhv);
166 /* A null value is treated as the empty string */
167 if (lhv == NULL)
168 lhv = UNCONST("");
169 if (rhv == NULL)
170 rhv = UNCONST("");
172 #ifdef HAVE_REGEX
173 if (op[1] == '~') {
174 regex_t re;
176 if (regcomp(&re, rhv, REG_EXTENDED | REG_ICASE | REG_NOSUB))
177 goto jesyn;
178 if (!noop)
179 rv = (regexec(&re, lhv, 0,NULL, 0) == REG_NOMATCH) ^ (c == '=');
180 regfree(&re);
181 } else
182 #endif
183 if (noop)
184 break;
185 else if (op[1] == '@')
186 rv = (asccasestr(lhv, rhv) == NULL) ^ (c == '=');
187 else {
188 /* Try to interpret as integers, prefer those, then */
189 char *eptr;
190 sl_i sli2, sli1;
192 sli2 = strtol(rhv, &eptr, 0);
193 if (*rhv != '\0' && *eptr == '\0') {
194 sli1 = strtol((cp = lhv), &eptr, 0);
195 if (*cp != '\0' && *eptr == '\0') {
196 sli1 -= sli2;
197 switch (c) {
198 default:
199 case '=': rv = (sli1 == 0); break;
200 case '!': rv = (sli1 != 0); break;
201 case '<': rv = (op[1] == '\0') ? sli1 < 0 : sli1 <= 0; break;
202 case '>': rv = (op[1] == '\0') ? sli1 > 0 : sli1 >= 0; break;
204 break;
208 /* It is not an integer, perform string comparison */
209 sli1 = asccasecmp(lhv, rhv);
210 switch (c) {
211 default:
212 case '=': rv = (sli1 == 0); break;
213 case '!': rv = (sli1 != 0); break;
214 case '<': rv = (op[1] == '\0') ? sli1 < 0 : sli1 <= 0; break;
215 case '>': rv = (op[1] == '\0') ? sli1 > 0 : sli1 >= 0; break;
218 break;
221 if (noop && rv < 0)
222 rv = TRU1;
223 jleave:
224 NYD2_LEAVE;
225 return rv;
228 static si8_t
229 _if_group(struct if_cmd *icp, size_t level, bool_t noop)
231 char const *emsg = NULL, *arg0, * const *argv, * const *argv_max_save;
232 size_t i;
233 char unary = '\0', c;
234 enum {
235 _FIRST = 1<<0,
236 _END_OK = 1<<1,
237 _NEED_LIST = 1<<2,
239 _CANNOT_UNARY = _NEED_LIST,
240 _CANNOT_OBRACK = _NEED_LIST,
241 _CANNOT_CBRACK = _FIRST,
242 _CANNOT_LIST = _FIRST,
243 _CANNOT_COND = _NEED_LIST
244 } state = _FIRST;
245 si8_t rv = -1, xrv;
246 NYD2_ENTER;
248 for (;;) {
249 arg0 = *(argv = icp->ic_argv);
250 if (arg0 == NULL) {
251 if (!(state & _END_OK)) {
252 emsg = N_("missing expression (premature end)");
253 goto jesyn;
255 if (noop && rv < 0)
256 rv = TRU1;
257 break; /* goto jleave; */
260 switch ((c = *arg0)) {
261 case '!':
262 if (arg0[1] != '\0')
263 goto jneed_cond;
265 if (state & _CANNOT_UNARY) {
266 emsg = N_("cannot use a unary operator here");
267 goto jesyn;
270 unary = (unary != '\0') ? '\0' : c;
271 state &= ~(_FIRST | _END_OK);
272 icp->ic_argv = ++argv;
273 continue;
275 case '[':
276 case ']':
277 if (arg0[1] != '\0')
278 goto jneed_cond;
280 if (c == '[') {
281 if (state & _CANNOT_OBRACK) {
282 emsg = N_("cannot open a group here");
283 goto jesyn;
286 icp->ic_argv = ++argv;
287 if ((xrv = _if_group(icp, level + 1, noop)) < 0) {
288 rv = xrv;
289 goto jleave;
290 } else if (!noop)
291 rv = (unary != '\0') ? !xrv : xrv;
293 unary = '\0';
294 state &= ~(_FIRST | _END_OK);
295 state |= (level == 0 ? _END_OK : 0) | _NEED_LIST;
296 continue;
297 } else {
298 if (state & _CANNOT_CBRACK) {
299 emsg = N_("cannot use closing bracket here");
300 goto jesyn;
303 if (level == 0) {
304 emsg = N_("no open groups to be closed here");
305 goto jesyn;
308 icp->ic_argv = ++argv;
309 if (noop && rv < 0)
310 rv = TRU1;
311 goto jleave;/* break;break; */
314 case '|':
315 case '&':
316 if (c != arg0[1] || arg0[2] != '\0')
317 goto jneed_cond;
319 if (state & _CANNOT_LIST) {
320 emsg = N_("cannot use a AND-OR list here");
321 goto jesyn;
324 noop = ((c == '&') ^ (rv == TRU1));
325 state &= ~(_FIRST | _END_OK | _NEED_LIST);
326 icp->ic_argv = ++argv;
327 continue;
329 default:
330 jneed_cond:
331 if (state & _CANNOT_COND) {
332 emsg = N_("cannot use a `if' condition here");
333 goto jesyn;
336 for (i = 0;; ++i) {
337 if ((arg0 = argv[i]) == NULL)
338 break;
339 c = *arg0;
340 if (c == '!' && arg0[1] == '\0')
341 break;
342 if ((c == '[' || c == ']') && arg0[1] == '\0')
343 break;
344 if ((c == '&' || c == '|') && c == arg0[1] && arg0[2] == '\0')
345 break;
347 if (i == 0) {
348 emsg = N_("empty conditional group");
349 goto jesyn;
352 argv_max_save = icp->ic_argv_max;
353 icp->ic_argv_max = argv + i;
354 if ((xrv = _if_test(icp, noop)) < 0) {
355 rv = xrv;
356 goto jleave;
357 } else if (!noop)
358 rv = (unary != '\0') ? !xrv : xrv;
359 icp->ic_argv_max = argv_max_save;
361 icp->ic_argv = (argv += i);
362 unary = '\0';
363 state &= ~_FIRST;
364 state |= _END_OK | _NEED_LIST;
365 break;
369 jleave:
370 NYD2_LEAVE;
371 return rv;
372 jesyn:
373 if (emsg == NULL)
374 emsg = N_("invalid grouping");
375 _if_error(icp, V_(emsg), arg0);
376 rv = -1;
377 goto jleave;
380 FL int
381 c_if(void *v)
383 struct if_cmd ic;
384 struct cond_stack *csp;
385 size_t argc;
386 si8_t xrv, rv = 1;
387 char const * const *argv = v;
388 NYD_ENTER;
390 csp = smalloc(sizeof *csp);
391 csp->c_outer = _cond_stack;
392 csp->c_error = FAL0;
393 csp->c_noop = condstack_isskip();
394 csp->c_go = TRU1;
395 csp->c_else = FAL0;
396 _cond_stack = csp;
398 if (csp->c_noop) {
399 rv = 0;
400 goto jleave;
403 for (argc = 0; argv[argc] != NULL; ++argc)
405 assert(argc > 0); /* (Minimum argument count for command) */
406 ic.ic_argv_base = ic.ic_argv = argv;
407 ic.ic_argv_max = argv + argc;
408 xrv = _if_group(&ic, 0, FAL0);
410 if (xrv >= 0) {
411 csp->c_go = (bool_t)xrv;
412 rv = 0;
413 } else
414 csp->c_error = csp->c_noop = TRU1;
415 jleave:
416 NYD_LEAVE;
417 return rv;
420 FL int
421 c_elif(void *v)
423 struct cond_stack *csp;
424 int rv;
425 NYD_ENTER;
427 if ((csp = _cond_stack) == NULL || csp->c_else) {
428 fprintf(stderr, _("`elif' without matching `if'\n"));
429 rv = 1;
430 } else if (!csp->c_error) {
431 csp->c_go = !csp->c_go;
432 rv = c_if(v);
433 _cond_stack->c_outer = csp->c_outer;
434 free(csp);
435 } else
436 rv = 0;
437 NYD_LEAVE;
438 return rv;
441 FL int
442 c_else(void *v)
444 int rv;
445 NYD_ENTER;
446 UNUSED(v);
448 if (_cond_stack == NULL || _cond_stack->c_else) {
449 fprintf(stderr, _("`else' without matching `if'\n"));
450 rv = 1;
451 } else {
452 _cond_stack->c_else = TRU1;
453 _cond_stack->c_go = !_cond_stack->c_go;
454 rv = 0;
456 NYD_LEAVE;
457 return rv;
460 FL int
461 c_endif(void *v)
463 struct cond_stack *csp;
464 int rv;
465 NYD_ENTER;
466 UNUSED(v);
468 if ((csp = _cond_stack) == NULL) {
469 fprintf(stderr, _("`endif' without matching `if'\n"));
470 rv = 1;
471 } else {
472 _cond_stack = csp->c_outer;
473 free(csp);
474 rv = 0;
476 NYD_LEAVE;
477 return rv;
480 FL bool_t
481 condstack_isskip(void)
483 bool_t rv;
484 NYD_ENTER;
486 rv = (_cond_stack != NULL && (_cond_stack->c_noop || !_cond_stack->c_go));
487 NYD_LEAVE;
488 return rv;
491 FL void *
492 condstack_release(void)
494 void *rv;
495 NYD_ENTER;
497 rv = _cond_stack;
498 _cond_stack = NULL;
499 NYD_LEAVE;
500 return rv;
503 FL bool_t
504 condstack_take(void *self)
506 struct cond_stack *csp;
507 bool_t rv;
508 NYD_ENTER;
510 if (!(rv = ((csp = _cond_stack) == NULL)))
511 do {
512 _cond_stack = csp->c_outer;
513 free(csp);
514 } while ((csp = _cond_stack) != NULL);
516 _cond_stack = self;
517 NYD_LEAVE;
518 return rv;
521 /* s-it-mode */