Turn NCL into M(ailx) L(ine) E(ditor)..
[s-mailx.git] / cmd_cnd.c
blob88d118ea2b296f289c04a499f9dfc6eb79f2e02e
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 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 lhv = noop ? NULL : vok_vlook(cp);
137 /* Single argument, "implicit boolean" form? */
138 if (argc == 1) {
139 rv = (lhv != NULL);
140 break;
142 op = argv[1];
144 /* Three argument comparison form required, check syntax */
145 emsg = N_("unrecognized condition");
146 if (argc == 2 || (c = op[0]) == '\0')
147 goto jesyn;
148 if (op[1] == '\0') {
149 if (c != '<' && c != '>')
150 goto jesyn;
151 } else if (op[2] != '\0')
152 goto jesyn;
153 else if (c == '<' || c == '>') {
154 if (op[1] != '=')
155 goto jesyn;
156 } else if (c == '=' || c == '!') {
157 if (op[1] != '=' && op[1] != '@'
158 #ifdef HAVE_REGEX
159 && op[1] != '~'
160 #endif
162 goto jesyn;
163 } else
164 goto jesyn;
166 /* The right hand side may also be a variable, more syntax checking */
167 emsg = N_("invalid right hand side");
168 if ((rhv = argv[2]) == NULL /* Can't happen */)
169 goto jesyn;
170 if (*rhv == '$') {
171 if (*++rhv == '\0')
172 goto jesyn;
173 else if(*rhv == '{'){
174 size_t i = strlen(rhv);
176 if(i > 0 && rhv[i - 1] == '}')
177 rhv = savestrbuf(++rhv, i -= 2);
178 else{
179 cp = --rhv;
180 goto jesyn;
183 rhv = noop ? NULL : vok_vlook(rhv);
186 /* A null value is treated as the empty string */
187 emsg = NULL;
188 if (lhv == NULL)
189 lhv = UNCONST("");
190 if (rhv == NULL)
191 rhv = UNCONST("");
193 #ifdef HAVE_REGEX
194 if (op[1] == '~') {
195 regex_t re;
197 if (regcomp(&re, rhv, REG_EXTENDED | REG_ICASE | REG_NOSUB)) {
198 emsg = N_("invalid regular expression");
199 goto jesyn;
201 if (!noop)
202 rv = (regexec(&re, lhv, 0,NULL, 0) == REG_NOMATCH) ^ (c == '=');
203 regfree(&re);
204 } else
205 #endif
206 if (noop)
207 break;
208 else if (op[1] == '@')
209 rv = (asccasestr(lhv, rhv) == NULL) ^ (c == '=');
210 else {
211 /* Try to interpret as integers, prefer those, then */
212 char *eptr;
213 sl_i sli2, sli1;
215 sli2 = strtol(rhv, &eptr, 0);
216 if (*rhv != '\0' && *eptr == '\0') {
217 sli1 = strtol((cp = lhv), &eptr, 0);
218 if (*cp != '\0' && *eptr == '\0') {
219 sli1 -= sli2;
220 switch (c) {
221 default:
222 case '=': rv = (sli1 == 0); break;
223 case '!': rv = (sli1 != 0); break;
224 case '<': rv = (op[1] == '\0') ? sli1 < 0 : sli1 <= 0; break;
225 case '>': rv = (op[1] == '\0') ? sli1 > 0 : sli1 >= 0; break;
227 break;
231 /* It is not an integer, perform string comparison */
232 sli1 = asccasecmp(lhv, rhv);
233 switch (c) {
234 default:
235 case '=': rv = (sli1 == 0); break;
236 case '!': rv = (sli1 != 0); break;
237 case '<': rv = (op[1] == '\0') ? sli1 < 0 : sli1 <= 0; break;
238 case '>': rv = (op[1] == '\0') ? sli1 > 0 : sli1 >= 0; break;
241 break;
244 if (noop && rv < 0)
245 rv = TRU1;
246 jleave:
247 NYD2_LEAVE;
248 return rv;
251 static si8_t
252 _if_group(struct if_cmd *icp, size_t level, bool_t noop)
254 char const *emsg = NULL, *arg0, * const *argv, * const *argv_max_save;
255 size_t i;
256 char unary = '\0', c;
257 enum {
258 _FIRST = 1<<0,
259 _END_OK = 1<<1,
260 _NEED_LIST = 1<<2,
262 _CANNOT_UNARY = _NEED_LIST,
263 _CANNOT_OBRACK = _NEED_LIST,
264 _CANNOT_CBRACK = _FIRST,
265 _CANNOT_LIST = _FIRST,
266 _CANNOT_COND = _NEED_LIST
267 } state = _FIRST;
268 si8_t rv = -1, xrv;
269 NYD2_ENTER;
271 for (;;) {
272 arg0 = *(argv = icp->ic_argv);
273 if (arg0 == NULL) {
274 if (!(state & _END_OK)) {
275 emsg = N_("missing expression (premature end)");
276 goto jesyn;
278 if (noop && rv < 0)
279 rv = TRU1;
280 break; /* goto jleave; */
283 switch ((c = *arg0)) {
284 case '!':
285 if (arg0[1] != '\0')
286 goto jneed_cond;
288 if (state & _CANNOT_UNARY) {
289 emsg = N_("cannot use a unary operator here");
290 goto jesyn;
293 unary = (unary != '\0') ? '\0' : c;
294 state &= ~(_FIRST | _END_OK);
295 icp->ic_argv = ++argv;
296 continue;
298 case '[':
299 case ']':
300 if (arg0[1] != '\0')
301 goto jneed_cond;
303 if (c == '[') {
304 if (state & _CANNOT_OBRACK) {
305 emsg = N_("cannot open a group here");
306 goto jesyn;
309 icp->ic_argv = ++argv;
310 if ((xrv = _if_group(icp, level + 1, noop)) < 0) {
311 rv = xrv;
312 goto jleave;
313 } else if (!noop)
314 rv = (unary != '\0') ? !xrv : xrv;
316 unary = '\0';
317 state &= ~(_FIRST | _END_OK);
318 state |= (level == 0 ? _END_OK : 0) | _NEED_LIST;
319 continue;
320 } else {
321 if (state & _CANNOT_CBRACK) {
322 emsg = N_("cannot use closing bracket here");
323 goto jesyn;
326 if (level == 0) {
327 emsg = N_("no open groups to be closed here");
328 goto jesyn;
331 icp->ic_argv = ++argv;
332 if (noop && rv < 0)
333 rv = TRU1;
334 goto jleave;/* break;break; */
337 case '|':
338 case '&':
339 if (c != arg0[1] || arg0[2] != '\0')
340 goto jneed_cond;
342 if (state & _CANNOT_LIST) {
343 emsg = N_("cannot use a AND-OR list here");
344 goto jesyn;
347 noop = ((c == '&') ^ (rv == TRU1));
348 state &= ~(_FIRST | _END_OK | _NEED_LIST);
349 icp->ic_argv = ++argv;
350 continue;
352 default:
353 jneed_cond:
354 if (state & _CANNOT_COND) {
355 emsg = N_("cannot use a `if' condition here");
356 goto jesyn;
359 for (i = 0;; ++i) {
360 if ((arg0 = argv[i]) == NULL)
361 break;
362 c = *arg0;
363 if (c == '!' && arg0[1] == '\0')
364 break;
365 if ((c == '[' || c == ']') && arg0[1] == '\0')
366 break;
367 if ((c == '&' || c == '|') && c == arg0[1] && arg0[2] == '\0')
368 break;
370 if (i == 0) {
371 emsg = N_("empty conditional group");
372 goto jesyn;
375 argv_max_save = icp->ic_argv_max;
376 icp->ic_argv_max = argv + i;
377 if ((xrv = _if_test(icp, noop)) < 0) {
378 rv = xrv;
379 goto jleave;
380 } else if (!noop)
381 rv = (unary != '\0') ? !xrv : xrv;
382 icp->ic_argv_max = argv_max_save;
384 icp->ic_argv = (argv += i);
385 unary = '\0';
386 state &= ~_FIRST;
387 state |= _END_OK | _NEED_LIST;
388 break;
392 jleave:
393 NYD2_LEAVE;
394 return rv;
395 jesyn:
396 if (emsg == NULL)
397 emsg = N_("invalid grouping");
398 _if_error(icp, V_(emsg), arg0);
399 rv = -1;
400 goto jleave;
403 FL int
404 c_if(void *v)
406 struct if_cmd ic;
407 struct cond_stack *csp;
408 size_t argc;
409 si8_t xrv, rv = 1;
410 char const * const *argv = v;
411 NYD_ENTER;
413 csp = smalloc(sizeof *csp);
414 csp->c_outer = _cond_stack;
415 csp->c_error = FAL0;
416 csp->c_noop = condstack_isskip();
417 csp->c_go = TRU1;
418 csp->c_else = FAL0;
419 _cond_stack = csp;
421 if (csp->c_noop) {
422 rv = 0;
423 goto jleave;
426 for (argc = 0; argv[argc] != NULL; ++argc)
428 assert(argc > 0); /* (Minimum argument count for command) */
429 ic.ic_argv_base = ic.ic_argv = argv;
430 ic.ic_argv_max = argv + argc;
431 xrv = _if_group(&ic, 0, FAL0);
433 if (xrv >= 0) {
434 csp->c_go = (bool_t)xrv;
435 rv = 0;
436 } else
437 csp->c_error = csp->c_noop = TRU1;
438 jleave:
439 NYD_LEAVE;
440 return rv;
443 FL int
444 c_elif(void *v)
446 struct cond_stack *csp;
447 int rv;
448 NYD_ENTER;
450 if ((csp = _cond_stack) == NULL || csp->c_else) {
451 n_err(_("`elif' without matching `if'\n"));
452 rv = 1;
453 } else if (!csp->c_error) {
454 csp->c_go = !csp->c_go;
455 rv = c_if(v);
456 _cond_stack->c_outer = csp->c_outer;
457 free(csp);
458 } else
459 rv = 0;
460 NYD_LEAVE;
461 return rv;
464 FL int
465 c_else(void *v)
467 int rv;
468 NYD_ENTER;
469 UNUSED(v);
471 if (_cond_stack == NULL || _cond_stack->c_else) {
472 n_err(_("`else' without matching `if'\n"));
473 rv = 1;
474 } else {
475 _cond_stack->c_else = TRU1;
476 _cond_stack->c_go = !_cond_stack->c_go;
477 rv = 0;
479 NYD_LEAVE;
480 return rv;
483 FL int
484 c_endif(void *v)
486 struct cond_stack *csp;
487 int rv;
488 NYD_ENTER;
489 UNUSED(v);
491 if ((csp = _cond_stack) == NULL) {
492 n_err(_("`endif' without matching `if'\n"));
493 rv = 1;
494 } else {
495 _cond_stack = csp->c_outer;
496 free(csp);
497 rv = 0;
499 NYD_LEAVE;
500 return rv;
503 FL bool_t
504 condstack_isskip(void)
506 bool_t rv;
507 NYD_ENTER;
509 rv = (_cond_stack != NULL && (_cond_stack->c_noop || !_cond_stack->c_go));
510 NYD_LEAVE;
511 return rv;
514 FL void *
515 condstack_release(void)
517 void *rv;
518 NYD_ENTER;
520 rv = _cond_stack;
521 _cond_stack = NULL;
522 NYD_LEAVE;
523 return rv;
526 FL bool_t
527 condstack_take(void *self)
529 struct cond_stack *csp;
530 bool_t rv;
531 NYD_ENTER;
533 if (!(rv = ((csp = _cond_stack) == NULL)))
534 do {
535 _cond_stack = csp->c_outer;
536 free(csp);
537 } while ((csp = _cond_stack) != NULL);
539 _cond_stack = self;
540 NYD_LEAVE;
541 return rv;
544 /* s-it-mode */