1use serde::{Deserialize, Serialize};
25
26use crate::compiler::{
27 lexer::{tokenizer::TokenInfo, word::Word, Token},
28 CompileError, ErrorType,
29};
30
31use super::{
32 actions::{action_convert::Convert, action_vacation::TestVacation},
33 expr::{parser::ExpressionParser, tokenizer::Tokenizer, Expression, UnaryOperator},
34 instruction::{CompilerState, Instruction},
35 tests::{
36 test_address::TestAddress,
37 test_body::TestBody,
38 test_date::{TestCurrentDate, TestDate},
39 test_duplicate::TestDuplicate,
40 test_envelope::TestEnvelope,
41 test_exists::TestExists,
42 test_extlists::TestValidExtList,
43 test_hasflag::TestHasFlag,
44 test_header::TestHeader,
45 test_ihave::TestIhave,
46 test_mailbox::{TestMailboxExists, TestMetadata, TestMetadataExists},
47 test_mailboxid::TestMailboxIdExists,
48 test_notify::{TestNotifyMethodCapability, TestValidNotifyMethod},
49 test_size::TestSize,
50 test_spamtest::{TestSpamTest, TestVirusTest},
51 test_specialuse::TestSpecialUseExists,
52 test_string::TestString,
53 },
54 Capability, Invalid,
55};
56
57#[allow(clippy::enum_variant_names)]
58#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
59pub enum Test {
60 True,
61 False,
62 Address(TestAddress),
63 Envelope(TestEnvelope),
64 Exists(TestExists),
65 Header(TestHeader),
66 Size(TestSize),
67 Invalid(Invalid),
68
69 Body(TestBody),
71
72 Convert(Convert),
74
75 Date(TestDate),
77 CurrentDate(TestCurrentDate),
78
79 Duplicate(TestDuplicate),
81
82 String(TestString),
84 Environment(TestString),
85
86 NotifyMethodCapability(TestNotifyMethodCapability),
88 ValidNotifyMethod(TestValidNotifyMethod),
89
90 ValidExtList(TestValidExtList),
92
93 Ihave(TestIhave),
95
96 HasFlag(TestHasFlag),
98
99 MailboxExists(TestMailboxExists),
101 Metadata(TestMetadata),
102 MetadataExists(TestMetadataExists),
103
104 MailboxIdExists(TestMailboxIdExists),
106
107 SpamTest(TestSpamTest),
109 VirusTest(TestVirusTest),
110
111 SpecialUseExists(TestSpecialUseExists),
113
114 Vacation(TestVacation),
116
117 #[cfg(test)]
119 TestCmd {
120 arguments: Vec<crate::compiler::Value>,
121 is_not: bool,
122 },
123}
124
125#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
126pub(crate) struct EvalExpression {
127 pub expr: Vec<Expression>,
128 pub is_not: bool,
129}
130
131#[derive(Debug)]
132struct Block {
133 is_all: bool,
134 is_not: bool,
135 p_count: u32,
136 jmps: Vec<usize>,
137}
138
139impl<'x> CompilerState<'x> {
140 pub(crate) fn parse_test(&mut self) -> Result<(), CompileError> {
141 let mut block_stack: Vec<Block> = Vec::new();
142 let mut block = Block {
143 is_all: false,
144 is_not: false,
145 p_count: 0,
146 jmps: Vec::new(),
147 };
148 let mut is_not = false;
149
150 loop {
151 let token_info = self.tokens.unwrap_next()?;
152 self.reset_param_check();
153 let test: Instruction =
154 match token_info.token {
155 Token::Comma
156 if !block_stack.is_empty()
157 && matches!(
158 self.instructions.last(),
159 Some(Instruction::Test(_) | Instruction::Eval(_))
160 )
161 && matches!(
162 self.tokens.peek(),
163 Some(Ok(TokenInfo {
164 token: Token::Identifier(_) | Token::Unknown(_),
165 ..
166 }))
167 ) =>
168 {
169 is_not = block.is_not;
170 block.jmps.push(self.instructions.len());
171 self.instructions.push(if block.is_all {
172 Instruction::Jz(usize::MAX)
173 } else {
174 Instruction::Jnz(usize::MAX)
175 });
176 continue;
177 }
178 Token::ParenthesisOpen => {
179 block.p_count += 1;
180 continue;
181 }
182 Token::ParenthesisClose => {
183 if block.p_count > 0 {
184 block.p_count -= 1;
185 continue;
186 } else if let Some(prev_block) = block_stack.pop() {
187 let cur_pos = self.instructions.len();
188 for jmp_pos in block.jmps {
189 if let Instruction::Jnz(jmp_pos) | Instruction::Jz(jmp_pos) =
190 &mut self.instructions[jmp_pos]
191 {
192 *jmp_pos = cur_pos;
193 } else {
194 debug_assert!(false, "This should not have happened")
195 }
196 }
197
198 block = prev_block;
199 is_not = block.is_not;
200 if block_stack.is_empty() {
201 break;
202 } else {
203 continue;
204 }
205 } else {
206 return Err(token_info.expected("test name"));
207 }
208 }
209 Token::Identifier(Word::Not) => {
210 if !matches!(
211 self.tokens.peek(),
212 Some(Ok(TokenInfo {
213 token: Token::Identifier(_) | Token::Unknown(_),
214 ..
215 }))
216 ) {
217 return Err(token_info.expected("test name"));
218 }
219 is_not = !is_not;
220 continue;
221 }
222 Token::Identifier(word @ (Word::AnyOf | Word::AllOf)) => {
223 if block_stack.len() < self.tokens.compiler.max_nested_tests {
224 self.tokens.expect_token(Token::ParenthesisOpen)?;
225 block_stack.push(block);
226 let (is_all, block_is_not) = if word == Word::AllOf {
227 if !is_not {
228 (true, false)
229 } else {
230 (false, true)
231 }
232 } else if !is_not {
233 (false, false)
234 } else {
235 (true, true)
236 };
237 block = Block {
238 is_all,
239 is_not: block_is_not,
240 p_count: 0,
241 jmps: Vec::new(),
242 };
243 is_not = block_is_not;
244 continue;
245 } else {
246 return Err(CompileError {
247 line_num: token_info.line_num,
248 line_pos: token_info.line_pos,
249 error_type: ErrorType::TooManyNestedTests,
250 });
251 }
252 }
253 Token::Identifier(Word::True) => if !is_not {
254 Test::True
255 } else {
256 is_not = false;
257 Test::False
258 }
259 .into(),
260 Token::Identifier(Word::False) => if !is_not {
261 Test::False
262 } else {
263 is_not = false;
264 Test::True
265 }
266 .into(),
267 Token::Identifier(Word::Address) => self.parse_test_address()?.into(),
268 Token::Identifier(Word::Envelope) => {
269 self.validate_argument(
270 0,
271 Capability::Envelope.into(),
272 token_info.line_num,
273 token_info.line_pos,
274 )?;
275 self.parse_test_envelope()?.into()
276 }
277 Token::Identifier(Word::Header) => self.parse_test_header()?.into(),
278 Token::Identifier(Word::Size) => self.parse_test_size()?.into(),
279 Token::Identifier(Word::Exists) => self.parse_test_exists()?.into(),
280
281 Token::Identifier(Word::Body) => {
283 self.validate_argument(
284 0,
285 Capability::Body.into(),
286 token_info.line_num,
287 token_info.line_pos,
288 )?;
289 self.parse_test_body()?.into()
290 }
291
292 Token::Identifier(Word::Convert) => {
294 self.validate_argument(
295 0,
296 Capability::Convert.into(),
297 token_info.line_num,
298 token_info.line_pos,
299 )?;
300 self.parse_test_convert()?.into()
301 }
302
303 Token::Identifier(Word::Date) => {
305 self.validate_argument(
306 0,
307 Capability::Date.into(),
308 token_info.line_num,
309 token_info.line_pos,
310 )?;
311 self.parse_test_date()?.into()
312 }
313 Token::Identifier(Word::CurrentDate) => {
314 self.validate_argument(
315 0,
316 Capability::Date.into(),
317 token_info.line_num,
318 token_info.line_pos,
319 )?;
320 self.parse_test_currentdate()?.into()
321 }
322
323 Token::Identifier(Word::Duplicate) => {
325 self.validate_argument(
326 0,
327 Capability::Duplicate.into(),
328 token_info.line_num,
329 token_info.line_pos,
330 )?;
331 self.parse_test_duplicate()?.into()
332 }
333
334 Token::Identifier(Word::String) => {
336 self.validate_argument(
337 0,
338 Capability::Variables.into(),
339 token_info.line_num,
340 token_info.line_pos,
341 )?;
342 self.parse_test_string()?.into()
343 }
344
345 Token::Identifier(Word::NotifyMethodCapability) => {
347 self.validate_argument(
348 0,
349 Capability::Enotify.into(),
350 token_info.line_num,
351 token_info.line_pos,
352 )?;
353 self.parse_test_notify_method_capability()?.into()
354 }
355 Token::Identifier(Word::ValidNotifyMethod) => {
356 self.validate_argument(
357 0,
358 Capability::Enotify.into(),
359 token_info.line_num,
360 token_info.line_pos,
361 )?;
362 self.parse_test_valid_notify_method()?.into()
363 }
364
365 Token::Identifier(Word::Environment) => {
367 self.validate_argument(
368 0,
369 Capability::Environment.into(),
370 token_info.line_num,
371 token_info.line_pos,
372 )?;
373 self.parse_test_environment()?.into()
374 }
375
376 Token::Identifier(Word::ValidExtList) => {
378 self.validate_argument(
379 0,
380 Capability::ExtLists.into(),
381 token_info.line_num,
382 token_info.line_pos,
383 )?;
384 self.parse_test_valid_ext_list()?.into()
385 }
386
387 Token::Identifier(Word::Ihave) => {
389 self.validate_argument(
390 0,
391 Capability::Ihave.into(),
392 token_info.line_num,
393 token_info.line_pos,
394 )?;
395 self.parse_test_ihave()?.into()
396 }
397
398 Token::Identifier(Word::HasFlag) => {
400 self.validate_argument(
401 0,
402 Capability::Imap4Flags.into(),
403 token_info.line_num,
404 token_info.line_pos,
405 )?;
406 self.parse_test_hasflag()?.into()
407 }
408
409 Token::Identifier(Word::MailboxExists) => {
411 self.validate_argument(
412 0,
413 Capability::Mailbox.into(),
414 token_info.line_num,
415 token_info.line_pos,
416 )?;
417 self.parse_test_mailboxexists()?.into()
418 }
419 Token::Identifier(Word::Metadata) => {
420 self.validate_argument(
421 0,
422 Capability::MboxMetadata.into(),
423 token_info.line_num,
424 token_info.line_pos,
425 )?;
426 self.parse_test_metadata()?.into()
427 }
428 Token::Identifier(Word::MetadataExists) => {
429 self.validate_argument(
430 0,
431 Capability::MboxMetadata.into(),
432 token_info.line_num,
433 token_info.line_pos,
434 )?;
435 self.parse_test_metadataexists()?.into()
436 }
437 Token::Identifier(Word::ServerMetadata) => {
438 self.validate_argument(
439 0,
440 Capability::ServerMetadata.into(),
441 token_info.line_num,
442 token_info.line_pos,
443 )?;
444 self.parse_test_servermetadata()?.into()
445 }
446 Token::Identifier(Word::ServerMetadataExists) => {
447 self.validate_argument(
448 0,
449 Capability::ServerMetadata.into(),
450 token_info.line_num,
451 token_info.line_pos,
452 )?;
453 self.parse_test_servermetadataexists()?.into()
454 }
455
456 Token::Identifier(Word::MailboxIdExists) => {
458 self.validate_argument(
459 0,
460 Capability::MailboxId.into(),
461 token_info.line_num,
462 token_info.line_pos,
463 )?;
464 self.parse_test_mailboxidexists()?.into()
465 }
466
467 Token::Identifier(Word::SpamTest) => {
469 self.validate_argument(
470 0,
471 Capability::SpamTest.into(),
472 token_info.line_num,
473 token_info.line_pos,
474 )?;
475 self.parse_test_spamtest()?.into()
476 }
477 Token::Identifier(Word::VirusTest) => {
478 self.validate_argument(
479 0,
480 Capability::VirusTest.into(),
481 token_info.line_num,
482 token_info.line_pos,
483 )?;
484 self.parse_test_virustest()?.into()
485 }
486
487 Token::Identifier(Word::SpecialUseExists) => {
489 self.validate_argument(
490 0,
491 Capability::SpecialUse.into(),
492 token_info.line_num,
493 token_info.line_pos,
494 )?;
495 self.parse_test_specialuseexists()?.into()
496 }
497
498 Token::Identifier(Word::Eval) => {
500 self.validate_argument(
501 0,
502 Capability::Expressions.into(),
503 token_info.line_num,
504 token_info.line_pos,
505 )?;
506
507 Instruction::Eval(self.parse_expr()?)
508 }
509 Token::Identifier(word) => {
510 self.ignore_test()?;
511 Test::Invalid(Invalid {
512 name: word.to_string(),
513 line_num: token_info.line_num,
514 line_pos: token_info.line_pos,
515 })
516 .into()
517 }
518 #[cfg(test)]
519 Token::Unknown(name) if name.contains("test") => {
520 use crate::compiler::Value;
521
522 let mut arguments = Vec::new();
523 arguments.push(Value::Text(name.into()));
524 while !matches!(
525 self.tokens.peek().map(|r| r.map(|t| &t.token)),
526 Some(Ok(Token::Comma
527 | Token::ParenthesisClose
528 | Token::CurlyOpen))
529 ) {
530 arguments.push(match self.tokens.unwrap_next()?.token {
531 Token::StringConstant(s) => Value::from(s),
532 Token::StringVariable(s) => self
533 .tokenize_string(&s, true)
534 .map_err(|error_type| CompileError {
535 line_num: 0,
536 line_pos: 0,
537 error_type,
538 })?,
539 Token::Number(n) => {
540 Value::Number(crate::compiler::Number::Integer(n as i64))
541 }
542 Token::Identifier(s) => Value::Text(s.to_string().into()),
543 Token::Tag(s) => Value::Text(format!(":{s}").into()),
544 Token::Unknown(s) => Value::Text(s.into()),
545 other => panic!("Invalid test param {other:?}"),
546 });
547 }
548 Test::TestCmd {
549 arguments,
550 is_not: false,
551 }
552 .into()
553 }
554 Token::Unknown(name) => {
555 self.ignore_test()?;
556 Test::Invalid(Invalid {
557 name,
558 line_num: token_info.line_num,
559 line_pos: token_info.line_pos,
560 })
561 .into()
562 }
563 _ => return Err(token_info.expected("test name")),
564 };
565
566 while block.p_count > 0 {
567 self.tokens.expect_token(Token::ParenthesisClose)?;
568 block.p_count -= 1;
569 }
570
571 self.instructions
572 .push(if !is_not { test } else { test.set_not() });
573
574 if block_stack.is_empty() {
575 break;
576 }
577 }
578
579 self.instructions.push(Instruction::Jz(usize::MAX));
580 Ok(())
581 }
582
583 pub(crate) fn parse_expr(&mut self) -> Result<Vec<Expression>, CompileError> {
584 let mut next_token = self.tokens.unwrap_next()?;
585 let expr = match next_token.token {
586 Token::StringConstant(s) => s.into_string().into_bytes(),
587 Token::StringVariable(s) => s,
588 _ => return Err(next_token.expected("string")),
589 };
590
591 match ExpressionParser::from_tokenizer(Tokenizer::from_iter(
592 expr.iter().enumerate().peekable(),
593 |var_name, maybe_namespace| self.parse_expr_fnc_or_var(var_name, maybe_namespace),
594 ))
595 .parse()
596 {
597 Ok(parser) => Ok(parser.output),
598 Err(err) => {
599 let err = ErrorType::InvalidExpression(format!(
600 "{}: {}",
601 std::str::from_utf8(&expr).unwrap_or_default(),
602 err
603 ));
604 next_token.token = Token::StringVariable(expr);
605 Err(next_token.custom(err))
606 }
607 }
608 }
609}
610
611impl From<Test> for Instruction {
612 fn from(test: Test) -> Self {
613 Instruction::Test(test)
614 }
615}
616
617impl Instruction {
618 pub fn set_not(mut self) -> Self {
619 match &mut self {
620 Instruction::Test(test) => match test {
621 Test::True => return Instruction::Test(Test::False),
622 Test::False => return Instruction::Test(Test::True),
623 Test::Address(op) => {
624 op.is_not = true;
625 }
626 Test::Envelope(op) => {
627 op.is_not = true;
628 }
629 Test::Exists(op) => {
630 op.is_not = true;
631 }
632 Test::Header(op) => {
633 op.is_not = true;
634 }
635 Test::Size(op) => {
636 op.is_not = true;
637 }
638 Test::Body(op) => {
639 op.is_not = true;
640 }
641 Test::Convert(op) => {
642 op.is_not = true;
643 }
644 Test::Date(op) => {
645 op.is_not = true;
646 }
647 Test::CurrentDate(op) => {
648 op.is_not = true;
649 }
650 Test::Duplicate(op) => {
651 op.is_not = true;
652 }
653 Test::String(op) | Test::Environment(op) => {
654 op.is_not = true;
655 }
656 Test::NotifyMethodCapability(op) => {
657 op.is_not = true;
658 }
659 Test::ValidNotifyMethod(op) => {
660 op.is_not = true;
661 }
662 Test::ValidExtList(op) => {
663 op.is_not = true;
664 }
665 Test::Ihave(op) => {
666 op.is_not = true;
667 }
668 Test::HasFlag(op) => {
669 op.is_not = true;
670 }
671 Test::MailboxExists(op) => {
672 op.is_not = true;
673 }
674 Test::Metadata(op) => {
675 op.is_not = true;
676 }
677 Test::MetadataExists(op) => {
678 op.is_not = true;
679 }
680 Test::MailboxIdExists(op) => {
681 op.is_not = true;
682 }
683 Test::SpamTest(op) => {
684 op.is_not = true;
685 }
686 Test::VirusTest(op) => {
687 op.is_not = true;
688 }
689 Test::SpecialUseExists(op) => {
690 op.is_not = true;
691 }
692 #[cfg(test)]
693 Test::TestCmd { is_not, .. } => {
694 *is_not = true;
695 }
696 Test::Vacation(_) | Test::Invalid(_) => {}
697 },
698 Instruction::Eval(expr) => expr.push(Expression::UnaryOperator(UnaryOperator::Not)),
699 _ => (),
700 }
701 self
702 }
703}