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