Drop requirement of BYTE_ORDER knowledge ++ for OPT_CROSS_BUILD..
[s-mailx.git] / cmd_cnd.c
blobc9764ccb630fbb84d6e0af4d55b66e62f6411f66
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 char *eptr;
230 sl_i sli2, sli1;
232 sli2 = strtol(rhv, &eptr, 0);
233 if (*rhv != '\0' && *eptr == '\0') {
234 sli1 = strtol((cp = lhv), &eptr, 0);
235 if (*cp != '\0' && *eptr == '\0') {
236 sli1 -= sli2;
237 switch (c) {
238 default:
239 case '=': rv = (sli1 == 0); break;
240 case '!': rv = (sli1 != 0); break;
241 case '<': rv = (op[1] == '\0') ? sli1 < 0 : sli1 <= 0; break;
242 case '>': rv = (op[1] == '\0') ? sli1 > 0 : sli1 >= 0; break;
244 break;
248 /* It is not an integer, perform string comparison */
249 sli1 = asccasecmp(lhv, rhv);
250 switch (c) {
251 default:
252 case '=': rv = (sli1 == 0); break;
253 case '!': rv = (sli1 != 0); break;
254 case '<': rv = (op[1] == '\0') ? sli1 < 0 : sli1 <= 0; break;
255 case '>': rv = (op[1] == '\0') ? sli1 > 0 : sli1 >= 0; break;
258 break;
261 if (noop && rv < 0)
262 rv = TRU1;
263 jleave:
264 NYD2_LEAVE;
265 return rv;
268 static si8_t
269 _if_group(struct if_cmd *icp, size_t level, bool_t noop)
271 char const *emsg = NULL, *arg0, * const *argv, * const *argv_max_save;
272 size_t i;
273 char unary = '\0', c;
274 enum {
275 _FIRST = 1<<0,
276 _END_OK = 1<<1,
277 _NEED_LIST = 1<<2,
279 _CANNOT_UNARY = _NEED_LIST,
280 _CANNOT_OBRACK = _NEED_LIST,
281 _CANNOT_CBRACK = _FIRST,
282 _CANNOT_LIST = _FIRST,
283 _CANNOT_COND = _NEED_LIST
284 } state = _FIRST;
285 si8_t rv = -1, xrv;
286 NYD2_ENTER;
288 for (;;) {
289 arg0 = *(argv = icp->ic_argv);
290 if (arg0 == NULL) {
291 if (!(state & _END_OK)) {
292 emsg = N_("missing expression (premature end)");
293 goto jesyn;
295 if (noop && rv < 0)
296 rv = TRU1;
297 break; /* goto jleave; */
300 switch ((c = *arg0)) {
301 case '!':
302 if (arg0[1] != '\0')
303 goto jneed_cond;
305 if (state & _CANNOT_UNARY) {
306 emsg = N_("cannot use a unary operator here");
307 goto jesyn;
310 unary = (unary != '\0') ? '\0' : c;
311 state &= ~(_FIRST | _END_OK);
312 icp->ic_argv = ++argv;
313 continue;
315 case '[':
316 case ']':
317 if (arg0[1] != '\0')
318 goto jneed_cond;
320 if (c == '[') {
321 if (state & _CANNOT_OBRACK) {
322 emsg = N_("cannot open a group here");
323 goto jesyn;
326 icp->ic_argv = ++argv;
327 if ((xrv = _if_group(icp, level + 1, noop)) < 0) {
328 rv = xrv;
329 goto jleave;
330 } else if (!noop)
331 rv = (unary != '\0') ? !xrv : xrv;
333 unary = '\0';
334 state &= ~(_FIRST | _END_OK);
335 state |= (level == 0 ? _END_OK : 0) | _NEED_LIST;
336 continue;
337 } else {
338 if (state & _CANNOT_CBRACK) {
339 emsg = N_("cannot use closing bracket here");
340 goto jesyn;
343 if (level == 0) {
344 emsg = N_("no open groups to be closed here");
345 goto jesyn;
348 icp->ic_argv = ++argv;
349 if (noop && rv < 0)
350 rv = TRU1;
351 goto jleave;/* break;break; */
354 case '|':
355 case '&':
356 if (c != arg0[1] || arg0[2] != '\0')
357 goto jneed_cond;
359 if (state & _CANNOT_LIST) {
360 emsg = N_("cannot use a AND-OR list here");
361 goto jesyn;
364 noop = ((c == '&') ^ (rv == TRU1));
365 state &= ~(_FIRST | _END_OK | _NEED_LIST);
366 icp->ic_argv = ++argv;
367 continue;
369 default:
370 jneed_cond:
371 if (state & _CANNOT_COND) {
372 emsg = N_("cannot use a `if' condition here");
373 goto jesyn;
376 for (i = 0;; ++i) {
377 if ((arg0 = argv[i]) == NULL)
378 break;
379 c = *arg0;
380 if (c == '!' && arg0[1] == '\0')
381 break;
382 if ((c == '[' || c == ']') && arg0[1] == '\0')
383 break;
384 if ((c == '&' || c == '|') && c == arg0[1] && arg0[2] == '\0')
385 break;
387 if (i == 0) {
388 emsg = N_("empty conditional group");
389 goto jesyn;
392 argv_max_save = icp->ic_argv_max;
393 icp->ic_argv_max = argv + i;
394 if ((xrv = _if_test(icp, noop)) < 0) {
395 rv = xrv;
396 goto jleave;
397 } else if (!noop)
398 rv = (unary != '\0') ? !xrv : xrv;
399 icp->ic_argv_max = argv_max_save;
401 icp->ic_argv = (argv += i);
402 unary = '\0';
403 state &= ~_FIRST;
404 state |= _END_OK | _NEED_LIST;
405 break;
409 jleave:
410 NYD2_LEAVE;
411 return rv;
412 jesyn:
413 if (emsg == NULL)
414 emsg = N_("invalid grouping");
415 _if_error(icp, V_(emsg), arg0);
416 rv = -1;
417 goto jleave;
420 FL int
421 c_if(void *v)
423 struct if_cmd ic;
424 char const * const *argv;
425 struct cond_stack *csp;
426 size_t argc;
427 si8_t xrv, rv;
428 NYD_ENTER;
430 csp = smalloc(sizeof *csp);
431 csp->c_outer = _cond_stack;
432 csp->c_error = FAL0;
433 csp->c_noop = condstack_isskip();
434 csp->c_go = TRU1;
435 csp->c_else = FAL0;
436 _cond_stack = csp;
438 if (csp->c_noop) {
439 rv = 0;
440 goto jleave;
443 /* For heaven's sake, support comments _today_ TODO wyshlist.. */
444 for (argc = 0, argv = v; argv[argc] != NULL; ++argc)
445 if(argv[argc][0] == '#'){
446 char const **nav = salloc(sizeof(char*) * (argc + 1));
447 size_t i;
449 for(i = 0; i < argc; ++i)
450 nav[i] = argv[i];
451 nav[i] = NULL;
452 argv = nav;
453 break;
455 ic.ic_argv_base = ic.ic_argv = argv;
456 ic.ic_argv_max = &argv[argc];
457 xrv = _if_group(&ic, 0, FAL0);
459 if (xrv >= 0) {
460 csp->c_go = (bool_t)xrv;
461 rv = 0;
462 } else {
463 csp->c_error = csp->c_noop = TRU1;
464 rv = 1;
466 jleave:
467 NYD_LEAVE;
468 return rv;
471 FL int
472 c_elif(void *v)
474 struct cond_stack *csp;
475 int rv;
476 NYD_ENTER;
478 if ((csp = _cond_stack) == NULL || csp->c_else) {
479 n_err(_("`elif' without matching `if'\n"));
480 rv = 1;
481 } else if (!csp->c_error) {
482 csp->c_go = !csp->c_go;
483 rv = c_if(v);
484 _cond_stack->c_outer = csp->c_outer;
485 free(csp);
486 } else
487 rv = 0;
488 NYD_LEAVE;
489 return rv;
492 FL int
493 c_else(void *v)
495 int rv;
496 NYD_ENTER;
497 n_UNUSED(v);
499 if (_cond_stack == NULL || _cond_stack->c_else) {
500 n_err(_("`else' without matching `if'\n"));
501 rv = 1;
502 } else {
503 _cond_stack->c_else = TRU1;
504 _cond_stack->c_go = !_cond_stack->c_go;
505 rv = 0;
507 NYD_LEAVE;
508 return rv;
511 FL int
512 c_endif(void *v)
514 struct cond_stack *csp;
515 int rv;
516 NYD_ENTER;
517 n_UNUSED(v);
519 if ((csp = _cond_stack) == NULL) {
520 n_err(_("`endif' without matching `if'\n"));
521 rv = 1;
522 } else {
523 _cond_stack = csp->c_outer;
524 free(csp);
525 rv = 0;
527 NYD_LEAVE;
528 return rv;
531 FL bool_t
532 condstack_isskip(void)
534 bool_t rv;
535 NYD_ENTER;
537 rv = (_cond_stack != NULL && (_cond_stack->c_noop || !_cond_stack->c_go));
538 NYD_LEAVE;
539 return rv;
542 FL void *
543 condstack_release(void)
545 void *rv;
546 NYD_ENTER;
548 rv = _cond_stack;
549 _cond_stack = NULL;
550 NYD_LEAVE;
551 return rv;
554 FL bool_t
555 condstack_take(void *self)
557 struct cond_stack *csp;
558 bool_t rv;
559 NYD_ENTER;
561 if (!(rv = ((csp = _cond_stack) == NULL)))
562 do {
563 _cond_stack = csp->c_outer;
564 free(csp);
565 } while ((csp = _cond_stack) != NULL);
567 _cond_stack = self;
568 NYD_LEAVE;
569 return rv;
572 /* s-it-mode */