1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Commands: conditional constructs.
4 * Copyright (c) 2014 - 2018 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
25 #define a_CCND_IF_ISSKIP() \
26 (n_go_data->gdc_ifcond != NULL &&\
27 (((struct a_ccnd_if_node*)n_go_data->gdc_ifcond)->cin_noop ||\
28 !((struct a_ccnd_if_node*)n_go_data->gdc_ifcond)->cin_go))
30 struct a_ccnd_if_node
{
31 struct a_ccnd_if_node
*cin_outer
;
32 bool_t cin_error
; /* Bad expression, skip entire if..endif */
33 bool_t cin_noop
; /* Outer stack !cin_go, entirely no-op */
34 bool_t cin_go
; /* Green light */
35 bool_t cin_else
; /* In `else' clause */
40 char const * const *cic_argv_base
;
41 char const * const *cic_argv_max
; /* BUT: .cic_argv MUST be terminated! */
42 char const * const *cic_argv
;
46 static void a_ccnd_oif_error(struct a_ccnd_if_ctx
const *cicp
,
47 char const *msg_or_null
, char const *nearby_or_null
);
49 /* noop and (1) don't work for real, only syntax-check and
50 * (2) non-error return is ignored */
51 static si8_t
a_ccnd_oif_test(struct a_ccnd_if_ctx
*cicp
, bool_t noop
);
52 static si8_t
a_ccnd_oif_group(struct a_ccnd_if_ctx
*cicp
, size_t level
,
55 /* Shared `if' / `elif' implementation */
56 static int a_ccnd_if(void *v
, bool_t iselif
);
59 a_ccnd_oif_error(struct a_ccnd_if_ctx
const *cicp
, char const *msg_or_null
,
60 char const *nearby_or_null
){
64 if(msg_or_null
== NULL
)
65 msg_or_null
= _("invalid expression syntax");
67 if(nearby_or_null
!= NULL
)
68 n_err(_("`if' conditional: %s -- near: %s\n"),
69 msg_or_null
, nearby_or_null
);
71 n_err(_("`if' conditional: %s\n"), msg_or_null
);
73 if((n_psonce
& n_PSO_INTERACTIVE
) || (n_poption
& n_PO_D_V
)){
74 str_concat_cpa(&s
, cicp
->cic_argv_base
,
75 (*cicp
->cic_argv_base
!= NULL
? " " : n_empty
));
76 n_err(_(" Expression: %s\n"), s
.s
);
78 str_concat_cpa(&s
, cicp
->cic_argv
,
79 (*cicp
->cic_argv
!= NULL
? " " : n_empty
));
80 n_err(_(" Stopped at: %s\n"), s
.s
);
86 a_ccnd_oif_test(struct a_ccnd_if_ctx
*cicp
, bool_t noop
){
87 char const *emsg
, * const *argv
, *cp
, *lhv
, *op
, *rhv
;
95 argv
= cicp
->cic_argv
;
96 argc
= PTR2SIZE(cicp
->cic_argv_max
- cicp
->cic_argv
);
102 }else if(cp
[1] == '\0')
113 a_ccnd_oif_error(cicp
, emsg
, cp
);
119 switch(boolify(cp
, UIZ_MAX
, -1)){
120 case 0: rv
= FAL0
; break;
121 case 1: rv
= TRU1
; break;
123 emsg
= N_("Expected a boolean");
128 rv
= !(n_psonce
& n_PSO_SENDMODE
);
131 rv
= ((n_psonce
& n_PSO_SENDMODE
) != 0);
134 if(!asccasecmp(cp
, "true")) /* Beware! */
137 rv
= ((n_psonce
& n_PSO_INTERACTIVE
) != 0);
145 /* Look up the value in question, we need it anyway */
147 size_t i
= strlen(cp
);
149 if(i
> 0 && cp
[i
- 1] == '}')
150 cp
= savestrbuf(++cp
, i
-= 2);
157 lhv
= n_var_vlook(cp
, TRU1
);
159 /* Single argument, "implicit boolean" form? */
166 /* Three argument comparison form required, check syntax */
167 emsg
= N_("unrecognized condition");
168 if(argc
== 2 || (c
= op
[0]) == '\0')
171 /* May be modifier */
185 if(c
!= '<' && c
!= '>')
187 }else if(c
!= '-' && op
[2] != '\0')
189 else if(c
== '<' || c
== '>'){
192 }else if(c
== '=' || c
== '!'){
193 if(op
[1] != '=' && op
[1] != '%' && op
[1] != '@'
200 if(op
[1] == '\0' || op
[2] == '\0' || op
[3] != '\0')
205 }else if(op
[1] == 'g' || op
[1] == 'l'){
206 if(op
[2] != 'e' && op
[2] != 't')
208 }else if(op
[1] == 'n'){
216 /* The right hand side may also be a variable, more syntax checking */
217 emsg
= N_("invalid right hand side");
218 if((rhv
= argv
[2]) == NULL
/* Can't happen */)
223 else if(*rhv
== '{'){
224 size_t i
= strlen(rhv
);
226 if(i
> 0 && rhv
[i
- 1] == '}')
227 rhv
= savestrbuf(++rhv
, i
-= 2);
236 rhv
= n_var_vlook(cp
= rhv
, TRU1
);
239 /* A null value is treated as the empty string */
242 lhv
= n_UNCONST(n_empty
);
244 rhv
= n_UNCONST(n_empty
);
251 if((s
= regcomp(&re
, rhv
, REG_EXTENDED
| REG_NOSUB
|
252 (flags
& a_ICASE
? REG_ICASE
: 0))) != 0){
253 emsg
= savecat(_("invalid regular expression: "),
254 n_regex_err_to_doc(NULL
, s
));
258 rv
= (regexec(&re
, lhv
, 0,NULL
, 0) == REG_NOMATCH
) ^ (c
== '=');
264 else if(op
[1] == '%' || op
[1] == '@'){
266 n_OBSOLETE("`if'++: \"=@\" and \"!@\" became \"=%\" and \"!%\"");
267 rv
= ((flags
& a_ICASE
? asccasestr(lhv
, rhv
) : strstr(lhv
, rhv
)
268 ) == NULL
) ^ (c
== '=');
276 if((n_idec_si64_cp(&lhvi
, lhv
, 0, NULL
277 ) & (n_IDEC_STATE_EMASK
| n_IDEC_STATE_CONSUMED
)
278 ) != n_IDEC_STATE_CONSUMED
|| (n_idec_si64_cp(&rhvi
, rhv
, 0, NULL
279 ) & (n_IDEC_STATE_EMASK
| n_IDEC_STATE_CONSUMED
)
280 ) != n_IDEC_STATE_CONSUMED
){
281 emsg
= N_("integer expression expected");
288 case 'e': rv
= (lhvi
== 0); break;
289 case 'n': rv
= (lhvi
!= 0); break;
290 case 'l': rv
= (op
[2] == 't') ? lhvi
< 0 : lhvi
<= 0; break;
291 case 'g': rv
= (op
[2] == 't') ? lhvi
> 0 : lhvi
>= 0; break;
297 scmp
= (flags
& a_ICASE
) ? asccasecmp(lhv
, rhv
) : strcmp(lhv
, rhv
);
300 case '=': rv
= (scmp
== 0); break;
301 case '!': rv
= (scmp
!= 0); break;
302 case '<': rv
= (op
[1] == '\0') ? scmp
< 0 : scmp
<= 0; break;
303 case '>': rv
= (op
[1] == '\0') ? scmp
> 0 : scmp
>= 0; break;
317 a_ccnd_oif_group(struct a_ccnd_if_ctx
*cicp
, size_t level
, bool_t noop
){
318 char const *emsg
, *arg0
, * const *argv
, * const *argv_max_save
;
326 a_CANNOT_UNARY
= a_NEED_LIST
,
327 a_CANNOT_OBRACK
= a_NEED_LIST
,
328 a_CANNOT_CBRACK
= a_FIRST
,
329 a_CANNOT_LIST
= a_FIRST
,
330 a_CANNOT_COND
= a_NEED_LIST
341 arg0
= *(argv
= cicp
->cic_argv
);
343 if(!(state
& a_END_OK
)){
344 emsg
= N_("missing expression (premature end)");
349 break; /* goto jleave; */
357 if(state
& a_CANNOT_UNARY
){
358 emsg
= N_("cannot use a unary operator here");
362 unary
= (unary
!= '\0') ? '\0' : c
;
363 state
&= ~(a_FIRST
| a_END_OK
);
364 cicp
->cic_argv
= ++argv
;
373 if(state
& a_CANNOT_OBRACK
){
374 emsg
= N_("cannot open a group here");
378 cicp
->cic_argv
= ++argv
;
379 if((xrv
= a_ccnd_oif_group(cicp
, level
+ 1, noop
)) < 0){
383 rv
= (unary
!= '\0') ? !xrv
: xrv
;
386 state
&= ~(a_FIRST
| a_END_OK
);
387 state
|= (level
== 0 ? a_END_OK
: 0) | a_NEED_LIST
;
390 if(state
& a_CANNOT_CBRACK
){
391 emsg
= N_("cannot use closing bracket here");
396 emsg
= N_("no open groups to be closed here");
400 cicp
->cic_argv
= ++argv
;
403 goto jleave
;/* break;break; */
408 if(c
!= arg0
[1] || arg0
[2] != '\0')
411 if(state
& a_CANNOT_LIST
){
412 emsg
= N_("cannot use a AND-OR list here");
416 noop
= ((c
== '&') ^ (rv
== TRU1
));
417 state
&= ~(a_FIRST
| a_END_OK
| a_NEED_LIST
);
418 cicp
->cic_argv
= ++argv
;
423 if(state
& a_CANNOT_COND
){
424 emsg
= N_("cannot use a `if' condition here");
429 if((arg0
= argv
[i
]) == NULL
)
432 if(c
== '!' && arg0
[1] == '\0')
434 if((c
== '[' || c
== ']') && arg0
[1] == '\0')
436 if((c
== '&' || c
== '|') && c
== arg0
[1] && arg0
[2] == '\0')
440 emsg
= N_("empty conditional group");
444 argv_max_save
= cicp
->cic_argv_max
;
445 cicp
->cic_argv_max
= argv
+ i
;
446 if((xrv
= a_ccnd_oif_test(cicp
, noop
)) < 0){
450 rv
= (unary
!= '\0') ? !xrv
: xrv
;
451 cicp
->cic_argv_max
= argv_max_save
;
453 cicp
->cic_argv
= (argv
+= i
);
456 state
|= a_END_OK
| a_NEED_LIST
;
466 emsg
= N_("invalid grouping");
467 a_ccnd_oif_error(cicp
, V_(emsg
), arg0
);
473 a_ccnd_if(void *v
, bool_t iselif
){
474 struct a_ccnd_if_ctx cic
;
475 char const * const *argv
;
478 struct a_ccnd_if_node
*cinp
;
482 cinp
= smalloc(sizeof *cinp
);
483 cinp
->cin_outer
= n_go_data
->gdc_ifcond
;
485 cinp
= n_go_data
->gdc_ifcond
;
486 assert(cinp
!= NULL
);
488 cinp
->cin_error
= FAL0
;
489 cinp
->cin_noop
= a_CCND_IF_ISSKIP();
491 cinp
->cin_else
= FAL0
;
493 n_go_data
->gdc_ifcond
= cinp
;
500 /* For heaven's sake, support comments _today_ TODO wyshlist.. */
501 for(argc
= 0, argv
= v
; argv
[argc
] != NULL
; ++argc
)
502 if(argv
[argc
][0] == '#'){
503 char const **nav
= salloc(sizeof(char*) * (argc
+ 1));
506 for(i
= 0; i
< argc
; ++i
)
512 cic
.cic_argv_base
= cic
.cic_argv
= argv
;
513 cic
.cic_argv_max
= &argv
[argc
];
514 xrv
= a_ccnd_oif_group(&cic
, 0, FAL0
);
517 cinp
->cin_go
= (bool_t
)xrv
;
520 cinp
->cin_error
= cinp
->cin_noop
= TRU1
;
533 rv
= a_ccnd_if(v
, FAL0
);
540 struct a_ccnd_if_node
*cinp
;
544 if((cinp
= n_go_data
->gdc_ifcond
) == NULL
|| cinp
->cin_else
){
545 n_err(_("`elif' without a matching `if'\n"));
547 }else if(!cinp
->cin_error
){
548 cinp
->cin_go
= !cinp
->cin_go
; /* Cause right _IF_ISSKIP() result */
549 rv
= a_ccnd_if(v
, TRU1
);
559 struct a_ccnd_if_node
*cinp
;
563 if((cinp
= n_go_data
->gdc_ifcond
) == NULL
|| cinp
->cin_else
){
564 n_err(_("`else' without a matching `if'\n"));
567 cinp
->cin_else
= TRU1
;
568 cinp
->cin_go
= !cinp
->cin_go
;
578 struct a_ccnd_if_node
*cinp
;
582 if((cinp
= n_go_data
->gdc_ifcond
) == NULL
){
583 n_err(_("`endif' without a matching `if'\n"));
586 n_go_data
->gdc_ifcond
= cinp
->cin_outer
;
595 n_cnd_if_isskip(void){
599 rv
= a_CCND_IF_ISSKIP();
605 n_cnd_if_stack_del(struct n_go_data_ctx
*gdcp
){
606 struct a_ccnd_if_node
*vp
, *cinp
;
609 vp
= gdcp
->gdc_ifcond
;
610 gdcp
->gdc_ifcond
= NULL
;
612 while((cinp
= vp
) != NULL
){
613 vp
= cinp
->cin_outer
;