1use ahash::{AHashMap, AHashSet};
25use serde::{Deserialize, Serialize};
26
27use crate::{
28 compiler::{
29 grammar::{test::Test, MatchType},
30 lexer::{tokenizer::Tokenizer, word::Word, Token},
31 CompileError, ErrorType, Value, VariableType,
32 },
33 Compiler, Sieve,
34};
35
36use super::{
37 actions::{
38 action_convert::Convert,
39 action_editheader::{AddHeader, DeleteHeader},
40 action_fileinto::FileInto,
41 action_flags::EditFlags,
42 action_include::Include,
43 action_keep::Keep,
44 action_mime::{Enclose, ExtractText, ForEveryPart, Replace},
45 action_notify::Notify,
46 action_redirect::Redirect,
47 action_reject::Reject,
48 action_set::{Let, Set},
49 action_vacation::Vacation,
50 },
51 expr::Expression,
52 Capability, Clear, Invalid, While,
53};
54
55use super::tests::test_ihave::Error;
56
57#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
58pub enum Instruction {
59 Require(Vec<Capability>),
60 Keep(Keep),
61 FileInto(FileInto),
62 Redirect(Redirect),
63 Discard,
64 Stop,
65 Invalid(Invalid),
66 Test(Test),
67 Jmp(usize),
68 Jz(usize),
69 Jnz(usize),
70
71 ForEveryPartPush,
73 ForEveryPart(ForEveryPart),
74 ForEveryPartPop(usize),
75 Replace(Replace),
76 Enclose(Enclose),
77 ExtractText(ExtractText),
78
79 Convert(Convert),
81
82 AddHeader(AddHeader),
84 DeleteHeader(DeleteHeader),
85
86 Set(Set),
88 Clear(Clear),
89
90 Notify(Notify),
92
93 Reject(Reject),
95
96 Vacation(Vacation),
98
99 Error(Error),
101
102 EditFlags(EditFlags),
104
105 Include(Include),
107 Return,
108
109 While(While),
111
112 Eval(Vec<Expression>),
114 Let(Let),
115
116 #[cfg(test)]
118 TestCmd(Vec<Value>),
119}
120
121pub(crate) const MAX_PARAMS: usize = 11;
122
123#[derive(Debug)]
124pub(crate) struct Block {
125 pub(crate) btype: Word,
126 pub(crate) label: Option<String>,
127 pub(crate) line_num: usize,
128 pub(crate) line_pos: usize,
129 pub(crate) last_block_start: usize,
130 pub(crate) if_jmps: Vec<usize>,
131 pub(crate) break_jmps: Vec<usize>,
132 pub(crate) match_test_pos: Vec<usize>,
133 pub(crate) match_test_vars: u64,
134 pub(crate) vars_local: AHashMap<String, usize>,
135 pub(crate) capabilities: AHashSet<Capability>,
136 pub(crate) require_pos: usize,
137}
138
139pub(crate) struct CompilerState<'x> {
140 pub(crate) compiler: &'x Compiler,
141 pub(crate) tokens: Tokenizer<'x>,
142 pub(crate) instructions: Vec<Instruction>,
143 pub(crate) block_stack: Vec<Block>,
144 pub(crate) block: Block,
145 pub(crate) last_block_type: Word,
146 pub(crate) vars_global: AHashSet<String>,
147 pub(crate) vars_num: usize,
148 pub(crate) vars_num_max: usize,
149 pub(crate) vars_match_max: usize,
150 pub(crate) vars_local: usize,
151 pub(crate) param_check: [bool; MAX_PARAMS],
152 pub(crate) includes_num: usize,
153}
154
155impl Compiler {
156 pub fn compile(&self, script: &[u8]) -> Result<Sieve, CompileError> {
157 if script.len() > self.max_script_size {
158 return Err(CompileError {
159 line_num: 0,
160 line_pos: 0,
161 error_type: ErrorType::ScriptTooLong,
162 });
163 }
164
165 let mut state = CompilerState {
166 compiler: self,
167 tokens: Tokenizer::new(self, script),
168 instructions: Vec::new(),
169 block_stack: Vec::new(),
170 block: Block::new(Word::Not),
171 last_block_type: Word::Not,
172 vars_global: AHashSet::new(),
173 vars_num: 0,
174 vars_num_max: 0,
175 vars_match_max: 0,
176 vars_local: 0,
177 param_check: [false; MAX_PARAMS],
178 includes_num: 0,
179 };
180
181 while let Some(token_info) = state.tokens.next() {
182 let token_info = token_info?;
183 state.reset_param_check();
184
185 match token_info.token {
186 Token::Identifier(instruction) => {
187 let mut is_new_block = None;
188
189 match instruction {
190 Word::Require => {
191 state.parse_require()?;
192 }
193 Word::If => {
194 state.parse_test()?;
195 state.block.if_jmps.clear();
196 is_new_block = Block::new(Word::If).into();
197 }
198 Word::ElsIf => {
199 if let Word::If | Word::ElsIf = &state.last_block_type {
200 state.parse_test()?;
201 is_new_block = Block::new(Word::ElsIf).into();
202 } else {
203 return Err(token_info.expected("'if' before 'elsif'"));
204 }
205 }
206 Word::Else => {
207 if let Word::If | Word::ElsIf = &state.last_block_type {
208 is_new_block = Block::new(Word::Else).into();
209 } else {
210 return Err(token_info.expected("'if' or 'elsif' before 'else'"));
211 }
212 }
213 Word::Keep => {
214 state.parse_keep()?;
215 }
216 Word::FileInto => {
217 state.validate_argument(
218 0,
219 Capability::FileInto.into(),
220 token_info.line_num,
221 token_info.line_pos,
222 )?;
223 state.parse_fileinto()?;
224 }
225 Word::Redirect => {
226 state.parse_redirect()?;
227 }
228 Word::Discard => {
229 state.instructions.push(Instruction::Discard);
230 }
231 Word::Stop => {
232 state.instructions.push(Instruction::Stop);
233 }
234
235 Word::ForEveryPart => {
237 state.validate_argument(
238 0,
239 Capability::ForEveryPart.into(),
240 token_info.line_num,
241 token_info.line_pos,
242 )?;
243
244 if state
245 .block_stack
246 .iter()
247 .filter(|b| matches!(&b.btype, Word::ForEveryPart))
248 .count()
249 == self.max_nested_foreverypart
250 {
251 return Err(
252 token_info.custom(ErrorType::TooManyNestedForEveryParts)
253 );
254 }
255
256 is_new_block = if let Some(Ok(Token::Tag(Word::Name))) =
257 state.tokens.peek().map(|r| r.map(|t| &t.token))
258 {
259 let tag = state.tokens.next().unwrap().unwrap();
260 let label = state.tokens.expect_static_string()?;
261 for block in &state.block_stack {
262 if block.label.as_ref().map_or(false, |n| n.eq(&label)) {
263 return Err(
264 tag.custom(ErrorType::LabelAlreadyDefined(label))
265 );
266 }
267 }
268 Block::new(Word::ForEveryPart).with_label(label)
269 } else {
270 Block::new(Word::ForEveryPart)
271 }
272 .into();
273
274 state.instructions.push(Instruction::ForEveryPartPush);
275 state
276 .instructions
277 .push(Instruction::ForEveryPart(ForEveryPart {
278 jz_pos: usize::MAX,
279 }));
280 }
281 Word::Break => {
282 if let Some(Ok(Token::Tag(Word::Name))) =
283 state.tokens.peek().map(|r| r.map(|t| &t.token))
284 {
285 state.validate_argument(
286 0,
287 Capability::ForEveryPart.into(),
288 token_info.line_num,
289 token_info.line_pos,
290 )?;
291
292 let tag = state.tokens.next().unwrap().unwrap();
293 let label = state.tokens.expect_static_string()?;
294 let mut label_found = false;
295 let mut num_pops = 0;
296
297 for block in [&mut state.block]
298 .into_iter()
299 .chain(state.block_stack.iter_mut().rev())
300 {
301 if let Word::ForEveryPart = &block.btype {
302 num_pops += 1;
303 if block.label.as_ref().map_or(false, |n| n.eq(&label)) {
304 state
305 .instructions
306 .push(Instruction::ForEveryPartPop(num_pops));
307 block.break_jmps.push(state.instructions.len());
308 label_found = true;
309 break;
310 }
311 }
312 }
313
314 if !label_found {
315 return Err(tag.custom(ErrorType::LabelUndefined(label)));
316 }
317 } else {
318 let mut block_found = None;
319 if matches!(&state.block.btype, Word::ForEveryPart | Word::While) {
320 block_found = Some(&mut state.block);
321 } else {
322 for block in state.block_stack.iter_mut().rev() {
323 if matches!(&block.btype, Word::ForEveryPart | Word::While)
324 {
325 block_found = Some(block);
326 break;
327 }
328 }
329 }
330
331 let block = block_found.ok_or_else(|| {
332 token_info.custom(ErrorType::BreakOutsideLoop)
333 })?;
334 if matches!(block.btype, Word::ForEveryPart) {
335 state.instructions.push(Instruction::ForEveryPartPop(1));
336 }
337
338 block.break_jmps.push(state.instructions.len());
339 }
340
341 state.instructions.push(Instruction::Jmp(usize::MAX));
342 }
343 Word::Replace => {
344 state.validate_argument(
345 0,
346 Capability::Replace.into(),
347 token_info.line_num,
348 token_info.line_pos,
349 )?;
350 state.parse_replace()?;
351 }
352 Word::Enclose => {
353 state.validate_argument(
354 0,
355 Capability::Enclose.into(),
356 token_info.line_num,
357 token_info.line_pos,
358 )?;
359 state.parse_enclose()?;
360 }
361 Word::ExtractText => {
362 state.validate_argument(
363 0,
364 Capability::ExtractText.into(),
365 token_info.line_num,
366 token_info.line_pos,
367 )?;
368 state.parse_extracttext()?;
369 }
370
371 Word::Convert => {
373 state.validate_argument(
374 0,
375 Capability::Convert.into(),
376 token_info.line_num,
377 token_info.line_pos,
378 )?;
379 state.parse_convert()?;
380 }
381
382 Word::AddHeader => {
384 state.validate_argument(
385 0,
386 Capability::EditHeader.into(),
387 token_info.line_num,
388 token_info.line_pos,
389 )?;
390 state.parse_addheader()?;
391 }
392 Word::DeleteHeader => {
393 state.validate_argument(
394 0,
395 Capability::EditHeader.into(),
396 token_info.line_num,
397 token_info.line_pos,
398 )?;
399 state.parse_deleteheader()?;
400 }
401
402 Word::Set => {
404 state.validate_argument(
405 0,
406 Capability::Variables.into(),
407 token_info.line_num,
408 token_info.line_pos,
409 )?;
410 state.parse_set()?;
411 }
412
413 Word::Notify => {
415 state.validate_argument(
416 0,
417 Capability::Enotify.into(),
418 token_info.line_num,
419 token_info.line_pos,
420 )?;
421 state.parse_notify()?;
422 }
423
424 Word::Reject => {
426 state.validate_argument(
427 0,
428 Capability::Reject.into(),
429 token_info.line_num,
430 token_info.line_pos,
431 )?;
432 state.parse_reject(false)?;
433 }
434 Word::Ereject => {
435 state.validate_argument(
436 0,
437 Capability::Ereject.into(),
438 token_info.line_num,
439 token_info.line_pos,
440 )?;
441 state.parse_reject(true)?;
442 }
443
444 Word::Vacation => {
446 state.validate_argument(
447 0,
448 Capability::Vacation.into(),
449 token_info.line_num,
450 token_info.line_pos,
451 )?;
452 state.parse_vacation()?;
453 }
454
455 Word::Error => {
457 state.validate_argument(
458 0,
459 Capability::Ihave.into(),
460 token_info.line_num,
461 token_info.line_pos,
462 )?;
463 state.parse_error()?;
464 }
465
466 Word::SetFlag | Word::AddFlag | Word::RemoveFlag => {
468 state.validate_argument(
469 0,
470 Capability::Imap4Flags.into(),
471 token_info.line_num,
472 token_info.line_pos,
473 )?;
474 state.parse_flag_action(instruction)?;
475 }
476
477 Word::Include => {
479 if state.includes_num < self.max_includes {
480 state.validate_argument(
481 0,
482 Capability::Include.into(),
483 token_info.line_num,
484 token_info.line_pos,
485 )?;
486 state.parse_include()?;
487 state.includes_num += 1;
488 } else {
489 return Err(token_info.custom(ErrorType::TooManyIncludes));
490 }
491 }
492 Word::Return => {
493 state.validate_argument(
494 0,
495 Capability::Include.into(),
496 token_info.line_num,
497 token_info.line_pos,
498 )?;
499 let mut num_pops = 0;
500
501 for block in [&state.block]
502 .into_iter()
503 .chain(state.block_stack.iter().rev())
504 {
505 if let Word::ForEveryPart = &block.btype {
506 num_pops += 1;
507 }
508 }
509
510 if num_pops > 0 {
511 state
512 .instructions
513 .push(Instruction::ForEveryPartPop(num_pops));
514 }
515
516 state.instructions.push(Instruction::Return);
517 }
518 Word::Global => {
519 state.validate_argument(
520 0,
521 Capability::Include.into(),
522 token_info.line_num,
523 token_info.line_pos,
524 )?;
525 state.validate_argument(
526 0,
527 Capability::Variables.into(),
528 token_info.line_num,
529 token_info.line_pos,
530 )?;
531 for global in state.parse_static_strings()? {
532 if !state.is_var_local(&global) {
533 if global.len() < self.max_variable_name_size {
534 state.register_global_var(&global);
535 } else {
536 return Err(state
537 .tokens
538 .unwrap_next()?
539 .custom(ErrorType::VariableTooLong));
540 }
541 } else {
542 return Err(state
543 .tokens
544 .unwrap_next()?
545 .custom(ErrorType::VariableIsLocal(global)));
546 }
547 }
548 }
549
550 Word::Let => {
552 state.validate_argument(
553 0,
554 Capability::Expressions.into(),
555 token_info.line_num,
556 token_info.line_pos,
557 )?;
558 state.parse_let()?;
559 }
560 Word::Eval => {
561 state.validate_argument(
562 0,
563 Capability::Expressions.into(),
564 token_info.line_num,
565 token_info.line_pos,
566 )?;
567 let expr = state.parse_expr()?;
568 state.instructions.push(Instruction::Eval(expr));
569 }
570
571 Word::While => {
573 state.validate_argument(
574 0,
575 Capability::While.into(),
576 token_info.line_num,
577 token_info.line_pos,
578 )?;
579
580 is_new_block = Block::new(Word::While).into();
581
582 let expr = state.parse_expr()?;
583 state.instructions.push(Instruction::While(While {
584 expr,
585 jz_pos: usize::MAX,
586 }));
587 }
588 Word::Continue => {
589 state.validate_argument(
590 0,
591 Capability::While.into(),
592 token_info.line_num,
593 token_info.line_pos,
594 )?;
595 let mut found_while = 0;
596 for block in [&state.block]
597 .into_iter()
598 .chain(state.block_stack.iter().rev())
599 {
600 if let Word::While = &block.btype {
601 found_while += 1;
602 } else if found_while == 1 {
603 state
604 .instructions
605 .push(Instruction::Jmp(block.last_block_start));
606 found_while += 1;
607 break;
608 }
609 }
610 if found_while != 2 {
611 return Err(token_info.custom(ErrorType::ContinueOutsideLoop));
612 }
613 }
614
615 _ => {
616 if state.has_capability(&Capability::Ihave) {
617 state.ignore_instruction()?;
618 state.instructions.push(Instruction::Invalid(Invalid {
619 name: instruction.to_string(),
620 line_num: token_info.line_num,
621 line_pos: token_info.line_pos,
622 }));
623 continue;
624 } else {
625 return Err(CompileError {
626 line_num: state.block.line_num,
627 line_pos: state.block.line_pos,
628 error_type: ErrorType::UnexpectedToken {
629 expected: "command".into(),
630 found: instruction.to_string(),
631 },
632 });
633 }
634 }
635 }
636
637 if let Some(mut new_block) = is_new_block {
638 new_block.line_num = state.tokens.line_num;
639 new_block.line_pos = state.tokens.pos - state.tokens.line_start;
640
641 state.tokens.expect_token(Token::CurlyOpen)?;
642 if state.block_stack.len() < self.max_nested_blocks {
643 state.block.last_block_start = state.instructions.len() - 1;
644 state.block_stack.push(state.block);
645 state.block = new_block;
646 } else {
647 return Err(CompileError {
648 line_num: state.block.line_num,
649 line_pos: state.block.line_pos,
650 error_type: ErrorType::TooManyNestedBlocks,
651 });
652 }
653 } else {
654 state.expect_instruction_end()?;
655 }
656 }
657 Token::CurlyClose if !state.block_stack.is_empty() => {
658 state.block_end();
659 let mut prev_block = state.block_stack.pop().unwrap();
660 match &state.block.btype {
661 Word::ForEveryPart => {
662 state
663 .instructions
664 .push(Instruction::Jmp(prev_block.last_block_start));
665 let cur_pos = state.instructions.len();
666 if let Instruction::ForEveryPart(fep) =
667 &mut state.instructions[prev_block.last_block_start]
668 {
669 fep.jz_pos = cur_pos;
670 } else {
671 debug_assert!(false, "This should not have happened.");
672 }
673 for pos in state.block.break_jmps {
674 if let Instruction::Jmp(jmp_pos) = &mut state.instructions[pos] {
675 *jmp_pos = cur_pos;
676 } else {
677 debug_assert!(false, "This should not have happened.");
678 }
679 }
680 state.last_block_type = Word::Not;
681 }
682 Word::If | Word::ElsIf => {
683 let next_is_block = matches!(
684 state.tokens.peek().map(|r| r.map(|t| &t.token)),
685 Some(Ok(Token::Identifier(Word::ElsIf | Word::Else)))
686 );
687 if next_is_block {
688 prev_block.if_jmps.push(state.instructions.len());
689 state.instructions.push(Instruction::Jmp(usize::MAX));
690 }
691 let cur_pos = state.instructions.len();
692 if let Instruction::Jz(jmp_pos) =
693 &mut state.instructions[prev_block.last_block_start]
694 {
695 *jmp_pos = cur_pos;
696 } else {
697 debug_assert!(false, "This should not have happened.");
698 }
699 if !next_is_block {
700 for pos in prev_block.if_jmps.drain(..) {
701 if let Instruction::Jmp(jmp_pos) = &mut state.instructions[pos]
702 {
703 *jmp_pos = cur_pos;
704 } else {
705 debug_assert!(false, "This should not have happened.");
706 }
707 }
708 state.last_block_type = Word::Not;
709 } else {
710 state.last_block_type = state.block.btype;
711 }
712 }
713 Word::Else => {
714 let cur_pos = state.instructions.len();
715 for pos in prev_block.if_jmps.drain(..) {
716 if let Instruction::Jmp(jmp_pos) = &mut state.instructions[pos] {
717 *jmp_pos = cur_pos;
718 } else {
719 debug_assert!(false, "This should not have happened.");
720 }
721 }
722 state.last_block_type = Word::Else;
723 }
724 Word::While => {
725 state
726 .instructions
727 .push(Instruction::Jmp(prev_block.last_block_start));
728 let cur_pos = state.instructions.len();
729 if let Instruction::While(fep) =
730 &mut state.instructions[prev_block.last_block_start]
731 {
732 fep.jz_pos = cur_pos;
733 } else {
734 debug_assert!(false, "This should not have happened.");
735 }
736 for pos in state.block.break_jmps {
737 if let Instruction::Jmp(jmp_pos) = &mut state.instructions[pos] {
738 *jmp_pos = cur_pos;
739 } else {
740 debug_assert!(false, "This should not have happened.");
741 }
742 }
743 state.last_block_type = Word::Not;
744 }
745 _ => {
746 debug_assert!(false, "This should not have happened.");
747 }
748 }
749
750 state.block = prev_block;
751 }
752
753 #[cfg(test)]
754 Token::Unknown(instruction) if instruction.contains("test") => {
755 let has_arguments = instruction != "test";
756 let mut arguments = vec![Value::Text(instruction.into())];
757
758 if !has_arguments {
759 arguments.push(state.parse_string()?);
760 state.instructions.push(Instruction::TestCmd(arguments));
761 let mut new_block = Block::new(Word::Else);
762 new_block.line_num = state.tokens.line_num;
763 new_block.line_pos = state.tokens.pos - state.tokens.line_start;
764 state.tokens.expect_token(Token::CurlyOpen)?;
765 state.block.last_block_start = state.instructions.len() - 1;
766 state.block_stack.push(state.block);
767 state.block = new_block;
768 } else {
769 loop {
770 arguments.push(match state.tokens.unwrap_next()?.token {
771 Token::StringConstant(s) => Value::from(s),
772 Token::StringVariable(s) => state
773 .tokenize_string(&s, true)
774 .map_err(|error_type| CompileError {
775 line_num: 0,
776 line_pos: 0,
777 error_type,
778 })?,
779 Token::Number(n) => {
780 Value::Number(crate::compiler::Number::Integer(n as i64))
781 }
782 Token::Identifier(s) => Value::Text(s.to_string().into()),
783 Token::Tag(s) => Value::Text(format!(":{s}").into()),
784 Token::Unknown(s) => Value::Text(s.into()),
785 Token::Semicolon => break,
786 other => panic!("Invalid test param {other:?}"),
787 });
788 }
789 state.instructions.push(Instruction::TestCmd(arguments));
790 }
791 }
792
793 Token::Unknown(instruction) => {
794 if state.has_capability(&Capability::Ihave) {
795 state.ignore_instruction()?;
796 state.instructions.push(Instruction::Invalid(Invalid {
797 name: instruction,
798 line_num: token_info.line_num,
799 line_pos: token_info.line_pos,
800 }));
801 } else {
802 return Err(CompileError {
803 line_num: state.block.line_num,
804 line_pos: state.block.line_pos,
805 error_type: ErrorType::UnexpectedToken {
806 expected: "command".into(),
807 found: instruction,
808 },
809 });
810 }
811 }
812 _ => {
813 return Err(token_info.expected("instruction"));
814 }
815 }
816 }
817
818 if !state.block_stack.is_empty() {
819 return Err(CompileError {
820 line_num: state.block.line_num,
821 line_pos: state.block.line_pos,
822 error_type: ErrorType::UnterminatedBlock,
823 });
824 }
825
826 let mut num_vars = std::cmp::max(state.vars_num_max, state.vars_num);
828 if state.vars_local > 0 {
829 state.map_local_vars(num_vars);
830 num_vars += state.vars_local;
831 }
832
833 Ok(Sieve {
834 instructions: state.instructions,
835 num_vars,
836 num_match_vars: state.vars_match_max,
837 })
838 }
839}
840
841impl<'x> CompilerState<'x> {
842 pub(crate) fn is_var_local(&self, name: &str) -> bool {
843 let name = name.to_ascii_lowercase();
844 if self.block.vars_local.contains_key(&name) {
845 true
846 } else {
847 for block in self.block_stack.iter().rev() {
848 if block.vars_local.contains_key(&name) {
849 return true;
850 }
851 }
852 false
853 }
854 }
855
856 pub(crate) fn is_var_global(&self, name: &str) -> bool {
857 let name = name.to_ascii_lowercase();
858 self.vars_global.contains(&name)
859 }
860
861 pub(crate) fn register_local_var(&mut self, name: String, register_as_local: bool) -> usize {
862 if let Some(var_id) = self.get_local_var(&name) {
863 var_id
864 } else if !register_as_local || self.block_stack.is_empty() {
865 let var_id = self.vars_num;
866 self.block.vars_local.insert(name, var_id);
867 self.vars_num += 1;
868 var_id
869 } else {
870 let var_id = usize::MAX - self.vars_local;
871 self.block_stack
872 .first_mut()
873 .unwrap()
874 .vars_local
875 .insert(name, usize::MAX - self.vars_local);
876 self.vars_local += 1;
877 var_id
878 }
879 }
880
881 pub(crate) fn register_global_var(&mut self, name: &str) {
882 self.vars_global.insert(name.to_ascii_lowercase());
883 }
884
885 pub(crate) fn get_local_var(&self, name: &str) -> Option<usize> {
886 let name = name.to_ascii_lowercase();
887 if let Some(var_id) = self.block.vars_local.get(&name) {
888 Some(*var_id)
889 } else {
890 for block in self.block_stack.iter().rev() {
891 if let Some(var_id) = block.vars_local.get(&name) {
892 return Some(*var_id);
893 }
894 }
895 None
896 }
897 }
898
899 pub(crate) fn register_match_var(&mut self, num: usize) -> bool {
900 let mut block = &mut self.block;
901
902 if block.match_test_pos.is_empty() {
903 for block_ in self.block_stack.iter_mut().rev() {
904 if !block_.match_test_pos.is_empty() {
905 block = block_;
906 break;
907 }
908 }
909 }
910
911 if !block.match_test_pos.is_empty() {
912 debug_assert!(num < 63);
913
914 for pos in &block.match_test_pos {
915 if let Instruction::Test(test) = &mut self.instructions[*pos] {
916 let match_type = match test {
917 Test::Address(t) => &mut t.match_type,
918 Test::Body(t) => &mut t.match_type,
919 Test::Date(t) => &mut t.match_type,
920 Test::CurrentDate(t) => &mut t.match_type,
921 Test::Envelope(t) => &mut t.match_type,
922 Test::HasFlag(t) => &mut t.match_type,
923 Test::Header(t) => &mut t.match_type,
924 Test::Metadata(t) => &mut t.match_type,
925 Test::NotifyMethodCapability(t) => &mut t.match_type,
926 Test::SpamTest(t) => &mut t.match_type,
927 Test::String(t) | Test::Environment(t) => &mut t.match_type,
928 Test::VirusTest(t) => &mut t.match_type,
929 _ => {
930 debug_assert!(false, "This should not have happened: {test:?}");
931 return false;
932 }
933 };
934 if let MatchType::Matches(positions) | MatchType::Regex(positions) = match_type
935 {
936 *positions |= 1 << num;
937 block.match_test_vars = *positions;
938 } else {
939 debug_assert!(false, "This should not have happened");
940 return false;
941 }
942 } else {
943 debug_assert!(false, "This should not have happened");
944 return false;
945 }
946 }
947 true
948 } else {
949 false
950 }
951 }
952
953 pub(crate) fn block_end(&mut self) {
954 let vars_num_block = self.block.vars_local.len();
955 if vars_num_block > 0 {
956 if self.vars_num > self.vars_num_max {
957 self.vars_num_max = self.vars_num;
958 }
959 self.vars_num -= vars_num_block;
960 self.instructions.push(Instruction::Clear(Clear {
961 match_vars: self.block.match_test_vars,
962 local_vars_idx: self.vars_num as u32,
963 local_vars_num: vars_num_block as u32,
964 }));
965 } else if self.block.match_test_vars != 0 {
966 self.instructions.push(Instruction::Clear(Clear {
967 match_vars: self.block.match_test_vars,
968 local_vars_idx: 0,
969 local_vars_num: 0,
970 }));
971 }
972 }
973
974 fn map_local_vars(&mut self, last_id: usize) {
975 for instruction in &mut self.instructions {
976 match instruction {
977 Instruction::Test(v) => v.map_local_vars(last_id),
978 Instruction::Keep(k) => k.flags.map_local_vars(last_id),
979 Instruction::FileInto(v) => {
980 v.folder.map_local_vars(last_id);
981 v.flags.map_local_vars(last_id);
982 v.mailbox_id.map_local_vars(last_id);
983 v.special_use.map_local_vars(last_id);
984 }
985 Instruction::Redirect(v) => {
986 v.address.map_local_vars(last_id);
987 v.by_time.map_local_vars(last_id);
988 }
989 Instruction::Replace(v) => {
990 v.subject.map_local_vars(last_id);
991 v.from.map_local_vars(last_id);
992 v.replacement.map_local_vars(last_id);
993 }
994 Instruction::Enclose(v) => {
995 v.subject.map_local_vars(last_id);
996 v.headers.map_local_vars(last_id);
997 v.value.map_local_vars(last_id);
998 }
999 Instruction::ExtractText(v) => {
1000 v.name.map_local_vars(last_id);
1001 }
1002 Instruction::Convert(v) => {
1003 v.from_media_type.map_local_vars(last_id);
1004 v.to_media_type.map_local_vars(last_id);
1005 v.transcoding_params.map_local_vars(last_id);
1006 }
1007 Instruction::AddHeader(v) => {
1008 v.field_name.map_local_vars(last_id);
1009 v.value.map_local_vars(last_id);
1010 }
1011 Instruction::DeleteHeader(v) => {
1012 v.field_name.map_local_vars(last_id);
1013 v.value_patterns.map_local_vars(last_id);
1014 }
1015 Instruction::Set(v) => {
1016 v.name.map_local_vars(last_id);
1017 v.value.map_local_vars(last_id);
1018 }
1019 Instruction::Let(v) => {
1020 v.name.map_local_vars(last_id);
1021 v.expr.map_local_vars(last_id);
1022 }
1023 Instruction::While(v) => {
1024 v.expr.map_local_vars(last_id);
1025 }
1026 Instruction::Eval(v) => {
1027 v.map_local_vars(last_id);
1028 }
1029 Instruction::Notify(v) => {
1030 v.from.map_local_vars(last_id);
1031 v.importance.map_local_vars(last_id);
1032 v.options.map_local_vars(last_id);
1033 v.message.map_local_vars(last_id);
1034 v.fcc.map_local_vars(last_id);
1035 v.method.map_local_vars(last_id);
1036 }
1037 Instruction::Reject(v) => {
1038 v.reason.map_local_vars(last_id);
1039 }
1040 Instruction::Vacation(v) => {
1041 v.subject.map_local_vars(last_id);
1042 v.from.map_local_vars(last_id);
1043 v.fcc.map_local_vars(last_id);
1044 v.reason.map_local_vars(last_id);
1045 }
1046 Instruction::Error(v) => {
1047 v.message.map_local_vars(last_id);
1048 }
1049 Instruction::EditFlags(v) => {
1050 v.name.map_local_vars(last_id);
1051 v.flags.map_local_vars(last_id);
1052 }
1053 Instruction::Include(v) => {
1054 v.value.map_local_vars(last_id);
1055 }
1056 _ => {}
1057 }
1058 }
1059 }
1060}
1061
1062pub trait MapLocalVars {
1063 fn map_local_vars(&mut self, last_id: usize);
1064}
1065
1066impl MapLocalVars for Test {
1067 fn map_local_vars(&mut self, last_id: usize) {
1068 match self {
1069 Test::Address(v) => {
1070 v.header_list.map_local_vars(last_id);
1071 v.key_list.map_local_vars(last_id);
1072 }
1073 Test::Envelope(v) => {
1074 v.key_list.map_local_vars(last_id);
1075 }
1076 Test::Exists(v) => {
1077 v.header_names.map_local_vars(last_id);
1078 }
1079 Test::Header(v) => {
1080 v.key_list.map_local_vars(last_id);
1081 v.header_list.map_local_vars(last_id);
1082 v.mime_opts.map_local_vars(last_id);
1083 }
1084 Test::Body(v) => {
1085 v.key_list.map_local_vars(last_id);
1086 }
1087 Test::Convert(v) => {
1088 v.from_media_type.map_local_vars(last_id);
1089 v.to_media_type.map_local_vars(last_id);
1090 v.transcoding_params.map_local_vars(last_id);
1091 }
1092 Test::Date(v) => {
1093 v.key_list.map_local_vars(last_id);
1094 v.header_name.map_local_vars(last_id);
1095 }
1096 Test::CurrentDate(v) => {
1097 v.key_list.map_local_vars(last_id);
1098 }
1099 Test::Duplicate(v) => {
1100 v.handle.map_local_vars(last_id);
1101 v.dup_match.map_local_vars(last_id);
1102 }
1103 Test::String(v) => {
1104 v.source.map_local_vars(last_id);
1105 v.key_list.map_local_vars(last_id);
1106 }
1107 Test::Environment(v) => {
1108 v.source.map_local_vars(last_id);
1109 v.key_list.map_local_vars(last_id);
1110 }
1111 Test::NotifyMethodCapability(v) => {
1112 v.key_list.map_local_vars(last_id);
1113 v.notification_capability.map_local_vars(last_id);
1114 v.notification_uri.map_local_vars(last_id);
1115 }
1116 Test::ValidNotifyMethod(v) => {
1117 v.notification_uris.map_local_vars(last_id);
1118 }
1119 Test::ValidExtList(v) => {
1120 v.list_names.map_local_vars(last_id);
1121 }
1122 Test::HasFlag(v) => {
1123 v.variable_list.map_local_vars(last_id);
1124 v.flags.map_local_vars(last_id);
1125 }
1126 Test::MailboxExists(v) => {
1127 v.mailbox_names.map_local_vars(last_id);
1128 }
1129 Test::Metadata(v) => {
1130 v.key_list.map_local_vars(last_id);
1131 v.medatata.map_local_vars(last_id);
1132 }
1133 Test::MetadataExists(v) => {
1134 v.annotation_names.map_local_vars(last_id);
1135 v.mailbox.map_local_vars(last_id);
1136 }
1137 Test::MailboxIdExists(v) => {
1138 v.mailbox_ids.map_local_vars(last_id);
1139 }
1140 Test::SpamTest(v) => {
1141 v.value.map_local_vars(last_id);
1142 }
1143 Test::VirusTest(v) => {
1144 v.value.map_local_vars(last_id);
1145 }
1146 Test::SpecialUseExists(v) => {
1147 v.mailbox.map_local_vars(last_id);
1148 v.attributes.map_local_vars(last_id);
1149 }
1150 Test::Vacation(v) => {
1151 v.addresses.map_local_vars(last_id);
1152 v.handle.map_local_vars(last_id);
1153 v.reason.map_local_vars(last_id);
1154 }
1155 #[cfg(test)]
1156 Test::TestCmd { arguments, .. } => {
1157 arguments.map_local_vars(last_id);
1158 }
1159 _ => (),
1160 }
1161 }
1162}
1163
1164impl MapLocalVars for VariableType {
1165 fn map_local_vars(&mut self, last_id: usize) {
1166 match self {
1167 VariableType::Local(id) if *id > last_id => {
1168 *id = (usize::MAX - *id) + last_id;
1169 }
1170 _ => (),
1171 }
1172 }
1173}
1174
1175impl MapLocalVars for Value {
1176 fn map_local_vars(&mut self, last_id: usize) {
1177 match self {
1178 Value::Variable(var) => var.map_local_vars(last_id),
1179 Value::List(items) => items.map_local_vars(last_id),
1180 _ => (),
1181 }
1182 }
1183}
1184
1185impl<T: MapLocalVars> MapLocalVars for Option<T> {
1186 fn map_local_vars(&mut self, last_id: usize) {
1187 if let Some(value) = self {
1188 value.map_local_vars(last_id);
1189 }
1190 }
1191}
1192
1193impl MapLocalVars for Expression {
1194 fn map_local_vars(&mut self, last_id: usize) {
1195 if let Expression::Variable(var) = self {
1196 var.map_local_vars(last_id)
1197 }
1198 }
1199}
1200
1201impl<T: MapLocalVars> MapLocalVars for Vec<T> {
1202 fn map_local_vars(&mut self, last_id: usize) {
1203 for item in self {
1204 item.map_local_vars(last_id);
1205 }
1206 }
1207}
1208
1209impl Block {
1210 pub fn new(btype: Word) -> Self {
1211 Block {
1212 btype,
1213 label: None,
1214 line_num: 0,
1215 line_pos: 0,
1216 last_block_start: 0,
1217 match_test_pos: vec![],
1218 match_test_vars: 0,
1219 if_jmps: vec![],
1220 break_jmps: vec![],
1221 vars_local: AHashMap::new(),
1222 capabilities: AHashSet::new(),
1223 require_pos: usize::MAX,
1224 }
1225 }
1226
1227 pub fn with_label(mut self, label: String) -> Self {
1228 self.label = label.into();
1229 self
1230 }
1231}