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