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.
19 #define n_FILE cmd_cnd
21 #ifndef HAVE_AMALGAMATION
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 */
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
;
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
);
52 _if_error(struct if_cmd
const *icp
, char const *msg_or_null
,
53 char const *nearby_or_null
)
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
);
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
);
80 _if_test(struct if_cmd
*icp
, bool_t noop
)
82 char const *emsg
, * const *argv
, *cp
, *lhv
, *op
, *rhv
;
91 argc
= PTR2SIZE(icp
->ic_argv_max
- icp
->ic_argv
);
97 } else if (cp
[1] == '\0')
108 _if_error(icp
, emsg
, cp
);
114 switch (boolify(cp
, UIZ_MAX
, -1)) {
115 case 0: rv
= FAL0
; break;
116 case 1: rv
= TRU1
; break;
118 emsg
= N_("Expected a boolean");
123 rv
= !(n_psonce
& n_PSO_SENDMODE
);
126 rv
= ((n_psonce
& n_PSO_SENDMODE
) != 0);
129 if (!asccasecmp(cp
, "true")) /* Beware! */
132 rv
= ((n_psonce
& n_PSO_TTYIN
) != 0);
135 /* Look up the value in question, we need it anyway */
137 size_t i
= strlen(cp
);
139 if(i
> 0 && cp
[i
- 1] == '}')
140 cp
= savestrbuf(++cp
, i
-= 2);
147 lhv
= n_var_vlook(cp
, TRU1
);
149 /* Single argument, "implicit boolean" form? */
156 /* Three argument comparison form required, check syntax */
157 emsg
= N_("unrecognized condition");
158 if (argc
== 2 || (c
= op
[0]) == '\0')
161 if (c
!= '<' && c
!= '>')
163 } else if (op
[2] != '\0')
165 else if (c
== '<' || c
== '>') {
168 } else if (c
== '=' || c
== '!') {
169 if (op
[1] != '=' && op
[1] != '@'
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 */)
185 else if(*rhv
== '{'){
186 size_t i
= strlen(rhv
);
188 if(i
> 0 && rhv
[i
- 1] == '}')
189 rhv
= savestrbuf(++rhv
, i
-= 2);
198 rhv
= n_var_vlook(cp
= rhv
, TRU1
);
201 /* A null value is treated as the empty string */
204 lhv
= n_UNCONST(n_empty
);
206 rhv
= n_UNCONST(n_empty
);
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
));
219 rv
= (regexec(&re
, lhv
, 0,NULL
, 0) == REG_NOMATCH
) ^ (c
== '=');
225 else if (op
[1] == '@')
226 rv
= (asccasestr(lhv
, rhv
) == NULL
) ^ (c
== '=');
228 /* Try to interpret as integers, prefer those, then */
231 if((n_idec_si64_cp(&lhvi
, lhv
, 0, NULL
232 ) & (n_IDEC_STATE_EMASK
| n_IDEC_STATE_CONSUMED
)
233 ) == n_IDEC_STATE_CONSUMED
&& (n_idec_si64_cp(&rhvi
, rhv
, 0, NULL
234 ) & (n_IDEC_STATE_EMASK
| n_IDEC_STATE_CONSUMED
)
235 ) == n_IDEC_STATE_CONSUMED
){
239 case '=': rv
= (lhvi
== 0); break;
240 case '!': rv
= (lhvi
!= 0); break;
241 case '<': rv
= (op
[1] == '\0') ? lhvi
< 0 : lhvi
<= 0; break;
242 case '>': rv
= (op
[1] == '\0') ? lhvi
> 0 : lhvi
>= 0; break;
246 /* It is not an integer, perform string comparison */
249 scmp
= asccasecmp(lhv
, rhv
);
252 case '=': rv
= (scmp
== 0); break;
253 case '!': rv
= (scmp
!= 0); break;
254 case '<': rv
= (op
[1] == '\0') ? scmp
< 0 : scmp
<= 0; break;
255 case '>': rv
= (op
[1] == '\0') ? scmp
> 0 : scmp
>= 0; break;
270 _if_group(struct if_cmd
*icp
, size_t level
, bool_t noop
)
272 char const *emsg
= NULL
, *arg0
, * const *argv
, * const *argv_max_save
;
274 char unary
= '\0', c
;
280 _CANNOT_UNARY
= _NEED_LIST
,
281 _CANNOT_OBRACK
= _NEED_LIST
,
282 _CANNOT_CBRACK
= _FIRST
,
283 _CANNOT_LIST
= _FIRST
,
284 _CANNOT_COND
= _NEED_LIST
290 arg0
= *(argv
= icp
->ic_argv
);
292 if (!(state
& _END_OK
)) {
293 emsg
= N_("missing expression (premature end)");
298 break; /* goto jleave; */
301 switch ((c
= *arg0
)) {
306 if (state
& _CANNOT_UNARY
) {
307 emsg
= N_("cannot use a unary operator here");
311 unary
= (unary
!= '\0') ? '\0' : c
;
312 state
&= ~(_FIRST
| _END_OK
);
313 icp
->ic_argv
= ++argv
;
322 if (state
& _CANNOT_OBRACK
) {
323 emsg
= N_("cannot open a group here");
327 icp
->ic_argv
= ++argv
;
328 if ((xrv
= _if_group(icp
, level
+ 1, noop
)) < 0) {
332 rv
= (unary
!= '\0') ? !xrv
: xrv
;
335 state
&= ~(_FIRST
| _END_OK
);
336 state
|= (level
== 0 ? _END_OK
: 0) | _NEED_LIST
;
339 if (state
& _CANNOT_CBRACK
) {
340 emsg
= N_("cannot use closing bracket here");
345 emsg
= N_("no open groups to be closed here");
349 icp
->ic_argv
= ++argv
;
352 goto jleave
;/* break;break; */
357 if (c
!= arg0
[1] || arg0
[2] != '\0')
360 if (state
& _CANNOT_LIST
) {
361 emsg
= N_("cannot use a AND-OR list here");
365 noop
= ((c
== '&') ^ (rv
== TRU1
));
366 state
&= ~(_FIRST
| _END_OK
| _NEED_LIST
);
367 icp
->ic_argv
= ++argv
;
372 if (state
& _CANNOT_COND
) {
373 emsg
= N_("cannot use a `if' condition here");
378 if ((arg0
= argv
[i
]) == NULL
)
381 if (c
== '!' && arg0
[1] == '\0')
383 if ((c
== '[' || c
== ']') && arg0
[1] == '\0')
385 if ((c
== '&' || c
== '|') && c
== arg0
[1] && arg0
[2] == '\0')
389 emsg
= N_("empty conditional group");
393 argv_max_save
= icp
->ic_argv_max
;
394 icp
->ic_argv_max
= argv
+ i
;
395 if ((xrv
= _if_test(icp
, noop
)) < 0) {
399 rv
= (unary
!= '\0') ? !xrv
: xrv
;
400 icp
->ic_argv_max
= argv_max_save
;
402 icp
->ic_argv
= (argv
+= i
);
405 state
|= _END_OK
| _NEED_LIST
;
415 emsg
= N_("invalid grouping");
416 _if_error(icp
, V_(emsg
), arg0
);
425 char const * const *argv
;
426 struct cond_stack
*csp
;
431 csp
= smalloc(sizeof *csp
);
432 csp
->c_outer
= _cond_stack
;
434 csp
->c_noop
= condstack_isskip();
444 /* For heaven's sake, support comments _today_ TODO wyshlist.. */
445 for (argc
= 0, argv
= v
; argv
[argc
] != NULL
; ++argc
)
446 if(argv
[argc
][0] == '#'){
447 char const **nav
= salloc(sizeof(char*) * (argc
+ 1));
450 for(i
= 0; i
< argc
; ++i
)
456 ic
.ic_argv_base
= ic
.ic_argv
= argv
;
457 ic
.ic_argv_max
= &argv
[argc
];
458 xrv
= _if_group(&ic
, 0, FAL0
);
461 csp
->c_go
= (bool_t
)xrv
;
464 csp
->c_error
= csp
->c_noop
= TRU1
;
475 struct cond_stack
*csp
;
479 if ((csp
= _cond_stack
) == NULL
|| csp
->c_else
) {
480 n_err(_("`elif' without matching `if'\n"));
482 } else if (!csp
->c_error
) {
483 csp
->c_go
= !csp
->c_go
;
485 _cond_stack
->c_outer
= csp
->c_outer
;
500 if (_cond_stack
== NULL
|| _cond_stack
->c_else
) {
501 n_err(_("`else' without matching `if'\n"));
504 _cond_stack
->c_else
= TRU1
;
505 _cond_stack
->c_go
= !_cond_stack
->c_go
;
515 struct cond_stack
*csp
;
520 if ((csp
= _cond_stack
) == NULL
) {
521 n_err(_("`endif' without matching `if'\n"));
524 _cond_stack
= csp
->c_outer
;
533 condstack_isskip(void)
538 rv
= (_cond_stack
!= NULL
&& (_cond_stack
->c_noop
|| !_cond_stack
->c_go
));
544 condstack_release(void)
556 condstack_take(void *self
)
558 struct cond_stack
*csp
;
562 if (!(rv
= ((csp
= _cond_stack
) == NULL
)))
564 _cond_stack
= csp
->c_outer
;
566 } while ((csp
= _cond_stack
) != NULL
);