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